Completed
Push — master ( 702f68...d96c91 )
by Gonzalo
9s
created

main()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 9
Bugs 0 Features 0
Metric Value
cc 1
c 9
b 0
f 0
dl 0
loc 3
rs 10
1
# -*- coding: utf-8 -*-
2
# -----------------------------------------------------------------------------
3
# Copyright (c) The Spyder Development Team
4
#
5
# Licensed under the terms of the MIT License
6
# (See LICENSE.txt for details)
7
# -----------------------------------------------------------------------------
8
"""Build a list of issues and pull requests per Github milestone."""
9
10
from __future__ import print_function
11
12
# Standard library imports
13
import argparse
14
import getpass
15
import re
16
import sys
17
import time
18
19
# Third party imports
20
from jinja2 import Template
21
22
# Local imports
23
from loghub.repo import GitHubRepo
24
from loghub.templates import (CHANGELOG_GROUPS_TEMPLATE_PATH,
25
                              CHANGELOG_TEMPLATE_PATH, RELEASE_TEMPLATE_PATH)
26
27
PY2 = sys.version[0] == '2'
28
29
30
def main():
31
    """Main script."""
32
    parse_arguments(skip=False)
33
34
35
def parse_arguments(skip=False):
36
    """Parse CLI arguments."""
37
    # Cli options
38
    parser = argparse.ArgumentParser(
39
        description='Script to print the list of issues and pull requests '
40
        'closed in a given milestone')
41
    parser.add_argument(
42
        'repository',
43
        help="Repository name to generate the Changelog for, in the form "
44
        "user/repo or org/repo (e.g. spyder-ide/spyder)")
45
    parser.add_argument(
46
        '-m',
47
        '--milestone',
48
        action="store",
49
        dest="milestone",
50
        default='',
51
        help="Github milestone to get issues and pull requests for")
52
    parser.add_argument(
53
        '-ilg',
54
        '--issue-label-group',
55
        action="append",
56
        nargs='+',
57
        dest="issue_label_groups",
58
        help="Groups the generated issues by the specified label. This option"
59
        "Takes 1 or 2 arguments, where the first one is the label to "
60
        "match and the second one is the label to print on the final"
61
        "output")
62
    parser.add_argument(
63
        '-ilr',
64
        '--issue-label-regex',
65
        action="store",
66
        dest="issue_label_regex",
67
        default='',
68
        help="Label issue filter using a regular expression filter")
69
    parser.add_argument(
70
        '-plr',
71
        '--pr-label-regex',
72
        action="store",
73
        dest="pr_label_regex",
74
        default='',
75
        help="Label pull requets filter using a regular expression filter")
76
    parser.add_argument(
77
        '-st',
78
        '--since-tag',
79
        action="store",
80
        dest="since_tag",
81
        default='',
82
        help="Github issues and pull requests since tag")
83
    parser.add_argument(
84
        '-ut',
85
        '--until-tag',
86
        action="store",
87
        dest="until_tag",
88
        default='',
89
        help="Github issues and pull requests until tag")
90
    parser.add_argument(
91
        '-b',
92
        '--branch',
93
        action="store",
94
        dest="branch",
95
        default='',
96
        help="Github base branch for merged PRs")
97
    parser.add_argument(
98
        '-f',
99
        '--format',
100
        action="store",
101
        dest="output_format",
102
        default='changelog',
103
        help="Format for print, either 'changelog' (for "
104
        "Changelog.md file) or 'release' (for the Github "
105
        "Releases page). Default is 'changelog'. The "
106
        "'release' option doesn't generate Markdown "
107
        "hyperlinks.")
108
    parser.add_argument(
109
        '--template',
110
        action="store",
111
        dest="template",
112
        default='',
113
        help="Use a custom Jinja2 template file ")
114
    parser.add_argument(
115
        '-u',
116
        '--user',
117
        action="store",
118
        dest="user",
119
        default='',
120
        help="Github user name")
121
    parser.add_argument(
122
        '-p',
123
        '--password',
124
        action="store",
125
        dest="password",
126
        default='',
127
        help="Github user password")
128
    parser.add_argument(
129
        '-t',
130
        '--token',
131
        action="store",
132
        dest="token",
133
        default='',
134
        help="Github access token")
135
    options = parser.parse_args()
136
137
    username = options.user
138
    password = options.password
139
    milestone = options.milestone
140
    issue_label_groups = options.issue_label_groups
141
142
    if username and not password:
143
        password = getpass.getpass()
144
145
    # Check if repo given
146
    if not options.repository:
147
        print('LOGHUB: Please define a repository name to this script. '
148
              'See its help')
149
        sys.exit(1)
150
151
    # Check if milestone or tag given
152
    if not milestone and not options.since_tag:
153
        print('\nLOGHUB: Querying all issues\n')
154
    elif milestone:
155
        print('\nLOGHUB: Querying issues for milestone {0}'
156
              '\n'.format(milestone))
157
158
    new_issue_label_groups = []
159
    if issue_label_groups:
160
        for item in issue_label_groups:
161
            dic = {}
162
            if len(item) == 1:
163
                dic['label'] = item[0]
164
                dic['name'] = item[0]
165
            elif len(item) >= 2:
166
                dic['label'] = item[0]
167
                dic['name'] = item[1]
168
            new_issue_label_groups.append(dic)
