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."""
28 class Generate(object):
29 """Matches a siphon comment block start"""
31 siphon_block_start = re.compile("^\s*/\*\?\s*(.*)$")
33 """Matches a siphon comment block stop"""
34 siphon_block_stop = re.compile("^(.*)\s*\?\*/\s*$")
36 """Siphon block directive delimiter"""
37 siphon_block_delimiter = "%%"
39 """Matches a siphon block directive such as
40 '%clicmd:group_label Debug CLI%'"""
41 siphon_block_directive = re.compile(
42 "(%s)\s*([a-zA-Z0-9_:]+)\s+(.*)\s*(%s)"
43 % (siphon_block_delimiter, siphon_block_delimiter)
46 """Matches the start of an initializer block"""
47 siphon_initializer = re.compile("\s*=")
49 """Collated output for each siphon"""
52 """Directory prefix to strip from input filenames to keep things tidy."""
55 """List of known siphons"""
61 def __init__(self, output_directory, input_prefix):
62 super(Generate, self).__init__()
63 self.log = logging.getLogger("siphon.generate")
65 # Build a list of known siphons
66 self.known_siphons = []
67 for item in siphon_patterns:
69 if siphon not in self.known_siphons:
70 self.known_siphons.append(siphon)
72 # Setup information for siphons we know about
74 for siphon in self.known_siphons:
75 self.output[siphon] = {
76 "file": "%s/%s.siphon" % (output_directory, siphon),
81 self.input_prefix = input_prefix
84 count open and close braces in str
85 return (0, index) when braces were found and count becomes 0.
86 index indicates the position at which the last closing brace was
88 return (-1, -1) if a closing brace is found before any opening one.
89 return (count, -1) if not all opening braces are closed, count is the
93 def count_braces(self, str, count=0, found=False):
94 for index in range(0, len(str)):
98 elif str[index] == "}":
100 # means we never found an open brace
104 if count == 0 and found:
105 return (count, index)
109 def parse(self, filename):
110 # Strip the current directory off the start of the
111 # filename for brevity
112 if filename[0 : len(self.input_prefix)] == self.input_prefix:
113 filename = filename[len(self.input_prefix) :]
114 if filename[0] == "/":
115 filename = filename[1:]
117 # Work out the abbreviated directory name
118 directory = os.path.dirname(filename)
119 if directory[0:2] == "./":
120 directory = directory[2:]
121 elif directory[0 : len(self.input_prefix)] == self.input_prefix:
122 directory = directory[len(self.input_prefix) :]
123 if directory[0] == "/":
124 directory = directory[1:]
126 # Open the file and explore its contents...
127 self.log.info("Siphoning from %s." % filename)
129 with open(filename) as fd:
139 str = line[:-1] # filter \n
141 """See if there is a block directive and if so extract it"""
143 def process_block_directive(str, directives):
144 m = self.siphon_block_directive.search(str)
147 v = m.group(3).strip()
149 # Return only the parts we did not match
150 return str[0 : m.start(1)] + str[m.end(4) :]
154 def process_block_prefix(str):
155 if str.startswith(" * "):
162 # See if the line contains the start of a siphon doc block
163 m = self.siphon_block_start.search(str)
168 # Now check if the block closes on the same line
169 m = self.siphon_block_stop.search(t)
174 # Check for directives
175 t = process_block_directive(t, directives)
177 # Filter for normal comment prefixes
178 t = process_block_prefix(t)
187 # Check to see if we have an end block marker
188 m = self.siphon_block_stop.search(str)
195 # Check for directives
196 t = process_block_directive(t, directives)
198 # Filter for normal comment prefixes
199 t = process_block_prefix(t)
202 siphon_block += t + "\n"
208 # Look for blocks we need to siphon
209 for p in siphon_patterns:
211 siphon = [p[1], str + "\n", 0]
212 siphon_line = line_num
214 # see if we have an initializer
215 m = self.siphon_initializer.search(str)
217 # count the braces on this line
218 (count, index) = self.count_braces(str[m.start() :])
220 # TODO - it's possible we have the
221 # initializer all on the first line
222 # we should check for it, but also
223 # account for the possibility that
224 # the open brace is on the next line
227 # close_siphon = siphon
230 # no initializer: close the siphon right now
231 close_siphon = siphon
234 # See if we should end the siphon here - do we have
236 (count, index) = self.count_braces(str, count=siphon[2], found=True)
238 # braces balanced - add the substring and
240 siphon[1] += str[: index + 1] + ";\n"
241 close_siphon = siphon
244 # add the whole string, move on
246 siphon[1] += str + "\n"
248 if close_siphon is not None:
249 # Write the siphoned contents to the right place
250 siphon_name = close_siphon[0]
252 # Copy directives for the file
254 for key in directives:
256 (sn, label) = key.split(":")
257 if sn == siphon_name:
258 details[label] = directives[key]
260 details[key] = directives[key]
262 # Copy details for this block
263 details["file"] = filename
264 details["directory"] = directory
265 details["line_start"] = siphon_line
266 details["line_end"] = line_num
267 details["siphon_block"] = siphon_block.strip()
268 details["block"] = close_siphon[1]
271 self.output[siphon_name]["items"].append(details)
278 for key in directives.keys():
282 if filename.endswith("/dir.dox"):
283 # very special! use the parent directory name
288 (sn, label) = key.split(":")
290 if sn not in self.output:
292 if "global" not in self.output[sn]:
293 self.output[sn]["global"] = {}
294 if l not in self.output[sn]["global"]:
295 self.output[sn]["global"][l] = {}
297 self.output[sn]["global"][l][label] = directives[key]
301 for siphon in self.output.keys():
302 self.log.info("Saving siphon data %s." % siphon)
303 s = self.output[siphon]
304 with open(s["file"], "a") as fp:
305 json.dump(s, fp, separators=(",", ": "), indent=4, sort_keys=True)