Completed
Pull Request — master (#65)
by Gonzalo
01:02
created

render_changelog()   C

Complexity

Conditions 8

Size

Total Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
c 2
b 0
f 0
dl 0
loc 51
rs 5.2591

How to fix   Long Method   

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:

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
"""Loghub filter and formatter."""
9
10
# yapf: disable
11
12
# Standard library imports
13
from collections import OrderedDict
14
import codecs
15
import re
16
import time
17
18
# Third party imports
19
from jinja2 import Template
20
21
# Local imports
22
from loghub.core.repo import GitHubRepo
23
from loghub.templates import (CHANGELOG_GROUPS_TEMPLATE_PATH,
24
                              CHANGELOG_TEMPLATE_PATH,
25
                              RELEASE_GROUPS_TEMPLATE_PATH,
26
                              RELEASE_TEMPLATE_PATH)
27
28
29
# yapf: enable
30
31
32
def filter_prs_by_regex(issues, pr_label_regex):
33
    """Filter prs by issue regex."""
34
    filtered_prs = []
35
    pr_pattern = re.compile(pr_label_regex)
36
37
    for issue in issues:
38
        is_pr = bool(issue.get('pull_request'))
39
        labels = ' '.join(issue.get('loghub_label_names'))
40
41
        if is_pr and pr_label_regex:
42
            pr_valid = bool(pr_pattern.search(labels))
43
            if pr_valid:
44
                filtered_prs.append(issue)
45
        if is_pr and not pr_label_regex:
46
            filtered_prs.append(issue)
47
48
    return filtered_prs
49
50
51
def filter_issues_by_regex(issues, issue_label_regex):
52
    """Filter issues by issue regex."""
53
    filtered_issues = []
54
    issue_pattern = re.compile(issue_label_regex)
55
56
    for issue in issues:
57
        is_pr = bool(issue.get('pull_request'))
58
        is_issue = not is_pr
59
        labels = ' '.join(issue.get('loghub_label_names'))
60
61
        if is_issue and issue_label_regex:
62
            issue_valid = bool(issue_pattern.search(labels))
63
            if issue_valid:
64
                filtered_issues.append(issue)
65
        elif is_issue and not issue_label_regex:
66
            filtered_issues.append(issue)
67
68
    return filtered_issues
69
70
71
def filter_issue_label_groups(issues, issue_label_groups):
72
    """Filter issues by the label groups."""
73
    grouped_filtered_issues = OrderedDict()
74
    print(issue_label_groups)
75
    if issue_label_groups:
76
        new_filtered_issues = []
77
        for label_group_dic in issue_label_groups:
78
            grouped_filtered_issues[label_group_dic['name']] = []
79
80
        for issue in issues:
81
            for label_group_dic in issue_label_groups:
82
                labels = issue.get('loghub_label_names')
83
                label = label_group_dic['label']
84
                name = label_group_dic['name']
85
                if label in labels:
86
                    grouped_filtered_issues[name].append(issue)
87
                    new_filtered_issues.append(issue)
88
89
        # Remove any empty groups
90
        for group, grouped_issues in grouped_filtered_issues.copy().items():
91
            if not grouped_issues:
92
                grouped_filtered_issues.pop(group)
93
    else:
94
        new_filtered_issues = issues
95
96
    return new_filtered_issues, grouped_filtered_issues
97
98
99
def create_changelog(repo=None,
100
                     username=None,
101
                     password=None,
102
                     token=None,
103
                     milestone=None,
104
                     since_tag=None,
105
                     until_tag=None,
106
                     branch=None,
107
                     output_format='changelog',
108
                     issue_label_regex='',
109
                     pr_label_regex='',
110
                     template_file=None,
111
                     issue_label_groups=None,
112
                     batch=None,
113
                     show_prs=True):
114
    """Create changelog data for single and batched mode."""
115
    gh = GitHubRepo(
116
        username=username,
117
        password=password,
118
        token=token,
119
        repo=repo, )
120
121
    all_changelogs = []
122
    version_tag_prefix = 'v'
123
124
    if batch:
125
        # This will get all the issues, might eat up the api rate limit!
126
        base_issues = issues = gh.issues(state='closed', branch=branch)
127
        if batch == 'milestones':
128
            milestones = [i.get('title') for i in gh.milestones()]
129
            empty_items = [None] * len(milestones)
130
            items = zip(*(milestones, empty_items, empty_items))
131
        elif batch == 'tags':
132
            tags = [
133
                i.get('ref', '').replace('refs/tags/', '') for i in gh.tags()
134
            ]
135
            since_tags = [None] + tags
136
            until_tags = tags + [None]
137
            empty_items = [None] * len(since_tags)
138
            items = zip(*(empty_items, since_tags, until_tags))
139
    else:
140
        base_issues = None
141
        if milestone:
142
            items = [(milestone, None, None)]
143
        else:
144
            items = [(None, since_tag, until_tag)]
145
146
    for (milestone, since_tag, until_tag) in reversed(items):
147
        version = until_tag or None
148
        closed_at = None
149
        since = None
150
        until = None
151
152
        # Set milestone or from tag
153
        if milestone and not since_tag:
154
            milestone_data = gh.milestone(milestone)
155
            closed_at = milestone_data['closed_at']
156
            version = milestone
157
158
            if version.startswith(version_tag_prefix):
159
                version = version[len(version_tag_prefix):]
160
161
        elif not milestone and since_tag:
162
            since = gh.tag(since_tag)['tagger']['date']
163
            if until_tag:
164
                until = gh.tag(until_tag)['tagger']['date']
165
                closed_at = until
166
167
        # This returns issues and pull requests
168
        issues = gh.issues(
169
            milestone=milestone,
170
            state='closed',
171
            since=since,
172
            until=until,
173
            branch=branch,
174
            base_issues=base_issues, )
175
176
        # Filter by regex if available
177
        filtered_prs = filter_prs_by_regex(issues, pr_label_regex)
178
        filtered_issues = filter_issues_by_regex(issues, issue_label_regex)
179
180
        # If issue label grouping, filter issues
181
        new_filtered_issues, issue_label_groups = filter_issue_label_groups(
182
            filtered_issues, issue_label_groups)
183
184
        ch = render_changelog(
185
            repo,
186
            new_filtered_issues,
187
            filtered_prs,
188
            version,
189
            closed_at=closed_at,
190
            output_format=output_format,
191
            template_file=template_file,
192
            issue_label_groups=issue_label_groups,
193
            show_prs=show_prs)
194
195
        all_changelogs.append(ch)
196
197
    changelog = '\n'.join(all_changelogs)
198
    write_changelog(changelog=changelog)
199
200
    return changelog
201
202
203
def render_changelog(repo,
204
                     issues,
205
                     prs,
206
                     version=None,
207
                     closed_at=None,
208
                     output_format='changelog',
209
                     template_file=None,
210
                     issue_label_groups=None,
211
                     show_prs=True):
212
    """Render changelog data on a jinja template."""
213
    # Header
214
    if not version:
215
        version = '<RELEASE_VERSION>'
216
217
    if closed_at:
218
        close_date = closed_at.split('T')[0]
219
    else:
220
        close_date = time.strftime("%Y/%m/%d")
221
222
    # Load template
223
    if template_file:
224
        filepath = template_file
225
    else:
226
        if issue_label_groups:
227
            if output_format == 'changelog':
228
                filepath = CHANGELOG_GROUPS_TEMPLATE_PATH
229
            else:
230
                filepath = RELEASE_GROUPS_TEMPLATE_PATH
231
        else:
232
            if output_format == 'changelog':
233
                filepath = CHANGELOG_TEMPLATE_PATH
234
            else:
235
                filepath = RELEASE_TEMPLATE_PATH
236
237
    with open(filepath) as f:
238
        data = f.read()
239
240
    repo_owner, repo_name = repo.split('/')
241
    template = Template(data)
242
    rendered = template.render(
243
        issues=issues,
244
        pull_requests=prs,
245
        version=version,
246
        close_date=close_date,
247
        repo_full_name=repo,
248
        repo_owner=repo_owner,
249
        repo_name=repo_name,
250
        issue_label_groups=issue_label_groups,
251
        show_prs=show_prs, )
252
253
    return rendered
254
255
256
def write_changelog(changelog, output_file='CHANGELOG.temp'):
257
    """Output rendered result to prompt and file."""
258
    print('#' * 79)
259
    print(changelog)
260
    print('#' * 79)
261
262
    with codecs.open(output_file, "w", "utf-8") as f:
263
        f.write(changelog)
264