~ubuntu-branches/debian/jessie/sqlalchemy/jessie

« back to all changes in this revision

Viewing changes to doc/build/builder/changelog.py

  • Committer: Package Import Robot
  • Author(s): Piotr Ożarowski, Jakub Wilk, Piotr Ożarowski
  • Date: 2013-07-06 20:53:52 UTC
  • mfrom: (1.4.23) (16.1.17 experimental)
  • Revision ID: package-import@ubuntu.com-20130706205352-ryppl1eto3illd79
Tags: 0.8.2-1
[ Jakub Wilk ]
* Use canonical URIs for Vcs-* fields.

[ Piotr Ożarowski ]
* New upstream release
* Upload to unstable
* Build depend on python3-all instead of -dev, extensions are not built for
  Python 3.X 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import re
 
2
from sphinx.util.compat import Directive
 
3
from docutils.statemachine import StringList
 
4
from docutils import nodes, utils
 
5
import textwrap
 
6
import itertools
 
7
import collections
 
8
import md5
 
9
 
 
10
def _comma_list(text):
 
11
    return re.split(r"\s*,\s*", text.strip())
 
12
 
 
13
def _parse_content(content):
 
14
    d = {}
 
15
    d['text'] = []
 
16
    idx = 0
 
17
    for line in content:
 
18
        idx += 1
 
19
        m = re.match(r' *\:(.+?)\:(?: +(.+))?', line)
 
20
        if m:
 
21
            attrname, value = m.group(1, 2)
 
22
            d[attrname] = value or ''
 
23
        else:
 
24
            break
 
25
    d["text"] = content[idx:]
 
26
    return d
 
27
 
 
28
 
 
29
class EnvDirective(object):
 
30
    @property
 
31
    def env(self):
 
32
        return self.state.document.settings.env
 
33
 
 
34
class ChangeLogDirective(EnvDirective, Directive):
 
35
    has_content = True
 
36
 
 
37
    type_ = "change"
 
38
 
 
39
    default_section = 'misc'
 
40
 
 
41
    def _organize_by_section(self, changes):
 
42
        compound_sections = [(s, s.split(" ")) for s in
 
43
                                self.sections if " " in s]
 
44
 
 
45
        bysection = collections.defaultdict(list)
 
46
        all_sections = set()
 
47
        for rec in changes:
 
48
            inner_tag = rec['tags'].intersection(self.inner_tag_sort)
 
49
            if inner_tag:
 
50
                inner_tag = inner_tag.pop()
 
51
            else:
 
52
                inner_tag = ""
 
53
 
 
54
            for compound, comp_words in compound_sections:
 
55
                if rec['tags'].issuperset(comp_words):
 
56
                    bysection[(compound, inner_tag)].append(rec)
 
57
                    all_sections.add(compound)
 
58
                    break
 
59
            else:
 
60
                intersect = rec['tags'].intersection(self.sections)
 
61
                if intersect:
 
62
                    for sec in rec['sorted_tags']:
 
63
                        if sec in intersect:
 
64
                            bysection[(sec, inner_tag)].append(rec)
 
65
                            all_sections.add(sec)
 
66
                            break
 
67
                else:
 
68
                    bysection[(self.default_section, inner_tag)].append(rec)
 
69
        return bysection, all_sections
 
70
 
 
71
    @classmethod
 
72
    def changes(cls, env):
 
73
        return env.temp_data['ChangeLogDirective_%s_changes' % cls.type_]
 
74
 
 
75
    def _setup_run(self):
 
76
        self.sections = self.env.config.changelog_sections
 
77
        self.inner_tag_sort = self.env.config.changelog_inner_tag_sort + [""]
 
78
        self.env.temp_data['ChangeLogDirective_%s_changes' % self.type_] = []
 
79
        self._parsed_content = _parse_content(self.content)
 
80
 
 
81
        p = nodes.paragraph('', '',)
 
82
        self.state.nested_parse(self.content[1:], 0, p)
 
83
 
 
84
    def run(self):
 
85
        self._setup_run()
 
86
        changes = self.changes(self.env)
 
87
        output = []
 
88
 
 
89
        self.version = version = self._parsed_content.get('version', '')
 
90
        id_prefix = "%s-%s" % (self.type_, version)
 
