Completed
Push — master ( db0eb2...ce6edf )
by Gonzalo
32s queued 27s
created

format_changelog()   D

Complexity

Conditions 10

Size

Total Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 14
Bugs 2 Features 0
Metric Value
cc 10
c 14
b 2
f 0
dl 0
loc 59
rs 4.8648

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like format_changelog() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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