Completed
Push — master ( c67fe0...f531d0 )
by Christophe
37s
created

meta_listing()   F

Complexity

Conditions 12

Size

Total Lines 25

Duplication

Lines 24
Ratio 96 %

Importance

Changes 0
Metric Value
cc 12
c 0
b 0
f 0
dl 24
loc 25
rs 2.7855

How to fix   Complexity   

Complexity

Complex classes like meta_listing() 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
#!/usr/bin/env python
2
3
"""
4
Pandoc filter to number all kinds of things.
5
"""
6
7
from functools import partial
8
import re
9
import unicodedata
10
import copy
11
12
from panflute import BlockQuote, BulletList, Citation, Cite, CodeBlock, Definition, DefinitionItem, DefinitionList, Div, Emph, Header, HorizontalRule, Image, LineBlock, LineBreak, LineItem, Link, ListItem, Note, Para, Plain, RawBlock, RawInline, SoftBreak, Space, Span, Str, Strong, Table, TableCell, TableRow, MetaBool, MetaInlines, MetaList, MetaMap, MetaString, run_filters, stringify, convert_text, debug
13
14
15
class Numbered(object):
16
    __slots__ = [
17
        '_elem',
18
        '_doc',
19
        '_match',
20
        '_tag',
21
        '_entry',
22
        '_link',
23
        '_caption',
24
        '_title',
25
        '_description',
26
        '_category',
27
        '_basic_category',
28
        '_first_section_level',
29
        '_last_section_level',
30
        '_leading',
31
        '_number',
32
        '_global_number',
33
        '_section_number',
34
        '_local_number',
35
    ]
36
37
    @property
38
    def tag(self):
39
        return self._tag
40
41
    @property
42
    def entry(self):
43
        return self._entry
44
45
    @property
46
    def link(self):
47
        return self._link
48
49
    @property
50
    def title(self):
51
        return self._title
52
53
    @property
54
    def description(self):
55
        return self._description
56
57
    @property
58
    def global_number(self):
59
        return self._global_number
60
61
    @property
62
    def section_number(self):
63
        return self._section_number
64
65
    @property
66
    def local_number(self):
67
        return self._local_number
68
69
    @property
70
    def category(self):
71
        return self._category
72
73
    @property
74
    def caption(self):
75
        return self._caption
76
77
    number_regex = '#((?P<prefix>[a-zA-Z][\\w.-]*):)?(?P<name>[a-zA-Z][\\w:.-]*)?'
78
    _regex = '(?P<header>(?P<hidden>(-\\.)*)(\\+\\.)*)'
79
    header_regex = '^' + _regex + '$'
80
    marker_regex = '^' + _regex + number_regex + '$'
81
    double_sharp_regex = '^' + _regex + '#' + number_regex + '$'
82
83
    @staticmethod
84
    def _remove_accents(string):
85
        nfkd_form = unicodedata.normalize('NFKD', string)
86
        return u''.join([c for c in nfkd_form if not unicodedata.combining(c)])
87
88
    @staticmethod
89
    def _identifier(string):
90
        # replace invalid characters by dash
91
        string = re.sub('[^0-9a-zA-Z_-]+', '-', Numbered._remove_accents(string.lower()))
92
93
        # Remove leading digits
94
        string = re.sub('^[^a-zA-Z]+', '', string)
95
96
        return string
97
98
    def __init__(self, elem, doc):
99
        self._elem = elem
100
        self._doc = doc
101
        self._entry = Span(classes=['pandoc-numbering-entry'])
102
        self._link = Span(classes=['pandoc-numbering-link'])
103
        self._tag = None
104
        if self._get_content() and isinstance(self._get_content()[-1], Str):
105
            self._match = re.match(Numbered.marker_regex, self._get_content()[-1].text)
106
            if self._match:
107
                self._replace_marker()
108
            elif re.match(Numbered.double_sharp_regex, self._get_content()[-1].text):
109
                self._replace_double_sharp()
110
111
    def _set_content(self, content):
112
        if isinstance(self._elem, Para):
113
            self._elem.content = content
114
        elif isinstance(self._elem, DefinitionItem):
115
            self._elem.term = content
116
117
    def _get_content(self):
118
        if isinstance(self._elem, Para):
119
            return self._elem.content
120
        elif isinstance(self._elem, DefinitionItem):
121
            return self._elem.term
122
        return None
123
124
    def _replace_double_sharp(self):
125
        self._get_content()[-1].text = self._get_content()[-1].text.replace('##', '#', 1)
126
127
    def _replace_marker(self):
128
        self._compute_title()
129
        self._compute_description()
130
        self._compute_basic_category()
131
        self._compute_levels()
132
        self._compute_section_number()
133
        self._compute_leading()
134
        self._compute_category()
135
        self._compute_number()
136
        self._compute_tag()
137
        self._compute_local_number()
138
        self._compute_global_number()
139
        self._compute_data()
140
141
    def _compute_title(self):
142
        self._title = []
143
        if isinstance(self._get_content()[-3], Str) and self._get_content()[-3].text[-1:] == ')':
144
            for (i, item) in enumerate(self._get_content()):
145
                if isinstance(item, Str) and item.text[0] == '(':
146
                    self._title = self._get_content()[i:-2]
147
                    # Detach from original parent
148
                    self._title.parent = None
149
                    self._title[0].text = self._title[0].text[1:]
150
                    self._title[-1].text = self._title[-1].text[:-1]
151
                    del self._get_content()[i - 1:-2]
152
                    break
153
        self._title = list(self._title)
154
155
    def _compute_description(self):
156
        self._description = self._get_content()[:-2]
157
        # Detach from original parent
158
        self._description.parent = None
159
        self._description = list(self._description)
160
161
    def _compute_basic_category(self):
162
        if self._match.group('prefix') is None:
163
            self._basic_category = Numbered._identifier(''.join(map(stringify, self._description)))
164
        else:
165
            self._basic_category = self._match.group('prefix')
166
        if self._basic_category not in self._doc.defined:
167
            define(self._basic_category, self._doc)
168
169
    def _compute_levels(self):
170
        # Compute the first and last section level values
171
        self._first_section_level = len(self._match.group('hidden')) // 2
172
        self._last_section_level = len(self._match.group('header')) // 2
173
174
        # Get the default first and last section level
175
        if self._first_section_level == 0 and self._last_section_level == 0:
176
            self._first_section_level = self._doc.defined[self._basic_category]['first-section-level']
177
            self._last_section_level = self._doc.defined[self._basic_category]['last-section-level']
178
179
    def _compute_section_number(self):
180
        self._section_number = '.'.join(map(str, self._doc.headers[:self._last_section_level]))
181
182
    def _compute_leading(self):
183
        # Compute the leading (composed of the section numbering and a dot)
184
        if self._last_section_level != 0:
185
            self._leading = self._section_number + '.'
186
        else:
187
            self._leading = ''
188
189
    def _compute_category(self):
190
        self._category = self._basic_category + ':' + self._leading
191
192
        # Is it a new category?
193
        if self._category not in self._doc.count:
194
            self._doc.count[self._category] = 0
195
196
        self._doc.count[self._category] = self._doc.count[self._category] + 1
197
198
    def _compute_number(self):
199
        self._number = str(self._doc.count[self._category])
200
201
    def _compute_tag(self):
202
        # Determine the final tag
203
        if self._match.group('name') is None:
204
            self._tag = self._category + self._number
205
        else:
206
            self._tag = self._basic_category + ':' + self._match.group('name')
207
208
        # Compute collections
209
        if self._basic_category not in self._doc.collections:
210
            self._doc.collections[self._basic_category] = []
211
212
        self._doc.collections[self._basic_category].append(self._tag)
213
214
    def _compute_local_number(self):
215
        # Replace the '-.-.+.+...#' by the category count (omitting the hidden part)
216
        self._local_number = '.'.join(map(str, self._doc.headers[self._first_section_level:self._last_section_level] + [self._number]))
217
218
    def _compute_global_number(self):
219
        # Compute the global number
