57e323e48acbf2dfba729b9a0d9c9d68d95693b7
[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             keep_trailing_newline=True)
86
87         # Convenience, get a reference to the internal escape and
88         # unescape methods in html.parser. These then become
89         # available to templates to use, if needed.
90         self._h = html.parser.HTMLParser()
91         self.escape = html.escape
92         self.unescape = html.unescape
93
94     # Output renderers
95
96     """Returns an object to be used as the sorting key in the item index."""
97     def index_sort_key(self, group):
98         return group
99
100     """Returns a string to use as the header at the top of the item index."""
101     def index_header(self):
102         return self.template("index_header")
103
104     """Returns the string fragment to use for each section in the item
105     index."""
106     def index_section(self, group):
107         return self.template("index_section", group=group)
108
109     """Returns the string fragment to use for each entry in the item index."""
110     def index_entry(self, meta, item):
111         return self.template("index_entry", meta=meta, item=item)
112
113     """Returns an object, typically a string, to be used as the sorting key
114     for items within a section."""
115     def item_sort_key(self, item):
116         return item['name']
117
118     """Returns a key for grouping items together."""
119     def group_key(self, directory, file, macro, name):
120         _global = self._cmds['_global']
121
122         if file in _global and 'group_label' in _global[file]:
123             self._group[file] = (directory, file)
124             return file
125
126         self._group[directory] = (directory, None)
127         return directory
128
129     """Returns a key for identifying items within a grouping."""
130     def item_key(self, directory, file, macro, name):
131         return name
132
133     """Returns a string to use as the header when rendering the item."""
134     def item_header(self, group):
135         return self.template("item_header", group=group)
136
137     """Returns a string to use as the body when rendering the item."""
138     def item_format(self, meta, item):
139         return self.template("item_format", meta=meta, item=item)
140
141     """Returns a string to use as the label for the page reference."""
142     def page_label(self, group):
143         return "_".join((
144             self.name,
145             self.sanitize_label(group)
146         ))
147
148     """Returns a title to use for a page."""
149     def page_title(self, group):
150         _global = self._cmds['_global']
151         (directory, file) = self._group[group]
152
153         if file and file in _global and 'group_label' in _global[file]:
154             return _global[file]['group_label']
155
156         if directory in _global and 'group_label' in _global[directory]:
157             return _global[directory]['group_label']
158
159         return directory
160
161     """Returns a string to use as the label for the section reference."""
162     def item_label(self, group, item):
163         return "__".join((
164             self.name,
165             item
166         ))
167
168     """Label sanitizer; for creating Doxygen references"""
169     def sanitize_label(self, value):
170         return value.replace(" ", "_") \
171                     .replace("/", "_") \
172                     .replace(".", "_")
173
174     """Template processor"""
175     def template(self, name, **kwargs):
176         tpl = self._tplenv.get_template(name + self._format.extension)
177         return tpl.render(
178             this=self,
179             **kwargs)
180
181     # Processing methods
182
183     """Parse the input file into a more usable dictionary structure."""
184     def load_json(self, files):
185         self._cmds = {}
186         self._group = {}
187
188         line_num = 0
189         line_start = 0
190         for filename in files:
191             filename = os.path.relpath(filename)
192             self.log.info("Parsing items in file \"%s\"." % filename)
193             data = None
194             with open(filename, "r") as fd:
195                 data = json.load(fd)
196
197             self._cmds['_global'] = data['global']
198
199             # iterate the items loaded and regroup it
200             for item in data["items"]:
201                 try:
202                     o = self._parser.parse(item['block'])
203                 except Exception:
204                     self.log.error("Exception parsing item: %s\n%s"
205                                    % (json.dumps(item, separators=(',', ': '),
206                                                  indent=4),
207                                       item['block']))
208                     raise
209
210                 # Augment the item with metadata
211                 o["meta"] = {}
212                 for key in item:
213                     if key == 'block':
214                         continue
215                     o['meta'][key] = item[key]
216
217                 # Load some interesting fields
218                 directory = item['directory']
219                 file = item['file']
220                 macro = o["macro"]
221                 name = o["name"]
222
223                 # Generate keys to group items by
224                 group_key = self.group_key(directory, file, macro, name)
225                 item_key = self.item_key(directory, file, macro, name)
226
227                 if group_key not in self._cmds:
228                     self._cmds[group_key] = {}
229
230                 self._cmds[group_key][item_key] = o
231
232     """Iterate over the input data, calling render methods to generate the
233     output."""
234     def process(self, out=None):
235
236         if out is None:
237             out = sys.stdout
238
239         # Accumulated body contents
240         contents = ""
241
242         # Write the header for this siphon type
243         out.write(self.index_header())
244
245         # Sort key helper for the index
246         def group_sort_key(group):
247             return self.index_sort_key(group)
248
249         # Iterate the dictionary and process it
250         for group in sorted(self._cmds.keys(), key=group_sort_key):
251             if group.startswith('_'):
252                 continue
253
254             self.log.info("Processing items in group \"%s\" (%s)." %
255                           (group, group_sort_key(group)))
256
257             # Generate the section index entry (write it now)
258             out.write(self.index_section(group))
259
260             # Generate the item header (save for later)
261             contents += self.item_header(group)
262
263             def item_sort_key(key):
264                 return self.item_sort_key(self._cmds[group][key])
265
266             for key in sorted(self._cmds[group].keys(), key=item_sort_key):
267                 self.log.debug("--- Processing key \"%s\" (%s)." %
268                                (key, item_sort_key(key)))
269
270                 o = self._cmds[group][key]
271                 meta = {
272                     "directory": o['meta']['directory'],
273                     "file": o['meta']['file'],
274                     "macro": o['macro'],
275                     "name": o['name'],
276                     "key": key,
277                     "label": self.item_label(group, key),
278                 }
279
280                 # Generate the index entry for the item (write it now)
281                 out.write(self.index_entry(meta, o))
282
283                 # Generate the item itself (save for later)
284                 contents += self.item_format(meta, o)
285
286         # Deliver the accumulated body output
287         out.write(contents)
288
289
290 class Format(object):
291     """Output format class"""
292
293     """Name of this output format"""
294     name = None
295
296     """Expected file extension of templates that build this format"""
297     extension = None
298
299
300 class FormatMarkdown(Format):
301     """Markdown output format"""
302     name = "markdown"
303     extension = ".md"
304
305
306 # Register 'markdown'
307 formats["markdown"] = FormatMarkdown
308
309
310 class FormatItemlist(Format):
311     """Itemlist output format"""
312     name = "itemlist"
313     extension = ".itemlist"
314
315
316 # Register 'itemlist'
317 formats["itemlist"] = FormatItemlist