91
        topsection = self._run_top(id_prefix)
 
92
        output.append(topsection)
 
93
 
 
94
        bysection, all_sections = self._organize_by_section(changes)
 
95
 
 
96
        counter = itertools.count()
 
97
 
 
98
        sections_to_render = [s for s in self.sections if s in all_sections]
 
99
        if not sections_to_render:
 
100
            for cat in self.inner_tag_sort:
 
101
                append_sec = self._append_node()
 
102
 
 
103
                for rec in bysection[(self.default_section, cat)]:
 
104
                    rec["id"] = "%s-%s" % (id_prefix, next(counter))
 
105
 
 
106
                    self._render_rec(rec, None, cat, append_sec)
 
107
 
 
108
                if append_sec.children:
 
109
                    topsection.append(append_sec)
 
110
        else:
 
111
            for section in sections_to_render + [self.default_section]:
 
112
                sec = nodes.section('',
 
113
                        nodes.title(section, section),
 
114
                        ids=["%s-%s" % (id_prefix, section.replace(" ", "-"))]
 
115
                )
 
116
 
 
117
                append_sec = self._append_node()
 
118
                sec.append(append_sec)
 
119
 
 
120
                for cat in self.inner_tag_sort:
 
121
                    for rec in bysection[(section, cat)]:
 
122
                        rec["id"] = "%s-%s" % (id_prefix, next(counter))
 
123
                        self._render_rec(rec, section, cat, append_sec)
 
124
 
 
125
                if append_sec.children:
 
126
                    topsection.append(sec)
 
127
 
 
128
        return output
 
129
 
 
130
    def _append_node(self):
 
131
        return nodes.bullet_list()
 
132
 
 
133
    def _run_top(self, id_prefix):
 
134
        version = self._parsed_content.get('version', '')
 
135
        topsection = nodes.section('',
 
136
                nodes.title(version, version),
 
137
                ids=[id_prefix]
 
138
            )
 
139
 
 
140
        if self._parsed_content.get("released"):
 
141
            topsection.append(nodes.Text("Released: %s" %
 
142
                        self._parsed_content['released']))
 
143
        else:
 
144
            topsection.append(nodes.Text("no release date"))
 
145
 
 
146
        intro_para = nodes.paragraph('', '')
 
147
        for len_, text in enumerate(self._parsed_content['text']):
 
148
            if ".. change::" in text:
 
149
                break
 
150
        if len_:
 
151
            self.state.nested_parse(self._parsed_content['text'][0:len_], 0,
 
152
                            intro_para)
 
153
            topsection.append(intro_para)
 
154
 
 
155
        return topsection
 
156
 
 
157
 
 
158
    def _render_rec(self, rec, section, cat, append_sec):
 
159
        para = rec['node'].deepcopy()
 
160
 
 
161
        text = _text_rawsource_from_node(para)
 
162
 
 
163
        to_hash = "%s %s" % (self.version, text[0:100])
 
164
        targetid = "%s-%s" % (self.type_,
 
165
                        md5.md5(to_hash.encode('ascii', 'ignore')
 
166
                            ).hexdigest())
 
167
        targetnode = nodes.target('', '', ids=[targetid])
 
168
        para.insert(0, targetnode)
 
169
        permalink = nodes.reference('', '',
 
170
                        nodes.Text("(link)", "(link)"),
 
171
                        refid=targetid,
 
172
                        classes=['changeset-link']
 
173
                    )
 
174
        para.append(permalink)
 
175
 
 
176
        insert_ticket = nodes.paragraph('')
 
177
        para.append(insert_ticket)
 
178
 
 
179
        i = 0
 
180
        for collection, render, prefix in (
 
181
                (rec['tickets'], self.env.config.changelog_render_ticket, "#%s"),
 
182
                (rec['pullreq'], self.env.config.changelog_render_pullreq,
 
183
                                            "pull request %s"),
 
184
                (rec['changeset'], self.env.config.changelog_render_changeset, "r%s"),
 
185
            ):
 
186
            for refname in collection:
 
187
                if i > 0:
 
188
                    insert_ticket.append(nodes.Text(", ", ", "))
 
189
                else:
 
190
                    insert_ticket.append(nodes.Text(" ", " "))
 
