blob: c7f8f1a232b095fdc95e282544e7e715b48218df [file] [log] [blame]
Chris Luke90f52bf2016-09-12 08:55:13 -04001# 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
17import logging, os,sys, cgi, json, jinja2, HTMLParser
18
19# Classes register themselves in this dictionary
20"""Mapping of known processors to their classes"""
21siphons = {}
22
23
24"""Generate rendered output for siphoned data."""
25class 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)