Starting on tool to check repository settings.
authorGreg Wilson <gvwilson@third-bit.com>
Thu, 7 Jul 2016 14:59:38 +0000 (10:59 -0400)
committerGreg Wilson <gvwilson@third-bit.com>
Thu, 7 Jul 2016 14:59:38 +0000 (10:59 -0400)
1.  Created `bin/repo_check.py`.
2.  Moved `require()` to `util.py`.
3.  Updated `Makefile` with new target.

Makefile
bin/lesson_check.py
bin/repo_check.py [new file with mode: 0755]
bin/util.py

index 71166297823615ef511eda638c301c55cc02036a..ca981d3ee95ffaf6d07603673c30db648cde4e0a 100644 (file)
--- 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}
index 016b395451659e299cdeb2f60af299cd0f9e96bc..3a43ba37467c5377dd351a1e143653e351fcd812 100755 (executable)
@@ -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 (executable)
index 0000000..42f4428
--- /dev/null
@@ -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()
index 2341bdb006b9bfae5d85243cc5d13dfe60e3b652..df350e7a7895bdfc656b1e64c3a66eead0a715d0 100644 (file)
@@ -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)