Remove software carpentries logo
[rnaseq-cwl-training.git] / bin / repo_check.py
1 """
2 Check repository settings.
3 """
4
5
6 import sys
7 import os
8 from subprocess import Popen, PIPE
9 import re
10 from argparse import ArgumentParser
11
12 from util import Reporter, require
13
14 # Import this way to produce a more useful error message.
15 try:
16     import requests
17 except ImportError:
18     print('Unable to import requests module: please install requests', file=sys.stderr)
19     sys.exit(1)
20
21
22 # Pattern to match Git command-line output for remotes => (user name, project name).
23 P_GIT_REMOTE = re.compile(r'upstream\s+(?:https://|git@)github.com[:/]([^/]+)/([^.]+)(\.git)?\s+\(fetch\)')
24
25 # Repository URL format string.
26 F_REPO_URL = 'https://github.com/{0}/{1}/'
27
28 # Pattern to match repository URLs => (user name, project name)
29 P_REPO_URL = re.compile(r'https?://github\.com/([^.]+)/([^/]+)/?')
30
31 # API URL format string.
32 F_API_URL = 'https://api.github.com/repos/{0}/{1}/labels'
33
34 # Expected labels and colors.
35 EXPECTED = {
36     'help wanted': 'dcecc7',
37     'status:in progress': '9bcc65',
38     'status:changes requested': '679f38',
39     'status:wait': 'fff2df',
40     'status:refer to cac': 'ffdfb2',
41     'status:need more info': 'ee6c00',
42     'status:blocked': 'e55100',
43     'status:out of scope': 'eeeeee',
44     'status:duplicate': 'bdbdbd',
45     'type:typo text': 'f8bad0',
46     'type:bug': 'eb3f79',
47     'type:formatting': 'ac1357',
48     'type:template and tools': '7985cb',
49     'type:instructor guide': '00887a',
50     'type:discussion': 'b2e5fc',
51     'type:enhancement': '7fdeea',
52     'type:clarification': '00acc0',
53     'type:teaching example': 'ced8dc',
54     'good first issue': 'ffeb3a',
55     'high priority': 'd22e2e'
56 }
57
58
59 def main():
60     """
61     Main driver.
62     """
63
64     args = parse_args()
65     reporter = Reporter()
66     repo_url = get_repo_url(args.repo_url)
67     check_labels(reporter, repo_url)
68     reporter.report()
69
70
71 def parse_args():
72     """
73     Parse command-line arguments.
74     """
75
76     parser = ArgumentParser(description="""Check repository settings.""")
77     parser.add_argument('-r', '--repo',
78                         default=None,
79                         dest='repo_url',
80                         help='repository URL')
81     parser.add_argument('-s', '--source',
82                         default=os.curdir,
83                         dest='source_dir',
84                         help='source directory')
85
86     args, extras = parser.parse_known_args()
87     require(not extras,
88             'Unexpected trailing command-line arguments "{0}"'.format(extras))
89
90     return args
91
92
93 def get_repo_url(repo_url):
94     """
95     Figure out which repository to query.
96     """
97
98     # Explicitly specified.
99     if repo_url is not None:
100         return repo_url
101
102     # Guess.
103     cmd = 'git remote -v'
104     p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE,
105               close_fds=True, universal_newlines=True, encoding='utf-8')
106     stdout_data, stderr_data = p.communicate()
107     stdout_data = stdout_data.split('\n')
108     matches = [P_GIT_REMOTE.match(line) for line in stdout_data]
109     matches = [m for m in matches if m is not None]
110     require(len(matches) == 1,
111             'Unexpected output from git remote command: "{0}"'.format(matches))
112
113     username = matches[0].group(1)
114     require(
115         username, 'empty username in git remote output {0}'.format(matches[0]))
116
117     project_name = matches[0].group(2)
118     require(
119         username, 'empty project name in git remote output {0}'.format(matches[0]))
120
121     url = F_REPO_URL.format(username, project_name)
122     return url
123
124
125 def check_labels(reporter, repo_url):
126     """
127     Check labels in repository.
128     """
129
130     actual = get_labels(repo_url)
131     extra = set(actual.keys()) - set(EXPECTED.keys())
132
133     reporter.check(not extra,
134                    None,
135                    'Extra label(s) in repository {0}: {1}',
136                    repo_url, ', '.join(sorted(extra)))
137
138     missing = set(EXPECTED.keys()) - set(actual.keys())
139     reporter.check(not missing,
140                    None,
141                    'Missing label(s) in repository {0}: {1}',
142                    repo_url, ', '.join(sorted(missing)))
143
144     overlap = set(EXPECTED.keys()).intersection(set(actual.keys()))
145     for name in sorted(overlap):
146         reporter.check(EXPECTED[name].lower() == actual[name].lower(),
147                        None,
148                        'Color mis-match for label {0} in {1}: expected {2}, found {3}',
149                        name, repo_url, EXPECTED[name], actual[name])
150
151
152 def get_labels(repo_url):
153     """
154     Get actual labels from repository.
155     """
156
157     m = P_REPO_URL.match(repo_url)
158     require(
159         m, 'repository URL {0} does not match expected pattern'.format(repo_url))
160
161     username = m.group(1)
162     require(username, 'empty username in repository URL {0}'.format(repo_url))
163
164     project_name = m.group(2)
165     require(
166         username, 'empty project name in repository URL {0}'.format(repo_url))
167
168     url = F_API_URL.format(username, project_name)
169     r = requests.get(url)
170     require(r.status_code == 200,
171             'Request for {0} failed with {1}'.format(url, r.status_code))
172
173     result = {}
174     for entry in r.json():
175         result[entry['name']] = entry['color']
176     return result
177
178
179 if __name__ == '__main__':
180     main()