Completed
Pull Request — master (#34)
by Gonzalo
56s
created

main()   F

Complexity

Conditions 11

Size

Total Lines 146

Duplication

Lines 0
Ratio 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
cc 11
c 7
b 0
f 0
dl 0
loc 146
rs 3.1764

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