misc: fix python sonarcloud BLOCKER level issues
[vpp.git] / doxygen / siphon / process.py
1 # Copyright (c) 2016 Comcast Cable Communications Management, LLC.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at:
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Generation template class
16
17 import html.parser
18 import json
19 import logging
20 import os
21 import sys
22
23 import jinja2
24
25 # Classes register themselves in this dictionary
26 """Mapping of known processors to their classes"""
27 siphons = {}
28
29 """Mapping of known output formats to their classes"""
30 formats = {}
31
32
33 class Siphon(object):
34     """Generate rendered output for siphoned data."""
35
36     # Set by subclasses
37     """Our siphon name"""
38     name = None
39
40     # Set by subclasses
41     """Name of an identifier used by this siphon"""
42     identifier = None
43
44     # Set by subclasses
45     """The pyparsing object to use to parse with"""
46     _parser = None
47
48     """The input data"""
49     _cmds = None
50
51     """Group key to (directory,file) mapping"""
52     _group = None
53
54     """Logging handler"""
55     log = None
56
57     """Directory to look for siphon rendering templates"""
58     template_directory = None
59
60     """Template environment, if we're using templates"""
61     _tplenv = None
62
63     def __init__(self, template_directory, format):
64         super(Siphon, self).__init__()
65         self.log = logging.getLogger("siphon.process.%s" % self.name)
66
67         # Get our output format details
68         fmt_klass = formats[format]
69         fmt = fmt_klass()
70         self._format = fmt
71
72         # Sort out the template search path
73         def _tpldir(name):
74             return os.sep.join((template_directory, fmt.name, name))
75
76         self.template_directory = template_directory
77         searchpath = [
78             _tpldir(self.name),
79             _tpldir("default"),
80         ]
81         loader = jinja2.FileSystemLoader(searchpath=searchpath)
82         self._tplenv = jinja2.Environment(
83             loader=loader,
84             trim_blocks=True,
85             autoescape=True,
86             keep_trailing_newline=True)
87
88         # Convenience, get a reference to the internal escape and
89         # unescape methods in html.parser. These then become
90         # available to templates to use, if needed.
91         self._h = html.parser.HTMLParser()
92         self.escape = html.escape
93         self.unescape = html.unescape
94
95     # Output renderers
96
97     """Returns an object to be used as the sorting key in the item index."""
98     def index_sort_key(self, group):
99         return group
100
101     """Returns a string to use as the header at the top of the item index."""
102     def index_header(self):
103         return self.template("index_header")
104
105     """Returns the string fragment to use for each section in the item
106     index."""
107     def index_section(self, group):
108         return self.template("index_section", group=group)
109
110     """Returns the string fragment to use for each entry in the item index."""
111     def index_entry(self, meta, item):
112         return self.template("index_entry", meta=meta, item=item)
113
114     """Returns an object, typically a string, to be used as the sorting key
115     for items within a section."""
116     def item_sort_key(self, item):
117         return item['name']
118
119     """Returns a key for grouping items together."""
120     def group_key(self, directory, file, macro, name):
121         _global = self._cmds['_global']
122
123         if file in _global and 'group_label' in _global[file]:
124             self._group[file] = (directory, file)
125             return file
126
127         self._group[directory] = (directory, None)
128         return directory
129
130     """Returns a key for identifying items within a grouping."""
131     def item_key(self, directory, file, macro, name):
132         return name
133
134     """Returns a string to use as the header when rendering the item."""
135     def item_header(self, group):
136         return self.template("item_header", group=group)
137
138     """Returns a string to use as the body when rendering the item."""
139     def item_format(self, meta, item):
140         return self.template("item_format", meta=meta, item=item)
141
142     """Returns a string to use as the label for the page reference."""
143     def page_label(self, group):
144         return "_".join((
145             self.name,
146             self.sanitize_label(group)
147         ))
148
149     """Returns a title to use for a page."""
150     def page_title(self, group):
151         _global = self._cmds['_global']
152         (directory, file) = self._group[group]
153
154         if file and file in _global and 'group_label' in _global[file]:
155             return _global[file]['group_label']
156
157         if directory in _global and 'group_label' in _global[directory]:
158             return _global[directory]['group_label']
159
160         return directory
161
162     """Returns a string to use as the label for the section reference."""
163     def item_label(self, group, item):
164         return "__".join((
165             self.name,
166             item
167         ))
168
169     """Label sanitizer; for creating Doxygen references"""
170     def sanitize_label(self, value):
171         return value.replace(" ", "_") \
172                     .replace("/", "_") \
173                     .replace(".", "_")
174
175     """Template processor"""
176     def template(self, name, **kwargs):
177         tpl = self._tplenv.get_template(name + self._format.extension)
178         return tpl.render(
179             this=self,
180             **kwargs)
181
182     # Processing methods
183
184     """Parse the input file into a more usable dictionary structure."""
185     def load_json(self, files):
186         self._cmds = {}
187         self._group = {}
188
189         line_num = 0
190         line_start = 0
191         for filename in files:
192             filename = os.path.relpath(filename)
193             self.log.info("Parsing items in file \"%s\"." % filename)
194             data = None
195             with open(filename, "r") as fd:
196                 data = json.load(fd)
197
198             self._cmds['_global'] = data['global']
199
200             # iterate the items loaded and regroup it
201             for item in data["items"]:
202                 try:
203                     o = self._parser.parse(item['block'])
204                 except Exception:
205                     self.log.error("Exception parsing item: %s\n%s"
206                                    % (json.dumps(item, separators=(',', ': '),
207                                                  indent=4),
208                                       item['block']))
209                     raise
210
211                 # Augment the item with metadata
212                 o["meta"] = {}
213                 for key in item:
214                     if key == 'block':
215                         continue
216                     o['meta'][key] = item[key]
217
218                 # Load some interesting fields
219                 directory = item['directory']
220                 file = item['file']
221                 macro = o["macro"]
222                 name = o["name"]
223
224                 # Generate keys to group items by
225                 group_key = self.group_key(directory, file, macro, name)
226                 item_key = self.item_key(directory, file, macro, name)
227
228                 if group_key not in self._cmds:
229                     self._cmds[group_key] = {}
230
231                 self._cmds[group_key][item_key] = o
232
233     """Iterate over the input data, calling render methods to generate the
234     output."""
235     def process(self, out=None):
236
237         if out is None:
238             out = sys.stdout
239
240         # Accumulated body contents
241         contents = ""
242
243         # Write the header for this siphon type
244         out.write(self.index_header())
245
246         # Sort key helper for the index
247         def group_sort_key(group):
248             return self.index_sort_key(group)
249
250         # Iterate the dictionary and process it
251         for group in sorted(self._cmds.keys(), key=group_sort_key):
252             if group.startswith('_'):
253                 continue
254
255             self.log.info("Processing items in group \"%s\" (%s)." %
256                           (group, group_sort_key(group)))
257
258             # Generate the section index entry (write it now)
259             out.write(self.index_section(group))
260
261             # Generate the item header (save for later)
262             contents += self.item_header(group)
263
264             def item_sort_key(key):
265                 return self.item_sort_key(self._cmds[group][key])
266
267             for key in sorted(self._cmds[group].keys(), key=item_sort_key):
268                 self.log.debug("--- Processing key \"%s\" (%s)." %
269                                (key, item_sort_key(key)))
270
271                 o = self._cmds[group][key]
272                 meta = {
273                     "directory": o['meta']['directory'],
274                     "file": o['meta']['file'],
275                     "macro": o['macro'],
276                     "name": o['name'],
277                     "key": key,
278                     "label": self.item_label(group, key),
279                 }
280
281                 # Generate the index entry for the item (write it now)
282                 out.write(self.index_entry(meta, o))
283
284                 # Generate the item itself (save for later)
285                 contents += self.item_format(meta, o)
286
287         # Deliver the accumulated body output
288         out.write(contents)
289
290
291 class Format(object):
292     """Output format class"""
293
294     """Name of this output format"""
295     name = None
296
297     """Expected file extension of templates that build this format"""
298     extension = None
299
300
301 class FormatMarkdown(Format):
302     """Markdown output format"""
303     name = "markdown"
304     extension = ".md"
305
306
307 # Register 'markdown'
308 formats["markdown"] = FormatMarkdown
309
310
311 class FormatItemlist(Format):
312     """Itemlist output format"""
313     name = "itemlist"
314     extension = ".itemlist"
315
316
317 # Register 'itemlist'
318 formats["itemlist"] = FormatItemlist