220
        if self._section_number:
221
            self._global_number = self._section_number + '.' + self._number
222
        else:
223
            self._global_number = self._number
224
225
    def _compute_data(self):
226
        classes = self._doc.defined[self._basic_category]['classes']
227
        self._set_content([Span(
228
            classes=['pandoc-numbering-text'] + classes,
229
            identifier=self._tag
230
        )])
231
        self._link.classes = self._link.classes + classes
232
        self._entry.classes = self._entry.classes + classes
233
234
        # Prepare the final data
235
        if self._title:
236
            self._get_content()[0].content = copy.deepcopy(self._doc.defined[self._basic_category]['format-text-title'])
237
            self._link.content = copy.deepcopy(self._doc.defined[self._basic_category]['format-link-title'])
238
            self._entry.content = copy.deepcopy(self._doc.defined[self._basic_category]['format-entry-title'])
239
            self._caption = self._doc.defined[self._basic_category]['format-caption-title']
240
        else:
241
            self._get_content()[0].content = copy.deepcopy(self._doc.defined[self._basic_category]['format-text-classic'])
242
            self._link.content = copy.deepcopy(self._doc.defined[self._basic_category]['format-link-classic'])
243
            self._entry.content = copy.deepcopy(self._doc.defined[self._basic_category]['format-entry-classic'])
244
            self._caption = self._doc.defined[self._basic_category]['format-caption-classic']
245
246
        # Compute caption (report replacing %c at the end since it is not known for the moment)
247
        title = stringify(Span(*self._title))
248
        description = stringify(Span(*self._description))
249
        self._caption = self._caption.replace('%t', title.lower())
250
        self._caption = self._caption.replace('%T', title)
251
        self._caption = self._caption.replace('%d', description.lower())
252
        self._caption = self._caption.replace('%D', description)
253
        self._caption = self._caption.replace('%s', self._section_number)
254
        self._caption = self._caption.replace('%g', self._global_number)
255
        self._caption = self._caption.replace('%n', self._local_number)
256
        self._caption = self._caption.replace('#', self._local_number)
257
        if self._doc.format == 'latex':
258
            self._caption = self._caption.replace('%p', '\\pageref{' + self._tag + '}')
259
260
        # Compute content
261
        replace_description(self._elem, self._description)
262
        replace_title(self._elem, self._title)
263
        replace_global_number(self._elem, self._global_number)
264
        replace_section_number(self._elem, self._section_number)
265
        replace_local_number(self._elem, self._local_number)
266
267
        # Compute link
268
        replace_description(self._link, self._description)
269
        replace_title(self._link, self._title)
270
        replace_global_number(self._link, self._global_number)
271
        replace_section_number(self._link, self._section_number)
272
        replace_local_number(self._link, self._local_number)
273
        if self._doc.format == 'latex':
274
            replace_page_number(self._link, self._tag)
275
276
        # Compute entry
277
        replace_description(self._entry, self._description)
278
        replace_title(self._entry, self._title)
279
        replace_global_number(self._entry, self._global_number)
280
        replace_section_number(self._entry, self._section_number)
281
        replace_local_number(self._entry, self._local_number)
282
283
        # Finalize the content
284
        if self._doc.format == 'latex':
285
            self._get_content()[0].content.insert(0, RawInline('\\label{' + self._tag + '}', 'tex'))
286
287
            latex_category = re.sub('[^a-z]+', '', self._basic_category)
288
            latex = '\\phantomsection\\addcontentsline{' + \
289
                latex_category + \
290
                '}{' + \
291
                latex_category + \
292
                '}{\\protect\\numberline {' + \
293
                self._leading + \
294
                self._number + \
295
                '}{\\ignorespaces ' + \
296
                to_latex(self._entry) + \
297
                '}}'
298
            self._get_content().insert(0, RawInline(latex, 'tex'))
299
300
301
def replace_description(where, description):
302
    where.walk(partial(replacing, search='%D', replace=copy.deepcopy(description)))
303
    where.walk(partial(replacing, search='%d', replace=list(item.walk(lowering) for item in copy.deepcopy(description))))