191
                i += 1
 
192
                if render is not None:
 
193
                    refuri = render % refname
 
194
                    node = nodes.reference('', '',
 
195
                            nodes.Text(prefix % refname, prefix % refname),
 
196
                            refuri=refuri
 
197
                        )
 
198
                else:
 
199
                    node = nodes.Text(prefix % refname, prefix % refname)
 
200
                insert_ticket.append(node)
 
201
 
 
202
        if rec['tags']:
 
203
            tag_node = nodes.strong('',
 
204
                        " ".join("[%s]" % t for t
 
205
                            in
 
206
                                [t1 for t1 in [section, cat]
 
207
                                    if t1 in rec['tags']] +
 
208
 
 
209
                                list(rec['tags'].difference([section, cat]))
 
210
                        ) + " "
 
211
                    )
 
212
            para.children[0].insert(0, tag_node)
 
213
 
 
214
        append_sec.append(
 
215
            nodes.list_item('',
 
216
                nodes.target('', '', ids=[rec['id']]),
 
217
                para
 
218
            )
 
219
        )
 
220
 
 
221
 
 
222
class ChangeDirective(EnvDirective, Directive):
 
223
    has_content = True
 
224
 
 
225
    type_ = "change"
 
226
    parent_cls = ChangeLogDirective
 
227
 
 
228
    def run(self):
 
229
        content = _parse_content(self.content)
 
230
        p = nodes.paragraph('', '',)
 
231
        sorted_tags = _comma_list(content.get('tags', ''))
 
232
        rec = {
 
233
            'tags': set(sorted_tags).difference(['']),
 
234
            'tickets': set(_comma_list(content.get('tickets', ''))).difference(['']),
 
235
            'pullreq': set(_comma_list(content.get('pullreq', ''))).difference(['']),
 
236
            'changeset': set(_comma_list(content.get('changeset', ''))).difference(['']),
 
237
            'node': p,
 
238
            'type': self.type_,
 
239
            "title": content.get("title", None),
 
240
            'sorted_tags': sorted_tags
 
241
        }
 
242
 
 
243
        if "declarative" in rec['tags']:
 
244
            rec['tags'].add("orm")
 
245
 
 
246
        self.state.nested_parse(content['text'], 0, p)
 
247
        self.parent_cls.changes(self.env).append(rec)
 
248
 
 
249
        return []
 
250
 
 
251
def _text_rawsource_from_node(node):
 
252
    src = []
 
253
    stack = [node]
 
254
    while stack:
 
255
        n = stack.pop(0)
 
256
        if isinstance(n, nodes.Text):
 
257
            src.append(n.rawsource)
 
258
        stack.extend(n.children)
 
259
    return "".join(src)
 
260
 
 
261
def _rst2sphinx(text):
 
262
    return StringList(
 
263
        [line.strip() for line in textwrap.dedent(text).split("\n")]
 
264
    )
 
265
 
 
266
 
 
267
def make_ticket_link(name, rawtext, text, lineno, inliner,
 
268
                      options={}, content=[]):
 
269
    env = inliner.document.settings.env
 
270
    render_ticket = env.config.changelog_render_ticket or "%s"
 
271
    prefix = "#%s"
 
272
    if render_ticket:
 
273
        ref = render_ticket % text
 
274
        node = nodes.reference(rawtext, prefix % text, refuri=ref, **options)
 
275
    else:
 
276
        node = nodes.Text(prefix % text, prefix % text)
 
277
    return [node], []
 
278
 
 
279
def setup(app):
 
280
    app.add_directive('changelog', ChangeLogDirective)
 
281
    app.add_directive('change', ChangeDirective)
 
282
    app.add_config_value("changelog_sections", [], 'env')
 
283
    app.add_config_value("changelog_inner_tag_sort", [], 'env')
 
284
    app.add_config_value("changelog_render_ticket",
 
285
            None,
 
286
            'env'
 
287
        )
 
288
    app.add_config_value("changelog_render_pullreq",
 
289
            None,
 
290
            'env'
 
291
        )
 
292
    app.add_config_value("changelog_render_changeset",
 
293
            None,
 
294
            'env'
 
295
        )
 
296
    app.add_role('ticket', make_ticket_link)