169
170
    if not skip:
171
        create_changelog(
172
            repo=options.repository,
173
            username=username,
174
            password=password,
175
            token=options.token,
176
            milestone=milestone,
177
            since_tag=options.since_tag,
178
            until_tag=options.until_tag,
179
            branch=options.branch,
180
            issue_label_regex=options.issue_label_regex,
181
            pr_label_regex=options.pr_label_regex,
182
            output_format=options.output_format,
183
            template_file=options.template,
184
            issue_label_groups=new_issue_label_groups)
185
186
    return options
187
188
189
def create_changelog(repo=None,
190
                     username=None,
191
                     password=None,
192
                     token=None,
193
                     milestone=None,
194
                     since_tag=None,
195
                     until_tag=None,
196
                     branch=None,
197
                     output_format='changelog',
198
                     issue_label_regex='',
199
                     pr_label_regex='',
200
                     template_file=None,
201
                     issue_label_groups=None):
202
    """Create changelog data."""
203
    # Instantiate Github API
204
    gh = GitHubRepo(
205
        username=username,
206
        password=password,
207
        token=token,
208
        repo=repo, )
209
210
    version = until_tag or None
211
    milestone_number = None
212
    closed_at = None
213
    since = None
214
    until = None
215
216
    # Set milestone or from tag
217
    if milestone and not since_tag:
218
        milestone_data = gh.milestone(milestone)
219
        milestone_number = milestone_data['number']
220
        closed_at = milestone_data['closed_at']
221
        version = milestone.replace('v', '')
222
    elif not milestone and since_tag:
223
        since = gh.tag(since_tag)['tagger']['date']
224
        if until_tag:
225
            until = gh.tag(until_tag)['tagger']['date']
226
            closed_at = until
227
228
    # This returns issues and pull requests
229
    issues = gh.issues(
230
        milestone=milestone_number,
231
        state='closed',
232
        since=since,
233
        until=until,
234
        branch=branch, )
235
236
    # Filter by regex if available
237
    filtered_issues, filtered_prs = [], []
238
    issue_pattern = re.compile(issue_label_regex)
239
    pr_pattern = re.compile(pr_label_regex)
240
    for issue in issues:
241
        is_pr = bool(issue.get('pull_request'))
242
        is_issue = not is_pr
243
        labels = ' '.join(issue.get('loghub_label_names'))
244
245
        if is_issue and issue_label_regex:
246
            issue_valid = bool(issue_pattern.search(labels))
247
            if issue_valid:
248
                filtered_issues.append(issue)
249
        elif is_pr and pr_label_regex:
250
            pr_valid = bool(pr_pattern.search(labels))
251
            if pr_valid:
252
                filtered_prs.append(issue)
253
        elif is_issue and not issue_label_regex:
254
            filtered_issues.append(issue)
255
        elif is_pr and not pr_label_regex:
256
            filtered_prs.append(issue)
257
258
    # If issue label grouping, filter issues
259
    new_filtered_issues = []
260
    if issue_label_groups:
261
        for issue in issues:
262
            for label_group_dic in issue_label_groups:
263
                labels = issue.get('loghub_label_names')
264
                label = label_group_dic['label']
265
                if label in labels:
266
                    new_filtered_issues.append(issue)
267
    else:
268
        new_filtered_issues = filtered_issues
269
270
    return format_changelog(
271
        repo,
272
        new_filtered_issues,
273
        filtered_prs,
274
        version,
275
        closed_at=closed_at,
276
        output_format=output_format,
277
        template_file=template_file,
278
        issue_label_groups=issue_label_groups)
279
280
281
def format_changelog(repo,
282
                     issues,
283
                     prs,
284
                     version,
285
                     closed_at=None,
286
                     output_format='changelog',
287
                     output_file='CHANGELOG.temp',
288
                     template_file=None,
289
                     issue_label_groups=None):
290
    """Create changelog data."""
291
    # Header
292
    if version and version[0] == 'v':
293
        version = version.replace('v', '')
294
    else:
295
        version = '<RELEASE_VERSION>'
296
297
    if closed_at:
298
        close_date = closed_at.split('T')[0]
299
    else:
300
        close_date = time.strftime("%Y/%m/%d")
301
302
    # Load template
303
    if template_file:
304
        filepath = template_file
305
    else:
306
        if output_format == 'changelog':
307
            filepath = CHANGELOG_TEMPLATE_PATH
308
        else:
309
            filepath = RELEASE_TEMPLATE_PATH
310
311
    if issue_label_groups:
312
        filepath = CHANGELOG_GROUPS_TEMPLATE_PATH
313
314
    with open(filepath) as f:
315
        data = f.read()
316
317
    repo_owner, repo_name = repo.split('/')
318
    template = Template(data)
319
    rendered = template.render(
320
        issues=issues,
321
        pull_requests=prs,
322
        version=version,
323
        close_date=close_date,
324
        repo_full_name=repo,
325
        repo_owner=repo_owner,
326
        repo_name=repo_name,
327
        issue_label_groups=issue_label_groups, )
328
329
    print('#' * 79)
330
    print(rendered)
331
    print('#' * 79)
332
333
    with open(output_file, 'w') as f:
334
        f.write(rendered)
335
336
    return rendered
337
338
339
if __name__ == '__main__':  # yapf: disable
340
    main()
341