304
305
306
def replace_title(where, title):
307
    where.walk(partial(replacing, search='%T', replace=copy.deepcopy(title)))
308
    where.walk(partial(replacing, search='%t', replace=list(item.walk(lowering) for item in copy.deepcopy(title))))
309
310
311
def replace_section_number(where, section_number):
312
    where.walk(partial(replacing, search='%s', replace=[Str(section_number)]))
313
314
315
def replace_global_number(where, global_number):
316
    where.walk(partial(replacing, search='%g', replace=[Str(global_number)]))
317
318
319
def replace_local_number(where, local_number):
320
    where.walk(partial(replacing, search='%n', replace=[Str(local_number)]))
321
    where.walk(partial(replacing, search='#', replace=[Str(local_number)]))
322
323
324
def replace_page_number(where, tag):
325
    where.walk(partial(replacing, search='%p', replace=[RawInline('\\pageref{' + tag + '}', 'tex')]))
326
327
328
def replace_count(where, count):
329
    where.walk(partial(replacing, search='%c', replace=[Str(count)]))
330
331
332
def remove_useless_latex(elem, _):
333
    if isinstance(elem, (BlockQuote, BulletList, Citation, Cite, CodeBlock, Definition, DefinitionItem, DefinitionList, Div, Header, HorizontalRule, Image, LineBlock, LineBreak, LineItem, ListItem, Note, Para, RawBlock, RawInline, SoftBreak, Table, TableCell, TableRow)):
334
        return []
335
    return None
336
337
338
def to_latex(elem):
339
    return convert_text(run_filters([remove_useless_latex], doc=Plain(elem)), input_format='panflute', output_format='latex', extra_args=['--no-highlight'])
340
341
342
def define(category, doc):
343
    doc.defined[category] = {
344
        'first-section-level':    0,
345
        'last-section-level':     0,
346
        'format-text-classic':    [Strong(Str('%D'), Space(), Str('%n'))],
347
        'format-text-title':      [Strong(Str('%D'), Space(), Str('%n')), Space(), Emph(Str('(%T)'))],
348
        'format-link-classic':    [Str('%D'), Space(), Str('%n')],
349
        'format-link-title':      [Str('%D'), Space(), Str('%n'), Space(), Str('(%T)')],
350
        'format-caption-classic': '%D %n',
351
        'format-caption-title':   '%D %n (%T)',
352
        'format-entry-title':     [Str('%T')],
353
        'classes':                [category],
354
        'cite-shortcut':          True,
355
        'listing-title':          None,
356
        'listing-unnumbered':     True,
357
        'listing-unlisted':       True,
358
        'listing-identifier':     True,
359
        'entry-tab':              1.5,
360
        'entry-space':            2.3,
361
    }
362
    if doc.format == 'latex':
363
        doc.defined[category]['format-entry-classic'] = [Str('%D')]
364
        doc.defined[category]['entry-tab'] = 1.5
365
        doc.defined[category]['entry-space'] = 2.3
366
    else:
367
        doc.defined[category]['format-entry-classic'] = [Str('%D'), Space(), Str('%g')]
368
369
370
def lowering(elem, _):
371
    if isinstance(elem, Str):
372
        elem.text = elem.text.lower()
373
374
375
def replacing(elem, _, search=None, replace=None):
376
    if isinstance(elem, Str):
377
        search_splitted = elem.text.split(search)
378
        if len(search_splitted) > 1:
379
380
            text = []
381
382
            if search_splitted[0] != '':
383
                text.append(Str(search_splitted[0]))
384
385
            for string in search_splitted[1:]:
386
                text.extend(replace)
387
                if string != '':
388
                    text.append(Str(string))
389
390
            return text
391
392
    return [elem]
393
394
395
def numbering(elem, doc):
396
    if isinstance(elem, Header):
397
        update_header_numbers(elem, doc)
398
    elif isinstance(elem, (Para, DefinitionItem)):
399
        numbered = Numbered(elem, doc)
400
        if numbered.tag is not None:
