Exemple #1
0
def download_gist(gist):
    """Download the gist specified by owner/name string.

    :return: Whether the gist has been successfully downloaded
    """
    logger.debug("downloading gist %s ...", gist)

    owner, gist_name = gist.split('/', 1)
    for gist_json in iter_gists(owner):
        # GitHub names gists after their first files in alphabetical order
        # TODO(xion): warn the user when this could create problems,
        # i.e. when a single owner has two separate gists named the same way
        filename = list(sorted(gist_json['files'].keys()))[0]
        if filename != gist_name:
            continue

        # the gist should be placed inside a directory named after its ID
        clone_needed = True
        gist_dir = GISTS_DIR / str(gist_json['id'])
        if gist_dir.exists():
            # this is an inconsistent state, as it means the binary
            # for a gist is missing, while the repository is not;
            # no real harm in that, but we should report it anyway
            logger.warning("gist %s already downloaded")
            clone_needed = False

        # clone it if necessary (which is usually the case)
        if clone_needed:
            logger.debug("gist %s found, cloning its repository...", gist)
            ensure_path(gist_dir)
            git_clone_run = run('git clone %s %s' % (
                gist_json['git_pull_url'], gist_dir))
            if git_clone_run.status_code != 0:
                logger.error(
                    "cloning repository for gist %s failed (exitcode %s)",
                    gist, git_clone_run.status_code)
                join(git_clone_run)
            logger.debug("gist %s successfully cloned", gist)

        # make sure the gist executable is, in fact, executable
        # TODO(xion): fix the hashbang while we're at it
        gist_exec = gist_dir / filename
        gist_exec.chmod(GIST_EXEC_PERMISSIONS)
        logger.debug("adjusted permissions for gist file %s", gist_exec)

        # create symlink from BIN_DIR/<owner>/<gist_name>
        # to the gist's executable file
        gist_owner_bin_dir = BIN_DIR / owner
        ensure_path(gist_owner_bin_dir)
        gist_link = gist_owner_bin_dir / filename
        if not gist_link.exists():
            gist_link.symlink_to(path_vector(from_=gist_link, to=gist_exec))
            logger.debug("symlinked gist 'binary' %s to executable %s",
                         gist_link, gist_exec)

        if clone_needed:
            logger.info("gist %s downloaded sucessfully", gist)
        return True

    return False
Exemple #2
0
def run_gist_url(gist, args=(), local=False):
    """Run the gist specified by an URL.

    If successful, this function does not return.

    :param gist: Gist as :class:`Gist` object
    :param args: Arguments to pass to the gist
    :param local: Whether to only run gists that are available locally
    """
    try:
        gist_info = get_gist_info(gist.id)
    except requests.exceptions.HTTPError:
        error("couldn't retrieve GitHub gist %s/%s", gist.owner, gist.id)

    # warn if the actual gist owner is different than the one in the URL;
    # TODO(xion): consider asking for confirmation;
    # there may be some phishing scenarios possible here
    owner = gist_info.get('owner', {}).get('login')
    if gist.owner != owner:
        logger.warning("gist %s is owned by %s, not %s", gist.id, owner,
                       gist.owner)

    gist_name = list(sorted(gist_info['files'].keys()))[0]
    actual_gist_ref = '/'.join((owner, gist_name))

    ensure_gist(actual_gist_ref, local=local)
    return run_named_gist(actual_gist_ref, args)
Exemple #3
0
def update_gist(gist):
    """Pull the latest version of the gist specified by owner/name string.

    :return: Whether the gist has been successfully updated
    """
    logger.debug("updating gist %s ...", gist)

    gist_id = get_gist_id(gist)
    gist_dir = GISTS_DIR / gist_id
    git_pull_run = run('git pull', cwd=str(gist_dir))
    if git_pull_run.status_code != 0:
        # TODO(xion): detect conflicts and do `git reset --merge` automatically
        logger.warning("pulling changes to gist %s failed (exitcode %s)",
                       gist, git_pull_run.status_code)
        join(git_pull_run)
    logger.info("gist %s successfully updated", gist)

    return True
Exemple #4
0
def run_named_gist(gist, args=()):
    """Run the gist specified by owner/name string.

    This function does not return, because the whole process
    is replaced by the gist's executable.

    :param gist: Gist as :class:`Gist` object or <owner>/<name> string
    :param args: Arguments to pass to the gist
    """
    if isinstance(gist, Gist):
        gist = gist.ref

    logger.info("running gist %s ...", gist)

    executable = bytes(BIN_DIR / gist)
    try:
        os.execv(executable, [executable] + list(args))
    except OSError as e:
        if e.errno != 8:  # Exec format error
            raise

        logger.warning(
            "couldn't run gist %s directly -- "
            "does it have a proper hashbang?", gist)

        # try to figure out the interpreter to use based on file extension
        # contained within the gist name
        extension = Path(gist).suffix
        if not extension:
            # TODO(xion): use MIME type from GitHub as additional hint
            # as to the choice of interpreter
            error(
                "can't deduce interpreter for gist %s "
                "without file extension", gist)
        interpreter = COMMON_INTERPRETERS.get(extension)
        if not interpreter:
            error("no interpreter found for extension '%s'", extension)

        # format an interpreter-specific command line
        # and execute it within current process (hence the argv shenanigans)
        cmd = interpreter % dict(script=str(BIN_DIR / gist),
                                 args=' '.join(map(shell_quote, args)))
        cmd_argv = shell_split(cmd)
        os.execvp(cmd_argv[0], cmd_argv)
Exemple #5
0
 def _on_cache_rescue(self, path, content):
     if flags.command == GistCommand.INFO:
         logger.warning("could not communicate with GitHub -- "
                        "gist information may be out of date")