Mercurial > repos > shellac > sam_consensus_v3
diff env/lib/python3.9/site-packages/planemo/github_util.py @ 0:4f3585e2f14b draft default tip
"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author | shellac |
---|---|
date | Mon, 22 Mar 2021 18:12:50 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/env/lib/python3.9/site-packages/planemo/github_util.py Mon Mar 22 18:12:50 2021 +0000 @@ -0,0 +1,302 @@ +"""Utilities for interacting with Github.""" +from __future__ import absolute_import + +import io +import os +import stat +import tarfile +import tempfile +from distutils.dir_util import copy_tree +from pathlib import Path + +import requests + +from planemo import git +from planemo.io import ( + communicate, + IS_OS_X, +) + +try: + import github + has_github_lib = True +except ImportError: + github = None + has_github_lib = False + +GH_VERSION = "1.5.0" + +NO_GITHUB_DEP_ERROR = ("Cannot use github functionality - " + "PyGithub library not available.") +FAILED_TO_DOWNLOAD_GH = "No gh executable available and it could not be installed." +DEFAULT_REMOTE_NAME = 'planemo-remote' + + +def get_github_config(ctx, allow_anonymous=False): + """Return a :class:`planemo.github_util.GithubConfig` for given configuration.""" + global_github_config = _get_raw_github_config(ctx) + return GithubConfig(global_github_config, allow_anonymous=allow_anonymous) + + +def clone_fork_branch(ctx, target, path, remote_name=DEFAULT_REMOTE_NAME, **kwds): + """Clone, fork, and branch a repository ahead of building a pull request.""" + git.checkout( + ctx, + target, + path, + branch=kwds.get("branch", None), + remote="origin", + from_branch="master" + ) + if kwds.get("fork"): + try: + fork(ctx, path, remote_name=remote_name, **kwds) + except Exception: + pass + if 'GITHUB_USER' in os.environ: + # On CI systems fork doesn't add a local remote under circumstances I don't quite understand, + # but that's probably linked to https://github.com/cli/cli/issues/2722 + cmd = ['git', 'remote', 'add', remote_name, f"https://github.com/{os.environ['GITHUB_USER']}/{os.path.basename(target)}"] + try: + communicate(cmd, cwd=path) + except RuntimeError: + # Can add the remote only once + pass + return remote_name + + +def fork(ctx, path, remote_name=DEFAULT_REMOTE_NAME, **kwds): + """Fork the target repository using ``gh``.""" + gh_path = ensure_gh(ctx, **kwds) + gh_env = get_gh_env(ctx, path, **kwds) + cmd = [gh_path, "repo", "fork", '--remote=true', '--remote-name', remote_name] + communicate(cmd, cwd=path, env=gh_env) + return remote_name + + +def get_or_create_repository(ctx, owner, repo, dry_run=True, **kwds): + """Clones or creates a repository and returns path on disk""" + target = os.path.realpath(tempfile.mkdtemp()) + remote_repo = "https://github.com/{owner}/{repo}".format(owner=owner, repo=repo) + try: + ctx.log('Cloning {}'.format(remote_repo)) + git.clone(ctx, src=remote_repo, dest=target) + except Exception: + ctx.log('Creating repository {}'.format(remote_repo)) + target = create_repository(ctx, owner=owner, repo=repo, dest=target, dry_run=dry_run) + return target + + +def create_repository(ctx, owner, repo, dest, dry_run, **kwds): + gh_path = ensure_gh(ctx, **kwds) + gh_env = get_gh_env(ctx, dry_run=dry_run, **kwds) + cmd = [gh_path, 'repo', 'create', '-y', '--public', "{owner}/{repo}".format(owner=owner, repo=repo)] + if dry_run: + "Would run command '{}'".format(" ".join(cmd)) + git.init(ctx, dest) + return dest + communicate(cmd, env=gh_env, cwd=dest) + return os.path.join(dest, repo) + + +def rm_dir_contents(directory, ignore_dirs=(".git")): + directory = Path(directory) + for item in directory.iterdir(): + if item.name not in ignore_dirs: + if item.is_dir(): + rm_dir_contents(item) + else: + item.unlink() + + +def add_dir_contents_to_repo(ctx, from_dir, target_dir, target_repository_path, version, dry_run, notes=""): + ctx.log("From {} to {}".format(from_dir, target_repository_path)) + rm_dir_contents(target_repository_path) + copy_tree(from_dir, target_repository_path) + git.add(ctx, target_repository_path, target_repository_path) + message = "Update for version {version}".format(version=version) + if notes: + message += "\n{notes}".format(notes=notes) + git.commit(ctx, repo_path=target_repository_path, message=message) + if not dry_run: + git.push(ctx, target_repository_path) + + +def assert_new_version(ctx, version, owner, repo): + remote_repo = "https://github.com/{owner}/{repo}".format(owner=owner, repo=repo) + try: + tags_and_versions = git.ls_remote(ctx, remote_repo=remote_repo) + if "refs/tags/v{}".format(version) in tags_and_versions or "refs/tags/{}".format(version) in tags_and_versions: + raise Exception("Version '{}' for {}/{} exists already. Please change the version.".format(version, owner, repo)) + except RuntimeError: + # repo doesn't exist + pass + + +def changelog_in_repo(target_repository_path): + changelog = [] + for path in os.listdir(target_repository_path): + if 'changelog.md' in path.lower(): + header_seen = False + header_chars = ('---', '===', '~~~') + with(open(os.path.join(target_repository_path, path))) as changelog_fh: + for line in changelog_fh: + if line.startswith(header_chars): + if header_seen: + return "\n".join(changelog[:-1]) + else: + header_seen = True + return "\n".join(changelog) + + +def create_release(ctx, from_dir, target_dir, owner, repo, version, dry_run, notes="", **kwds): + assert_new_version(ctx, version, owner=owner, repo=repo) + target_repository_path = get_or_create_repository(ctx, owner=owner, repo=repo, dry_run=dry_run) + add_dir_contents_to_repo(ctx, from_dir, target_dir, target_repository_path, version=version, dry_run=dry_run, notes=notes) + gh_path = ensure_gh(ctx, **kwds) + gh_env = get_gh_env(ctx, dry_run=dry_run, **kwds) + cmd = [ + gh_path, + 'release', + '-R', + "{}/{}".format(owner, repo), + 'create', + "v{version}".format(version=version), + '--title', + str(version), + ] + cmd.extend(['--notes', notes or changelog_in_repo(target_repository_path)]) + if not dry_run: + communicate(cmd, env=gh_env) + else: + ctx.log("Would run command '{}'".format(" ".join(cmd))) + + +def pull_request(ctx, path, message=None, repo=None, **kwds): + """Create a pull request against the origin of the path using ``gh``.""" + gh_path = ensure_gh(ctx, **kwds) + gh_env = get_gh_env(ctx, path, **kwds) + cmd = [gh_path, "pr", "create"] + if message is None: + cmd.append('--fill') + else: + lines = message.splitlines() + cmd.extend(['--title', lines[0]]) + if len(lines) > 1: + cmd.extend(["--body", "\n".join(lines[1:])]) + if repo: + cmd.extend(['--repo', repo]) + communicate(cmd, env=gh_env) + + +def get_gh_env(ctx, path=None, dry_run=False, **kwds): + """Return a environment dictionary to run gh with given user and repository target.""" + if path is None: + env = {} + else: + env = git.git_env_for(path).copy() + if not dry_run: + github_config = _get_raw_github_config(ctx) + if github_config is not None: + if "access_token" in github_config: + env["GITHUB_TOKEN"] = github_config["access_token"] + + return env + + +def ensure_gh(ctx, **kwds): + """Ensure gh is available for planemo + + This method will ensure ``gh`` is installed at the correct version. + + For more information on ``gh`` checkout https://cli.github.com/ + """ + planemo_gh_path = os.path.join(ctx.workspace, f"gh-{GH_VERSION}") + if not os.path.exists(planemo_gh_path): + _try_download_gh(planemo_gh_path) + + if not os.path.exists(planemo_gh_path): + raise Exception(FAILED_TO_DOWNLOAD_GH) + + return planemo_gh_path + + +def _try_download_gh(planemo_gh_path): + link = _gh_link() + path = Path(planemo_gh_path) + resp = requests.get(link) + with tarfile.open(fileobj=io.BytesIO(resp.content)) as tf, path.open('wb') as outfile: + for member in tf.getmembers(): + if member.name.endswith('bin/gh'): + outfile.write(tf.extractfile(member).read()) + path.chmod(path.stat().st_mode | stat.S_IEXEC) + + +def _get_raw_github_config(ctx): + """Return a :class:`planemo.github_util.GithubConfig` for given configuration.""" + if "github" not in ctx.global_config: + if "GITHUB_TOKEN" in os.environ: + return { + "access_token": os.environ["GITHUB_TOKEN"], + } + if "github" not in ctx.global_config: + raise Exception("github account not found in planemo config and GITHUB_TOKEN environment variables unset") + return ctx.global_config["github"] + + +class GithubConfig(object): + """Abstraction around a Github account. + + Required to use ``github`` module methods that require authorization. + """ + + def __init__(self, config, allow_anonymous=False): + if not has_github_lib: + raise Exception(NO_GITHUB_DEP_ERROR) + if "access_token" not in config: + if not allow_anonymous: + raise Exception("github authentication unavailable") + github_object = github.Github() + else: + github_object = github.Github(config["access_token"]) + self._github = github_object + + +def _gh_link(): + if IS_OS_X: + template_link = "https://github.com/cli/cli/releases/download/v%s/gh_%s_macOS_amd64.tar.gz" + else: + template_link = "https://github.com/cli/cli/releases/download/v%s/gh_%s_linux_amd64.tar.gz" + return template_link % (GH_VERSION, GH_VERSION) + + +def publish_as_gist_file(ctx, path, name="index"): + """Publish a gist. + + More information on gists at http://gist.github.com/. + """ + github_config = get_github_config(ctx, allow_anonymous=False) + user = github_config._github.get_user() + with open(path, "r") as fh: + content = fh.read() + content_file = github.InputFileContent(content) + gist = user.create_gist(False, {name: content_file}) + return gist.files[name].raw_url + + +def get_repository_object(ctx, name): + github_object = get_github_config(ctx, allow_anonymous=True) + return github_object._github.get_repo(name) + + +__all__ = ( + "add_dir_contents_to_repo", + "clone_fork_branch", + "create_release", + "ensure_gh", + "fork", + "get_github_config", + "get_gh_env", + "get_or_create_repository", + "publish_as_gist_file", +)