401
            doc.information[numbered.tag] = numbered
402
403
404
def referencing(elem, doc):
405
    if isinstance(elem, Link):
406
        return referencing_link(elem, doc)
407
    elif isinstance(elem, Cite):
408
        return referencing_cite(elem, doc)
409
    elif isinstance(elem, Span) and elem.identifier in doc.information:
410
        replace_count(elem, str(doc.count[doc.information[elem.identifier].category]))
411
    return None
412
413
414
def referencing_link(elem, doc):
415
    match = re.match('^#(?P<tag>([a-zA-Z][\\w:.-]*))$', elem.url)
416
    if match:
417
        tag = match.group('tag')
418
        if tag in doc.information:
419
            replace_title(elem, doc.information[tag].title)
420
            replace_description(elem, doc.information[tag].description)
421
            replace_global_number(elem, doc.information[tag].global_number)
422
            replace_section_number(elem, doc.information[tag].section_number)
423
            replace_local_number(elem, doc.information[tag].local_number)
424
            replace_count(elem, str(doc.count[doc.information[tag].category]))
425
            if doc.format == 'latex':
426
                replace_page_number(elem, tag)
427
428
            title = stringify(Span(*doc.information[tag].title))
429
            description = stringify(Span(*doc.information[tag].description))
430
            elem.title = elem.title.replace('%t', title.lower())
431
            elem.title = elem.title.replace('%T', title)
432
            elem.title = elem.title.replace('%d', description.lower())
433
            elem.title = elem.title.replace('%D', description)
434
            elem.title = elem.title.replace('%s', doc.information[tag].section_number)
435
            elem.title = elem.title.replace('%g', doc.information[tag].global_number)
436
            elem.title = elem.title.replace('%n', doc.information[tag].local_number)
437
            elem.title = elem.title.replace('#', doc.information[tag].local_number)
438
            elem.title = elem.title.replace('%c', str(doc.count[doc.information[tag].category]))
439
            if doc.format == 'latex':
440
                elem.title = elem.title.replace('%p', '\\pageref{' + tag + '}')
441
442
443
def referencing_cite(elem, doc):
444
    if len(elem.content) == 1 and isinstance(elem.content[0], Str):
445
        match = re.match('^(@(?P<tag>(?P<category>[a-zA-Z][\\w.-]*):(([a-zA-Z][\\w.-]*)|(\\d*(\\.\\d*)*))))$', elem.content[0].text)
446
        if match:
447
            category = match.group('category')
448
            if category in doc.defined and doc.defined[category]['cite-shortcut']:
449
                # Deal with @prefix:name shortcut
450
                tag = match.group('tag')
451
                if tag in doc.information:
452
                    ret = Link(
453
                        doc.information[tag].link,
454
                        url='#' + tag,
455
                        title=doc.information[tag].caption.replace('%c', str(doc.count[doc.information[tag].category]))
456
                    )
457
                    replace_count(ret, str(doc.count[doc.information[tag].category]))
458
                    return ret
459
    return None
460
461
462
def update_header_numbers(elem, doc):
463
    if 'unnumbered' not in elem.classes:
464
        doc.headers[elem.level - 1] = doc.headers[elem.level - 1] + 1
465
        for index in range(elem.level, 6):
466
            doc.headers[index] = 0
467
468
469
def prepare(doc):
470
    doc.headers = [0, 0, 0, 0, 0, 0]
471
    doc.information = {}
472
    doc.defined = {}
473
474
    if 'pandoc-numbering' in doc.metadata.content and isinstance(doc.metadata.content['pandoc-numbering'], MetaMap):
475
        for category, definition in doc.metadata.content['pandoc-numbering'].content.items():
476
            if isinstance(definition, MetaMap):
477
                add_definition(category, definition, doc)
478
479
    doc.count = {}
480
    doc.collections = {}
481
482
483
def add_definition(category, definition, doc):
484
    # Create the category with options by default
485
    define(category, doc)
486
487
    # Detect general options
488
    if 'general' in definition:
489
        meta_cite(category, definition['general'], doc.defined)
