• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zopefoundation / z3c.rml / 16098868126

14 Apr 2025 06:50AM UTC coverage: 87.385%. Remained the same
16098868126

push

github

icemac
Back to development: 5.1

561 of 792 branches covered (70.83%)

Branch coverage included in aggregate %.

3990 of 4416 relevant lines covered (90.35%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

17.33
/src/z3c/rml/reference.py
1
##############################################################################
2
#
3
# Copyright (c) 2007 Zope Foundation and Contributors.
4
# All Rights Reserved.
5
#
6
# This software is subject to the provisions of the Zope Public License,
7
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11
# FOR A PARTICULAR PURPOSE.
12
#
13
##############################################################################
14
"""RML Reference Generator
15
"""
16
import os
1✔
17
import re
1✔
18
from xml.sax import saxutils
1✔
19

20
import pygments.token
1✔
21
import zope.schema
1✔
22
import zope.schema.interfaces
1✔
23
from lxml import etree
1✔
24
from pygments.lexers import XmlLexer
1✔
25

26
from z3c.rml import __version__
1✔
27
from z3c.rml import attr
1✔
28
from z3c.rml import document
1✔
29
from z3c.rml import interfaces
1✔
30
from z3c.rml import pagetemplate
1✔
31

32

33
INPUT_URL = ('https://github.com/zopefoundation/z3c.rml/blob/master/src/z3c/'
1✔
34
             'rml/tests/input/%s')
35
EXPECTED_URL = (
1✔
36
    'https://github.com/zopefoundation/z3c.rml/blob/master/src/z3c/'
37
    'rml/tests/expected/%s?raw=true')
38

39
EXAMPLES_DIRECTORY = os.path.join(os.path.dirname(__file__), 'tests', 'input')
1✔
40
IGNORE_ATTRIBUTES = ('RMLAttribute', 'BaseChoice')
1✔
41
CONTENT_FIELD_TYPES = (
1✔
42
    attr.TextNode, attr.TextNodeSequence, attr.TextNodeGrid,
43
    attr.RawXMLContent, attr.XMLContent)
44
STYLES_FORMATTING = {
1✔
45
    pygments.token.Name.Tag: ('<font textColor="red">', '</font>'),
46
    pygments.token.Literal.String: ('<font textColor="blue">', '</font>'),
47
}
48
EXAMPLE_NS = 'http://namespaces.zope.org/rml/doc'
1✔
49
EXAMPLE_ATTR_NAME = '{%s}example' % EXAMPLE_NS
1✔
50

51

52
def dedent(rml):
1✔
53
    spaces = re.findall('\n( *)<', rml)
×
54
    if not spaces:
×
55
        return rml
×
56
    least = min([len(s) for s in spaces if s != ''])
×
57
    return rml.replace('\n' + ' ' * least, '\n')
×
58

59

60
def enforceColumns(rml, columns=80):
1✔
61
    result = []
×
62
    for line in rml.split('\n'):
×
63
        if len(line) <= columns:
×
64
            result.append(line)
×
65
            continue
×
66
        # Determine the indentation for all other lines
67
        lineStart = re.findall('^( *<[a-zA-Z0-9]+ )', line)
×
68
        lineIndent = 0
×
69
        if lineStart:
×
70
            lineIndent = len(lineStart[0])
×
71
        # Create lines having at most the specified number of columns
72
        while len(line) > columns:
×
73
            end = line[:columns].rfind(' ')
×
74
            result.append(line[:end])
×
75
            line = ' ' * lineIndent + line[end + 1:]
×
76
        result.append(line)
×
77

78
    return '\n'.join(result)
×
79

80

81
def highlightRML(rml):
1✔
82
    lexer = XmlLexer()
×
83
    styledRml = ''
×
84
    for ttype, token in lexer.get_tokens(rml):
×
85
        start, end = STYLES_FORMATTING.get(ttype, ('', ''))
×
86
        styledRml += start + saxutils.escape(token) + end
×
87
    return styledRml
×
88

89

90
def removeDocAttributes(elem):
1✔
91
    for name in elem.attrib.keys():
×
92
        if name.startswith('{' + EXAMPLE_NS + '}'):
×
93
            del elem.attrib[name]
×
94
    for child in elem.getchildren():
×
95
        removeDocAttributes(child)
×
96

97

98
def getAttributeTypes():
1✔
99
    types = []
×
100
    candidates = sorted(attr.__dict__.items(), key=lambda e: e[0])
×
101
    for name, candidate in candidates:
×
102
        if not (isinstance(candidate, type) and
×
103
                zope.schema.interfaces.IField.implementedBy(candidate) and
104
                name not in IGNORE_ATTRIBUTES):
105
            continue
×
106
        types.append({
×
107
            'name': name,
108
            'description': candidate.__doc__
109
        })
110
    return types
×
111

112

113
def formatField(field):
1✔
114
    return field.__class__.__name__
×
115

116

117
def formatChoice(field):
1✔
118
    choices = ', '.join([repr(choice) for choice in field.choices.keys()])
×
119
    return f'{field.__class__.__name__} of ({choices})'
×
120

121

122
def formatSequence(field):
1✔
123
    vtFormatter = typeFormatters.get(field.value_type.__class__, formatField)
×
124
    return '{} of {}'.format(
×
125
        field.__class__.__name__, vtFormatter(field.value_type))
126

127

128
def formatGrid(field):
1✔
129
    vtFormatter = typeFormatters.get(field.value_type.__class__, formatField)
×
130
    return '%s with %i cols of %s' % (
×
131
        field.__class__.__name__, field.columns, vtFormatter(field.value_type))
132

133

134
def formatCombination(field):
1✔
135
    vts = [typeFormatters.get(vt.__class__, formatField)(vt)
×
136
           for vt in field.value_types]
137
    return '{} of {}'.format(field.__class__.__name__, ', '.join(vts))
×
138

139

140
typeFormatters = {
1✔
141
    attr.Choice: formatChoice,
142
    attr.Sequence: formatSequence,
143
    attr.Combination: formatCombination,
144
    attr.TextNodeSequence: formatSequence,
145
    attr.TextNodeGrid: formatGrid}
146

147

148
def processSignature(name, signature, queue, examples, directives=None):
1✔
149
    if directives is None:
×
150
        directives = {}
×
151
    # Process this directive
152
    if signature not in directives:
×
153
        info = {'name': name, 'description': signature.getDoc(),
×
154
                'id': str(hash(signature)), 'deprecated': False}
155
        # If directive is deprecated, then add some info
156
        if interfaces.IDeprecatedDirective.providedBy(signature):
×
157
            info['deprecated'] = True
×
158
            info['reason'] = signature.getTaggedValue('deprecatedReason')
×
159
        attrs = []
×
160
        content = None
×
161
        for fname, field in zope.schema.getFieldsInOrder(signature):
×
162
            # Handle the case, where the field describes the content
163
            typeFormatter = typeFormatters.get(field.__class__, formatField)
×
164
            fieldInfo = {
×
165
                'name': fname,
166
                'type': typeFormatter(field),
167
                'title': field.title,
168
                'description': field.description,
169
                'required': field.required,
170
                'deprecated': False,
171
            }
172
            if field.__class__ in CONTENT_FIELD_TYPES:
×
173
                content = fieldInfo
×
174
            else:
175
                attrs.append(fieldInfo)
×
176

177
            # Add a separate entry for the deprecated field
178
            if interfaces.IDeprecated.providedBy(field):
×
179
                deprFieldInfo = fieldInfo.copy()
×
180
                deprFieldInfo['deprecated'] = True
×
181
                deprFieldInfo['name'] = field.deprecatedName
×
182
                deprFieldInfo['reason'] = field.deprecatedReason
×
183
                attrs.append(deprFieldInfo)
×
184

185
        info['attributes'] = attrs
×
186
        info['content'] = content
×
187
        # Examples can be either gotten by interface path or tag name
188
        ifacePath = signature.__module__ + '.' + signature.__name__
×
189
        if ifacePath in examples:
×
190
            info['examples'] = examples[ifacePath]
×
191
        else:
192
            info['examples'] = examples.get(name, None)
×
193

194
        subs = []
×
195
        for occurence in signature.queryTaggedValue('directives', ()):
×
196
            subs.append({
×
197
                'name': occurence.tag,
198
                'occurence': occurence.__class__.__name__,
199
                'deprecated': interfaces.IDeprecatedDirective.providedBy(
200
                    occurence.signature),
201
                'id': str(hash(occurence.signature))
202
            })
203
        info['sub-directives'] = subs
×
204
        directives[signature] = info
×
205

206
        # Process Children
207
        for occurence in signature.queryTaggedValue('directives', ()):
×
208
            queue.append((occurence.tag, occurence.signature))
×
209

210

211
def extractExamples(directory):
1✔
212
    examples = {}
×
213
    for filename in os.listdir(directory):
×
214
        if not filename.endswith('.rml'):
×
215
            continue
×
216
        rmlFile = open(os.path.join(directory, filename), 'rb')
×
217
        root = etree.parse(rmlFile).getroot()
×
218
        elements = root.xpath('//@doc:example/parent::*',
×
219
                              namespaces={'doc': EXAMPLE_NS})
220
        # Phase 1: Collect all elements
221
        for elem in elements:
×
222
            demoTag = elem.get(EXAMPLE_ATTR_NAME) or elem.tag
×
223
            elemExamples = examples.setdefault(demoTag, [])
×
224
            elemExamples.append({
×
225
                'filename': filename,
226
                'line': elem.sourceline,
227
                'element': elem,
228
                'rmlurl': INPUT_URL % filename,
229
                'pdfurl': EXPECTED_URL % (filename[:-4] + '.pdf')
230
            })
231
        # Phase 2: Render all elements
232
        removeDocAttributes(root)
×
233
        for dirExamples in examples.values():
×
234
            for example in dirExamples:
×
235
                xml = etree.tounicode(example['element']).strip()
×
236
                xml = re.sub(
×
237
                    ' ?xmlns:doc="http://namespaces.zope.org/rml/doc"', '', xml
238
                )
239
                xml = dedent(xml)
×
240
                xml = enforceColumns(xml, 80)
×
241
                xml = highlightRML(xml)
×
242
                example['code'] = xml
×
243

244
        rmlFile.close()
×
245

246
    return examples
×
247

248

249
def main(outPath=None):
1✔
250
    examples = extractExamples(EXAMPLES_DIRECTORY)
×
251

252
    template = pagetemplate.RMLPageTemplateFile('reference.pt')
×
253

254
    directives = {}
×
255
    queue = [('document', document.IDocument)]
×
256
    while queue:
×
257
        tag, sig = queue.pop()
×
258
        processSignature(tag, sig, queue, examples, directives)
×
259
    directives = sorted(directives.values(), key=lambda d: d['name'])
×
260

261
    pdf = template(
×
262
        version=__version__,
263
        types=getAttributeTypes(),
264
        directives=directives)
265
    file_ = open(outPath or 'rml-reference.pdf', 'wb')
×
266
    file_.write(pdf)
×
267
    file_.close()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc