From 9a5ab181b0ee63b72319678370de7b77c6dd895d Mon Sep 17 00:00:00 2001 From: Greg Wilson Date: Thu, 7 Jul 2016 10:59:38 -0400 Subject: [PATCH] Starting on tool to check repository settings. 1. Created `bin/repo_check.py`. 2. Moved `require()` to `util.py`. 3. Updated `Makefile` with new target. --- Makefile | 5 ++ bin/lesson_check.py | 10 +--- bin/repo_check.py | 139 ++++++++++++++++++++++++++++++++++++++++++++ bin/util.py | 8 +++ 4 files changed, 153 insertions(+), 9 deletions(-) create mode 100755 bin/repo_check.py diff --git a/Makefile b/Makefile index 7116629..ca981d3 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,11 @@ site : lesson-rmd figures : @bin/extract_figures.py -s _episodes -p ${PARSER} > _includes/all_figures.html +# repo-check : check repository settings. +repo-check : + @bin/repo_check.py -s . + + ## clean : clean up junk files. clean : @rm -rf ${DST} diff --git a/bin/lesson_check.py b/bin/lesson_check.py index 016b395..3a43ba3 100755 --- a/bin/lesson_check.py +++ b/bin/lesson_check.py @@ -11,7 +11,7 @@ import json import re from optparse import OptionParser -from util import Reporter, read_markdown, load_yaml, check_unwanted_files +from util import Reporter, read_markdown, load_yaml, check_unwanted_files, require __version__ = '0.2' @@ -252,14 +252,6 @@ def create_checker(args, filename, info): return cls(args, filename, **info) -def require(condition, message): - """Fail if condition not met.""" - - if not condition: - print(message, file=sys.stderr) - sys.exit(1) - - class CheckBase(object): """Base class for checking Markdown files.""" diff --git a/bin/repo_check.py b/bin/repo_check.py new file mode 100755 index 0000000..42f4428 --- /dev/null +++ b/bin/repo_check.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python + +""" +Check repository settings. +""" + +import sys +import os +import re +from optparse import OptionParser + +from util import Reporter, load_yaml, require + +# Import this way to produce a more useful error message. +try: + import requests +except ImportError: + print('Unable to import requests module: please install requests', file=sys.stderr) + sys.exit(1) + + +# Pattern to match repository URLs and extract username and project name. +P_REPO_URL = re.compile(r'https?://github\.com/([^.]+)/([^/]+)/?') + +# API URL format string. +F_API_URL = 'https://api.github.com/repos/{0}/{1}/labels' + +# Expected labels and colors. +EXPECTED = { + 'bug' : 'bd2c00', + 'discussion' : 'fc8dc1', + 'enhancement' : '9cd6dc', + 'help-wanted' : 'f4fd9c', + 'instructor-training' : '6e5494', + 'newcomer-friendly' : 'eec275', + 'question' : '808040', + 'template-and-tools' : '2b3990', + 'work-in-progress' : '7ae78e' +} + + +def main(): + """ + Main driver. + """ + + args = parse_args() + reporter = Reporter() + repo_url = get_repo_url(args.source_dir) + check_labels(reporter, repo_url) + reporter.report() + + +def parse_args(): + """ + Parse command-line arguments. + """ + + parser = OptionParser() + parser.add_option('-s', '--source', + default=os.curdir, + dest='source_dir', + help='source directory') + + args, extras = parser.parse_args() + require(not extras, + 'Unexpected trailing command-line arguments "{0}"'.format(extras)) + + return args + + +def get_repo_url(source_dir): + """ + Figure out which repository to query. + """ + + config_file = os.path.join(source_dir, '_config.yml') + config = load_yaml(config_file) + if 'repo' not in config: + print('"repo" not found in {0}'.format(config_file), file=sys.stderr) + sys.exit(1) + + return config['repo'] + + +def check_labels(reporter, repo_url): + """ + Check labels in repository. + """ + + actual = get_labels(repo_url) + extra = set(actual.keys()) - set(EXPECTED.keys()) + + reporter.check(not extra, + None, + 'Extra label(s) in repository {0}: {1}', + repo_url, ', '.join(sorted(extra))) + + missing = set(EXPECTED.keys()) - set(actual.keys()) + reporter.check(not missing, + None, + 'Missing label(s) in repository {0}: {1}', + repo_url, ', '.join(sorted(missing))) + + overlap = set(EXPECTED.keys()).intersection(set(actual.keys())) + for name in sorted(overlap): + reporter.check(EXPECTED[name] == actual[name], + None, + 'Color mis-match for label {0} in {1}: expected {2}, found {3}', + name, repo_url, EXPECTED[name], actual[name]) + + +def get_labels(repo_url): + """ + Get actual labels from repository. + """ + + m = P_REPO_URL.match(repo_url) + require(m, 'repository URL {0} does not match expected pattern'.format(repo_url)) + + username = m.group(1) + require(username, 'empty username in repository URL {0}'.format(repo_url)) + + project_name = m.group(2) + require(username, 'empty project name in repository URL {0}'.format(repo_url)) + + url = F_API_URL.format(username, project_name) + r = requests.get(url) + require(r.status_code == 200, + 'Request for {0} failed with {1}'.format(url, r.status_code)) + + result = {} + for entry in r.json(): + result[entry['name']] = entry['color'] + return result + + +if __name__ == '__main__': + main() diff --git a/bin/util.py b/bin/util.py index 2341bdb..df350e7 100644 --- a/bin/util.py +++ b/bin/util.py @@ -151,3 +151,11 @@ def check_unwanted_files(dir_path, reporter): reporter.check(not os.path.exists(path), path, "Unwanted file found") + + +def require(condition, message): + """Fail if condition not met.""" + + if not condition: + print(message, file=sys.stderr) + sys.exit(1) -- 2.30.2