490
        meta_listing(category, definition['general'], doc.defined)
491
        meta_levels(category, definition['general'], doc.defined)
492
        meta_classes(category, definition['general'], doc.defined)
493
494
    # Detect LaTeX options
495
    if doc.format == 'latex':
496
        if 'latex' in definition:
497
            meta_format_text(category, definition['latex'], doc.defined)
498
            meta_format_link(category, definition['latex'], doc.defined)
499
            meta_format_caption(category, definition['latex'], doc.defined)
500
            meta_format_entry(category, definition['latex'], doc.defined)
501
            meta_entry_tab(category, definition['latex'], doc.defined)
502
            meta_entry_space(category, definition['latex'], doc.defined)
503
    # Detect standard options
504
    else:
505
        if 'standard' in definition:
506
            meta_format_text(category, definition['standard'], doc.defined)
507
            meta_format_link(category, definition['standard'], doc.defined)
508
            meta_format_caption(category, definition['standard'], doc.defined)
509
            meta_format_entry(category, definition['standard'], doc.defined)
510
511
512
def meta_cite(category, definition, defined):
513 View Code Duplication
    if 'cite-shortcut' in definition:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
514
        if isinstance(definition['cite-shortcut'], MetaBool):
515
            defined[category]['cite-shortcut'] = definition['cite-shortcut'].boolean
516
        else:
517
            debug('[WARNING] pandoc-numbering: cite-shortcut is not correct for category ' + category)
518
519
520
# pylint:disable=too-many-branches
521
def meta_listing(category, definition, defined):
522
    if 'listing-title' in definition:
523
        if isinstance(definition['listing-title'], MetaInlines):
524
            defined[category]['listing-title'] = definition['listing-title'].content
525
            # Detach from original parent
526
            defined[category]['listing-title'].parent = None
527
        else:
528
            debug('[WARNING] pandoc-numbering: listing-title is not correct for category ' + category)
529
    if 'listing-unnumbered' in definition:
530 View Code Duplication
        if isinstance(definition['listing-unnumbered'], MetaBool):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
531
            defined[category]['listing-unnumbered'] = definition['listing-unnumbered'].boolean
532
        else:
533
            debug('[WARNING] pandoc-numbering: listing-unnumbered is not correct for category ' + category)
534
    if 'listing-unlisted' in definition:
535
        if isinstance(definition['listing-unlisted'], MetaBool):
536
            defined[category]['listing-unlisted'] = definition['listing-unlisted'].boolean
537
        else:
538
            debug('[WARNING] pandoc-numbering: listing-unlisted is not correct for category ' + category)
539
    if 'listing-identifier' in definition:
540
        if isinstance(definition['listing-identifier'], MetaBool):
541
            defined[category]['listing-identifier'] = definition['listing-identifier'].boolean
542
        elif isinstance(definition['listing-identifier'], MetaInlines) and len(definition['listing-identifier'].content) == 1 and isinstance(definition['listing-identifier'].content[0], Str):
543
            defined[category]['listing-identifier'] = definition['listing-identifier'].content[0].text
544
        else:
545
            debug('[WARNING] pandoc-numbering: listing-identifier is not correct for category ' + category)
546
547
548
def meta_format_text(category, definition, defined):
549
    if 'format-text-classic' in definition:
550
        if isinstance(definition['format-text-classic'], MetaInlines):
551
            defined[category]['format-text-classic'] = definition['format-text-classic'].content
552
            # Detach from original parent
553
            defined[category]['format-text-classic'].parent = None
554
        else:
555
            debug('[WARNING] pandoc-numbering: format-text-classic is not correct for category ' + category)
556
557
    if 'format-text-title' in definition:
558
        if isinstance(definition['format-text-title'], MetaInlines):
559
            defined[category]['format-text-title'] = definition['format-text-title'].content
560 View Code Duplication
            # Detach from original parent
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
561
            defined[category]['format-text-title'].parent = None
562
        else:
563
            debug('[WARNING] pandoc-numbering: format-text-title is not correct for category ' + category)
