1 # Copyright (c) 2016 Comcast Cable Communications Management, LLC.
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:
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 # Generate .siphon source fragments for later processing
22 """List of (regexp, siphon_name) tuples for matching the start of C
23 initializer blocks in source files. Each siphon class registers
24 themselves on this list."""
27 class Generate(object):
28 """Matches a siphon comment block start"""
29 siphon_block_start = re.compile("^\s*/\*\?\s*(.*)$")
31 """Matches a siphon comment block stop"""
32 siphon_block_stop = re.compile("^(.*)\s*\?\*/\s*$")
34 """Siphon block directive delimiter"""
35 siphon_block_delimiter = "%%"
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))
42 """Matches the start of an initializer block"""
43 siphon_initializer = re.compile("\s*=")
45 """Collated output for each siphon"""
48 """Directory prefix to strip from input filenames to keep things tidy."""
51 """List of known siphons"""
58 def __init__(self, output_directory, input_prefix):
59 super(Generate, self).__init__()
60 self.log = logging.getLogger("siphon.generate")
62 # Build a list of known siphons
63 self.known_siphons = []
64 for item in siphon_patterns:
66 if siphon not in self.known_siphons:
67 self.known_siphons.append(siphon)
69 # Setup information for siphons we know about
71 for siphon in self.known_siphons:
72 self.output[siphon] = {
73 "file": "%s/%s.siphon" % (output_directory, siphon),
78 self.input_prefix = input_prefix
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
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
90 def count_braces(self, str, count=0, found=False):
91 for index in range(0, len(str)):
95 elif str[index] == '}':
97 # means we never found an open brace
101 if count == 0 and found:
102 return (count, index)
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:]
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:]
123 # Open the file and explore its contents...
124 self.log.info("Siphoning from %s." % filename)
126 with open(filename) as fd:
136 str = line[:-1] # filter \n
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)
143 v = m.group(3).strip()
145 # Return only the parts we did not match
146 return str[0:m.start(1)] + str[m.end(4):]
150 def process_block_prefix(str):
151 if str.startswith(" * "):
158 # See if the line contains the start of a siphon doc block
159 m = self.siphon_block_start.search(str)
164 # Now check if the block closes on the same line
165 m = self.siphon_block_stop.search(t)
170 # Check for directives
171 t = process_block_directive(t, directives)
173 # Filter for normal comment prefixes
174 t = process_block_prefix(t)
183 # Check to see if we have an end block marker
184 m = self.siphon_block_stop.search(str)
191 # Check for directives
192 t = process_block_directive(t, directives)
194 # Filter for normal comment prefixes
195 t = process_block_prefix(t)
198 siphon_block += t + "\n"
205 # Look for blocks we need to siphon
206 for p in siphon_patterns:
208 siphon = [ p[1], str + "\n", 0 ]
209 siphon_line = line_num
211 # see if we have an initializer
212 m = self.siphon_initializer.search(str)
214 # count the braces on this line
216 self.count_braces(str[m.start():])
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
225 # close_siphon = siphon
228 # no initializer: close the siphon right now
229 close_siphon = siphon
232 # See if we should end the siphon here - do we have
234 (count, index) = self.count_braces(str,
235 count=siphon[2], found=True)
237 # braces balanced - add the substring and
239 siphon[1] += str[:index+1] + ";\n"
240 close_siphon = siphon
243 # add the whole string, move on
245 siphon[1] += str + "\n"
247 if close_siphon is not None:
248 # Write the siphoned contents to the right place
249 siphon_name = close_siphon[0]
251 # Copy directives for the file
253 for key in directives:
255 (sn, label) = key.split(":")
256 if sn == siphon_name:
257 details[label] = directives[key]
259 details[key] = directives[key]
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]
270 self.output[siphon_name]['items'].append(details)
277 for key in directives.keys():
281 if filename.endswith("/dir.dox"):
282 # very special! use the parent directory name
287 (sn, label) = key.split(":")
289 if sn not in self.output:
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] = {}
296 self.output[sn]['global'][l][label] = directives[key]
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:
305 separators=(',', ': '), indent=4, sort_keys=True)