blob: 2ae5a1b6f1b844163abcb7ec46f41fc8ae706484 [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# Generate .siphon source fragments for later processing
16
Paul Vinciguerra464e5e02019-11-01 15:07:32 -040017import json
Chris Luke90f52bf2016-09-12 08:55:13 -040018import logging
Paul Vinciguerra464e5e02019-11-01 15:07:32 -040019import os
20import re
Chris Luke90f52bf2016-09-12 08:55:13 -040021
22"""List of (regexp, siphon_name) tuples for matching the start of C
23 initializer blocks in source files. Each siphon class registers
Paul Vinciguerra464e5e02019-11-01 15:07:32 -040024 themselves on this list."""
Chris Luke90f52bf2016-09-12 08:55:13 -040025siphon_patterns = []
26
27class Generate(object):
28 """Matches a siphon comment block start"""
29 siphon_block_start = re.compile("^\s*/\*\?\s*(.*)$")
30
31 """Matches a siphon comment block stop"""
32 siphon_block_stop = re.compile("^(.*)\s*\?\*/\s*$")
33
34 """Siphon block directive delimiter"""
35 siphon_block_delimiter = "%%"
36
37 """Matches a siphon block directive such as
38 '%clicmd:group_label Debug CLI%'"""
39 siphon_block_directive = re.compile("(%s)\s*([a-zA-Z0-9_:]+)\s+(.*)\s*(%s)" % \
40 (siphon_block_delimiter, siphon_block_delimiter))
41
42 """Matches the start of an initializer block"""
43 siphon_initializer = re.compile("\s*=")
44
45 """Collated output for each siphon"""
46 output = None
47
48 """Directory prefix to strip from input filenames to keep things tidy."""
49 input_prefix = None
50
51 """List of known siphons"""
52 known_siphons = None
53
54 """Logging handler"""
55 log = None
56
57
58 def __init__(self, output_directory, input_prefix):
59 super(Generate, self).__init__()
60 self.log = logging.getLogger("siphon.generate")
61
62 # Build a list of known siphons
63 self.known_siphons = []
64 for item in siphon_patterns:
65 siphon = item[1]
66 if siphon not in self.known_siphons:
67 self.known_siphons.append(siphon)
68
69 # Setup information for siphons we know about
70 self.output = {}
71 for siphon in self.known_siphons:
72 self.output[siphon] = {
73 "file": "%s/%s.siphon" % (output_directory, siphon),
74 "global": {},
75 "items": [],
76 }
77
78 self.input_prefix = input_prefix
79
80
81 """
82 count open and close braces in str
83 return (0, index) when braces were found and count becomes 0.
84 index indicates the position at which the last closing brace was
85 found.
86 return (-1, -1) if a closing brace is found before any opening one.
87 return (count, -1) if not all opening braces are closed, count is the
88 current depth
89 """
90 def count_braces(self, str, count=0, found=False):
91 for index in range(0, len(str)):
92 if str[index] == '{':
93 count += 1;
94 found = True
95 elif str[index] == '}':
96 if count == 0:
97 # means we never found an open brace
98 return (-1, -1)
99 count -= 1;
100
101 if count == 0 and found:
102 return (count, index)
103
104 return (count, -1)
105
106 def parse(self, filename):
107 # Strip the current directory off the start of the
108 # filename for brevity
109 if filename[0:len(self.input_prefix)] == self.input_prefix:
110 filename = filename[len(self.input_prefix):]
111 if filename[0] == "/":
112 filename = filename[1:]
113
114 # Work out the abbreviated directory name
115 directory = os.path.dirname(filename)
116 if directory[0:2] == "./":
117 directory = directory[2:]
118 elif directory[0:len(self.input_prefix)] == self.input_prefix:
119 directory = directory[len(self.input_prefix):]
120 if directory[0] == "/":
121 directory = directory[1:]
122
123 # Open the file and explore its contents...
124 self.log.info("Siphoning from %s." % filename)
125 directives = {}
126 with open(filename) as fd:
127 siphon = None
128 close_siphon = None
129 siphon_block = ""
130 in_block = False
131 line_num = 0
132 siphon_line = 0
133
134 for line in fd:
135 line_num += 1
136 str = line[:-1] # filter \n
137
138 """See if there is a block directive and if so extract it"""
139 def process_block_directive(str, directives):
140 m = self.siphon_block_directive.search(str)
141 if m is not None:
142 k = m.group(2)
143 v = m.group(3).strip()
144 directives[k] = v
145 # Return only the parts we did not match
146 return str[0:m.start(1)] + str[m.end(4):]
147
148 return str
149
150 def process_block_prefix(str):
151 if str.startswith(" * "):
152 str = str[3:]
153 elif str == " *":
154 str = ""
155 return str
156
157 if not in_block:
158 # See if the line contains the start of a siphon doc block
159 m = self.siphon_block_start.search(str)
160 if m is not None:
161 in_block = True
162 t = m.group(1)
163
164 # Now check if the block closes on the same line
165 m = self.siphon_block_stop.search(t)
166 if m is not None:
167 t = m.group(1)
168 in_block = False
169
170 # Check for directives
171 t = process_block_directive(t, directives)
172
173 # Filter for normal comment prefixes
174 t = process_block_prefix(t)
175
176 # Add what is left
177 siphon_block += t
178
179 # Skip to next line
180 continue
181
182 else:
183 # Check to see if we have an end block marker
184 m = self.siphon_block_stop.search(str)
185 if m is not None:
186 in_block = False
187 t = m.group(1)
188 else:
189 t = str
190
191 # Check for directives
192 t = process_block_directive(t, directives)
193
194 # Filter for normal comment prefixes
195 t = process_block_prefix(t)
196
197 # Add what is left
198 siphon_block += t + "\n"
199
200 # Skip to next line
201 continue
202
203
204 if siphon is None:
205 # Look for blocks we need to siphon
206 for p in siphon_patterns:
207 if p[0].match(str):
208 siphon = [ p[1], str + "\n", 0 ]
209 siphon_line = line_num
210
211 # see if we have an initializer
212 m = self.siphon_initializer.search(str)
213 if m is not None:
214 # count the braces on this line
215 (count, index) = \
216 self.count_braces(str[m.start():])
217 siphon[2] = count
218 # TODO - it's possible we have the
219 # initializer all on the first line
220 # we should check for it, but also
221 # account for the possibility that
222 # the open brace is on the next line
223 #if count == 0:
224 # # braces balanced
225 # close_siphon = siphon
226 # siphon = None
227 else:
228 # no initializer: close the siphon right now
229 close_siphon = siphon
230 siphon = None
231 else:
232 # See if we should end the siphon here - do we have
233 # balanced braces?
234 (count, index) = self.count_braces(str,
235 count=siphon[2], found=True)
236 if count == 0:
237 # braces balanced - add the substring and
238 # close the siphon
239 siphon[1] += str[:index+1] + ";\n"
240 close_siphon = siphon
241 siphon = None
242 else:
243 # add the whole string, move on
244 siphon[2] = count
245 siphon[1] += str + "\n"
246
247 if close_siphon is not None:
248 # Write the siphoned contents to the right place
249 siphon_name = close_siphon[0]
250
251 # Copy directives for the file
252 details = {}
253 for key in directives:
254 if ":" in key:
255 (sn, label) = key.split(":")
256 if sn == siphon_name:
257 details[label] = directives[key]
258 else:
259 details[key] = directives[key]
260
261 # Copy details for this block
262 details['file'] = filename
263 details['directory'] = directory
264 details['line_start'] = siphon_line
265 details['line_end'] = line_num
266 details['siphon_block'] = siphon_block.strip()
267 details["block"] = close_siphon[1]
268
269 # Store the item
270 self.output[siphon_name]['items'].append(details)
271
272 # All done
273 close_siphon = None
274 siphon_block = ""
275
276 # Update globals
277 for key in directives.keys():
278 if ':' not in key:
279 continue
280
281 if filename.endswith("/dir.dox"):
282 # very special! use the parent directory name
283 l = directory
284 else:
285 l = filename
286
287 (sn, label) = key.split(":")
288
289 if sn not in self.output:
290 self.output[sn] = {}
291 if 'global' not in self.output[sn]:
292 self.output[sn]['global'] = {}
293 if l not in self.output[sn]['global']:
294 self.output[sn]['global'][l] = {}
295
296 self.output[sn]['global'][l][label] = directives[key]
297
298 def deliver(self):
299 # Write out the data
300 for siphon in self.output.keys():
301 self.log.info("Saving siphon data %s." % siphon)
302 s = self.output[siphon]
303 with open(s['file'], "a") as fp:
304 json.dump(s, fp,
305 separators=(',', ': '), indent=4, sort_keys=True)
306