564
565
566
def meta_format_link(category, definition, defined):
567
    if 'format-link-classic' in definition:
568
        if isinstance(definition['format-link-classic'], MetaInlines):
569
            defined[category]['format-link-classic'] = definition['format-link-classic'].content
570
            # Detach from original parent
571
            defined[category]['format-link-classic'].parent = None
572
        else:
573
            debug('[WARNING] pandoc-numbering: format-link-classic is not correct for category ' + category)
574
575
    if 'format-link-title' in definition:
576
        if isinstance(definition['format-link-title'], MetaInlines):
577
            defined[category]['format-link-title'] = definition['format-link-title'].content
578
            # Detach from original parent
579
            defined[category]['format-link-title'].parent = None
580
        else:
581
            debug('[WARNING] pandoc-numbering: format-link-title is not correct for category ' + category)
582
583
584
def meta_format_caption(category, definition, defined):
585
    if 'format-caption-classic' in definition:
586
        if isinstance(definition['format-caption-classic'], MetaInlines):
587
            defined[category]['format-caption-classic'] = stringify(definition['format-caption-classic'])
588
        else:
589
            debug('[WARNING] pandoc-numbering: format-caption-classic is not correct for category ' + category)
590
591
    if 'format-caption-title' in definition:
592
        if isinstance(definition['format-caption-title'], MetaInlines):
593
            defined[category]['format-caption-title'] = stringify(definition['format-caption-title'])
594
        else:
595
            debug('[WARNING] pandoc-numbering: format-caption-title is not correct for category ' + category)
596
597
598
def meta_format_entry(category, definition, defined):
599
    if 'format-entry-classic' in definition:
600
        if isinstance(definition['format-entry-classic'], MetaInlines):
601
            defined[category]['format-entry-classic'] = definition['format-entry-classic'].content
602
            # Detach from original parent
603
            defined[category]['format-entry-classic'].parent = None
604
        else:
605
            debug('[WARNING] pandoc-numbering: format-entry-classic is not correct for category ' + category)
606
607
    if 'format-entry-title' in definition:
608
        if isinstance(definition['format-entry-title'], MetaInlines):
609
            defined[category]['format-entry-title'] = definition['format-entry-title'].content
610
            # Detach from original parent
611
            defined[category]['format-entry-title'].parent = None
612
        else:
613
            debug('[WARNING] pandoc-numbering: format-entry-title is not correct for category ' + category)
614
615
616
def meta_entry_tab(category, definition, defined):
617
    if 'entry-tab' in definition and isinstance(definition['entry-tab'], MetaString):
618
        # Get the tab
619
        try:
620
            tab = float(definition['entry-tab'].text)
621
            if tab > 0:
622
                defined[category]['entry-tab'] = tab
623
            else:
624
                debug('[WARNING] pandoc-numbering: entry-tab must be positive for category ' + category)
625
        except ValueError:
626
            debug('[WARNING] pandoc-numbering: entry-tab is not correct for category ' + category)
627
628
629
def meta_entry_space(category, definition, defined):
630
    if 'entry-space' in definition and isinstance(definition['entry-space'], MetaString):
631
        # Get the space
632
        try:
633
            space = float(definition['entry-space'].text)
634
            if space > 0:
635
                defined[category]['entry-space'] = space
636
            else:
637
                debug('[WARNING] pandoc-numbering: entry-space must be positive for category ' + category)
638
        except ValueError:
639
            debug('[WARNING] pandoc-numbering: entry-space is not correct for category ' + category)
640
641
642
def meta_levels(category, definition, defined):
643
    if 'sectioning-levels' in definition and isinstance(definition['sectioning-levels'], MetaInlines) and len(definition['sectioning-levels'].content) == 1:
644
        match = re.match(Numbered.header_regex, definition['sectioning-levels'].content[0].text)
645
        if match:
646
            # Compute the first and last levels section
647
            defined[category]['first-section-level'] = len(match.group('hidden')) // 2
648
            defined[category]['last-section-level'] = len(match.group('header')) // 2
649
    if 'first-section-level' in definition and isinstance(definition['first-section-level'], MetaString):
