Refactor pre-Doxy siphon scripts; VPP-396
[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 logging, os,sys, cgi, json, jinja2, HTMLParser
18
19 # Classes register themselves in this dictionary
20 """Mapping of known processors to their classes"""
21 siphons = {}
22
23
24 """Generate rendered output for siphoned data."""
25 class Siphon(object):
26
27     # Set by subclasses
28     """Our siphon name"""
29     name = None
30
31     # Set by subclasses
32     """Name of an identifier used by this siphon"""
33     identifier = None
34
35     # Set by subclasses
36     """The pyparsing object to use to parse with"""
37     _parser = None
38
39     """The input data"""
40     _cmds = None
41
42     """Group key to (directory,file) mapping"""
43     _group = None
44
45     """Logging handler"""
46     log = None
47
48     """Directory to look for siphon rendering templates"""
49     template_directory = None
50
51     """Template environment, if we're using templates"""
52     _tplenv = None
53
54     def __init__(self, template_directory=None):
55         super(Siphon, self).__init__()
56         self.log = logging.getLogger("siphon.process.%s" % self.name)
57
58         if template_directory is not None:
59           self.template_directory = template_directory
60           searchpath = [
61               template_directory + "/" + self.name,
62               template_directory + "/" + "default",
63           ]
64           loader = jinja2.FileSystemLoader(searchpath=searchpath)
65           self._tplenv = jinja2.Environment(
66               loader=loader,
67               trim_blocks=True,
68               keep_trailing_newline=True)
69
70           # Convenience, get a reference to the internal escape and
71           # unescape methods in cgi and HTMLParser. These then become
72           # available to templates to use, if needed.
73           self._h = HTMLParser.HTMLParser()
74           self.escape = cgi.escape
75           self.unescape = self._h.unescape
76
77
78     # Output renderers
79
80     """Returns an object to be used as the sorting key in the item index."""
81     def index_sort_key(self, group):
82         return group
83
84     """Returns a string to use as the header at the top of the item index."""
85     def index_header(self):
86         return self.template("index_header")
87
88     """Returns the string fragment to use for each section in the item
89     index."""
90     def index_section(self, group):
91         return self.template("index_section", group=group)
92
93     """Returns the string fragment to use for each entry in the item index."""
94     def index_entry(self, meta, item):
95         return self.template("index_entry", meta=meta, item=item)
96
97     """Returns an object, typically a string, to be used as the sorting key
98     for items within a section."""
99     def item_sort_key(self, item):
100         return item['name']
101
102     """Returns a key for grouping items together."""
103     def group_key(self, directory, file, macro, name):
104         _global = self._cmds['_global']
105
106         if file in _global and 'group_label' in _global[file]:
107             self._group[file] = (directory, file)
108             return file
109
110         self._group[directory] = (directory, None)
111         return directory
112
113     """Returns a key for identifying items within a grouping."""
114     def item_key(self, directory, file, macro, name):
115         return name
116
117     """Returns a string to use as the header when rendering the item."""
118     def item_header(self, group):
119         return self.template("item_header", group=group)
120
121     """Returns a string to use as the body when rendering the item."""
122     def item_format(self, meta, item):
123         return self.template("item_format", meta=meta, item=item)
124
125     """Returns a string to use as the label for the page reference."""
126     def page_label(self, group):
127         return "_".join((
128             self.name,
129             self.sanitize_label(group)
130         ))
131
132     """Returns a title to use for a page."""
133     def page_title(self, group):
134         _global = self._cmds['_global']
135         (directory, file) = self._group[group]
136
137         if file and file in _global and 'group_label' in _global[file]:
138             return _global[file]['group_label']
139
140         if directory in _global and 'group_label' in _global[directory]:
141             return _global[directory]['group_label']
142
143         return directory
144
145     """Returns a string to use as the label for the section reference."""
146     def item_label(self, group, item):
147         return "__".join((
148             self.name,
149             item
150         ))
151
152     """Label sanitizer; for creating Doxygen references"""
153     def sanitize_label(self, value):
154         return value.replace(" ", "_") \
155                     .replace("/", "_") \
156                     .replace(".", "_")
157
158     """Template processor"""
159     def template(self, name, **kwargs):
160       tpl = self._tplenv.get_template(name + ".md")
161       return tpl.render(
162             this=self,
163             **kwargs)
164
165
166     # Processing methods
167
168     """Parse the input file into a more usable dictionary structure."""
169     def load_json(self, files):
170         self._cmds = {}
171         self._group = {}
172
173         line_num = 0
174         line_start = 0
175         for filename in files:
176             filename = os.path.relpath(filename)
177             self.log.info("Parsing items in file \"%s\"." % filename)
178             data = None
179             with open(filename, "r") as fd:
180                 data = json.load(fd)
181
182             self._cmds['_global'] = data['global']
183
184             # iterate the items loaded and regroup it
185             for item in data["items"]:
186                 try:
187                     o = self._parser.parse(item['block'])
188                 except:
189                     self.log.error("Exception parsing item: %s\n%s" \
190                             % (json.dumps(item, separators=(',', ': '),
191                                 indent=4),
192                                 item['block']))
193                     raise
194
195                 # Augment the item with metadata
196                 o["meta"] = {}
197                 for key in item:
198                     if key == 'block':
199                         continue
200                     o['meta'][key] = item[key]
201
202                 # Load some interesting fields
203                 directory = item['directory']
204                 file = item['file']
205                 macro = o["macro"]
206                 name = o["name"]
207
208                 # Generate keys to group items by
209                 group_key = self.group_key(directory, file, macro, name)
210                 item_key = self.item_key(directory, file, macro, name)
211
212                 if group_key not in self._cmds:
213                     self._cmds[group_key] = {}
214
215                 self._cmds[group_key][item_key] = o
216
217     """Iterate over the input data, calling render methods to generate the
218     output."""
219     def process(self, out=None):
220
221         if out is None:
222             out = sys.stdout
223
224         # Accumulated body contents
225         contents = ""
226
227         # Write the header for this siphon type
228         out.write(self.index_header())
229
230         # Sort key helper for the index
231         def group_sort_key(group):
232             return self.index_sort_key(group)
233
234         # Iterate the dictionary and process it
235         for group in sorted(self._cmds.keys(), key=group_sort_key):
236             if group.startswith('_'):
237                 continue
238
239             self.log.info("Processing items in group \"%s\" (%s)." % \
240                 (group, group_sort_key(group)))
241
242             # Generate the section index entry (write it now)
243             out.write(self.index_section(group))
244
245             # Generate the item header (save for later)
246             contents += self.item_header(group)
247
248             def item_sort_key(key):
249                 return self.item_sort_key(self._cmds[group][key])
250
251             for key in sorted(self._cmds[group].keys(), key=item_sort_key):
252                 self.log.debug("--- Processing key \"%s\" (%s)." % \
253                     (key, item_sort_key(key)))
254
255                 o = self._cmds[group][key]
256                 meta = {
257                     "directory": o['meta']['directory'],
258                     "file": o['meta']['file'],
259                     "macro": o['macro'],
260                     "key": key,
261                     "label": self.item_label(group, key),
262                 }
263
264                 # Generate the index entry for the item (write it now)
265                 out.write(self.index_entry(meta, o))
266
267                 # Generate the item itself (save for later)
268                 contents += self.item_format(meta, o)
269
270         # Deliver the accumulated body output
271         out.write(contents)