650
        # Get the level
651
        try:
652
            level = int(definition['first-section-level'].text) - 1
653
            if level >= 0 and level <= 6:
654
                defined[category]['first-section-level'] = level
655
            else:
656
                debug('[WARNING] pandoc-numbering: first-section-level must be positive or zero for category ' + category)
657
        except ValueError:
658
            debug('[WARNING] pandoc-numbering: first-section-level is not correct for category ' + category)
659
    if 'last-section-level' in definition and isinstance(definition['last-section-level'], MetaString):
660
        # Get the level
661
        try:
662
            level = int(definition['last-section-level'].text)
663
            if level >= 0 and level <= 6:
664
                defined[category]['last-section-level'] = level
665
            else:
666
                debug('[WARNING] pandoc-numbering: last-section-level must be positive or zero for category ' + category)
667
        except ValueError:
668
            debug('[WARNING] pandoc-numbering: last-section-level is not correct for category ' + category)
669
670
671
def meta_classes(category, definition, defined):
672
    if 'classes' in definition and isinstance(definition['classes'], MetaList):
673
        classes = []
674
        for elt in definition['classes'].content:
675
            classes.append(stringify(elt))
676
        defined[category]['classes'] = classes
677
678
679
def finalize(doc):
680
    # Loop on all listings definition
681
    i = 0
682
    for category, definition in doc.defined.items():
683
        if definition['listing-title'] is not None:
684
            classes = ['pandoc-numbering-listing'] + definition['classes']
685
            if definition['listing-unnumbered']:
686
                classes.append('unnumbered')
687
            if definition['listing-unlisted']:
688
                classes.append('unlisted')
689
            if definition['listing-identifier'] is False:
690
                header = Header(*definition['listing-title'], level=1, classes=classes)
691
            elif definition['listing-identifier'] is True:
692
                header = Header(*definition['listing-title'], level=1, classes=classes)
693
                header = convert_text(
694
                    convert_text(header, input_format='panflute', output_format='markdown'),
695
                    output_format='panflute'
696
                )[0]
697
            else:
698
                header = Header(
699
                    *definition['listing-title'],
700
                    level=1,
701
                    classes=classes,
702
                    identifier=definition['listing-identifier']
703
                )
704
            doc.content.insert(i, header)
705
            i = i + 1
706
707
            if doc.format == 'latex':
708
                table = table_latex(doc, category, definition)
709
            else:
710
                table = table_other(doc, category, definition)
711
712
            if table:
713
                doc.content.insert(i, table)
714
                i = i + 1
715
716
717
def table_other(doc, category, _):
718
    if category in doc.collections:
719
        # Prepare the list
720
        elements = []
721
        # Loop on the collection
722
        for tag in doc.collections[category]:
723
            # Add an item to the list
724
            elements.append(ListItem(Plain(Link(doc.information[tag].entry, url='#' + tag))))
725
        # Return a bullet list
726
        return BulletList(*elements)
727
    return None
728
729
730
def table_latex(doc, category, definition):
731
    latex_category = re.sub('[^a-z]+', '', category)
732
    latex = [
733
        link_color(doc),
734
        '\\makeatletter',
735
        '\\newcommand*\\l@' + latex_category + '{\\@dottedtocline{1}{' + str(definition['entry-tab']) + 'em}{' + str(definition['entry-space']) + 'em}}',
736
        '\\@starttoc{' + latex_category + '}',
737
        '\\makeatother'
738
    ]
739
    # Return a RawBlock
740
    return RawBlock(''.join(latex), 'tex')
741
742
743
def link_color(doc):
744
    # Get the link color
745
    metadata = doc.get_metadata()
746
    if 'toccolor' in metadata:
747
        return '\\hypersetup{linkcolor=' + str(metadata['toccolor']) + '}'
748
    return '\\hypersetup{linkcolor=black}'
749
750
751
def main(doc=None):
752
    return run_filters([numbering, referencing], prepare=prepare, doc=doc, finalize=finalize)
753
754
755
if __name__ == '__main__':
756
    main()
757