1 # Copyright (c) 2017 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
16 Parsing of the specification YAML file.
21 from yaml import load, YAMLError
22 from pprint import pformat
24 from errors import PresentationError
27 class Specification(object):
28 """Specification of Presentation and analytics layer.
30 - based on specification specified in the specification YAML file
31 - presentation and analytics layer is model driven
34 # Tags are used in specification YAML file and replaced while the file is
39 def __init__(self, cfg_file):
42 :param cfg_file: File handler for the specification YAML file.
43 :type cfg_file: BinaryIO
45 self._cfg_file = cfg_file
48 self._specification = {"environment": dict(),
58 def specification(self):
59 """Getter - specification.
61 :returns: Specification.
64 return self._specification
67 def environment(self):
68 """Getter - environment.
70 :returns: Environment specification.
73 return self._specification["environment"]
77 """Getter - static content.
79 :returns: Static content specification.
82 return self._specification["static"]
88 :returns: Debug specification
91 return self._specification["debug"]
95 """Getter - debug mode
97 :returns: True if debug mode is on, otherwise False.
102 if self.environment["configuration"]["CFG[DEBUG]"] == 1:
111 """Getter - specification - inputs.
117 return self._specification["input"]
121 """Getter - builds defined in specification.
123 :returns: Builds defined in the specification.
126 return self.input["builds"]
130 """Getter - specification - output formats and versions to be generated.
132 - versions: full, ...
134 :returns: Outputs to be generated.
137 return self._specification["output"]
141 """Getter - tables to be generated.
143 :returns: List of specifications of tables to be generated.
146 return self._specification["tables"]
150 """Getter - plots to be generated.
152 :returns: List of specifications of plots to be generated.
155 return self._specification["plots"]
159 """Getter - files to be generated.
161 :returns: List of specifications of files to be generated.
164 return self._specification["files"]
166 def set_input_state(self, job, build_nr, state):
167 """Set the state of input
176 for build in self._specification["input"]["builds"][job]:
177 if build["build"] == build_nr:
178 build["status"] = state
181 raise PresentationError("Build '{}' is not defined for job '{}'"
182 " in specification file.".
183 format(build_nr, job))
185 raise PresentationError("Job '{}' and build '{}' is not defined in "
186 "specification file.".format(job, build_nr))
188 def set_input_file_name(self, job, build_nr, file_name):
189 """Set the state of input
198 for build in self._specification["input"]["builds"][job]:
199 if build["build"] == build_nr:
200 build["file-name"] = file_name
203 raise PresentationError("Build '{}' is not defined for job '{}'"
204 " in specification file.".
205 format(build_nr, job))
207 raise PresentationError("Job '{}' and build '{}' is not defined in "
208 "specification file.".format(job, build_nr))
210 def _get_type_index(self, item_type):
211 """Get index of item type (environment, input, output, ...) in
212 specification YAML file.
214 :param item_type: Item type: Top level items in specification YAML file,
215 e.g.: environment, input, output.
217 :returns: Index of the given item type.
222 for item in self._cfg_yaml:
223 if item["type"] == item_type:
228 def _find_tag(self, text):
229 """Find the first tag in the given text. The tag is enclosed by the
230 TAG_OPENER and TAG_CLOSER.
232 :param text: Text to be searched.
234 :returns: The tag, or None if not found.
238 start = text.index(self.TAG_OPENER)
239 end = text.index(self.TAG_CLOSER, start + 1) + 1
240 return text[start:end]
244 def _replace_tags(self, data, src_data=None):
245 """Replace tag(s) in the data by their values.
247 :param data: The data where the tags will be replaced by their values.
248 :param src_data: Data where the tags are defined. It is dictionary where
249 the key is the tag and the value is the tag value. If not given, 'data'
251 :type data: str or dict
253 :returns: Data with the tags replaced.
255 :raises: PresentationError if it is not possible to replace the tag or
256 the data is not the supported data type (str, dict).
262 if isinstance(data, str):
263 tag = self._find_tag(data)
265 data = data.replace(tag, src_data[tag[1:-1]])
267 elif isinstance(data, dict):
269 for key, value in data.items():
270 tag = self._find_tag(value)
273 data[key] = value.replace(tag, src_data[tag[1:-1]])
276 raise PresentationError("Not possible to replace the "
277 "tag '{}'".format(tag))
279 self._replace_tags(data, src_data)
281 raise PresentationError("Replace tags: Not supported data type.")
285 def _parse_env(self):
286 """Parse environment specification in the specification YAML file.
289 logging.info("Parsing specification file: environment ...")
291 idx = self._get_type_index("environment")
296 self._specification["environment"]["configuration"] = \
297 self._cfg_yaml[idx]["configuration"]
299 self._specification["environment"]["configuration"] = None
302 self._specification["environment"]["paths"] = \
303 self._replace_tags(self._cfg_yaml[idx]["paths"])
305 self._specification["environment"]["paths"] = None
308 self._specification["environment"]["urls"] = \
309 self._replace_tags(self._cfg_yaml[idx]["urls"])
311 self._specification["environment"]["urls"] = None
314 self._specification["environment"]["make-dirs"] = \
315 self._cfg_yaml[idx]["make-dirs"]
317 self._specification["environment"]["make-dirs"] = None
320 self._specification["environment"]["remove-dirs"] = \
321 self._cfg_yaml[idx]["remove-dirs"]
323 self._specification["environment"]["remove-dirs"] = None
326 self._specification["environment"]["build-dirs"] = \
327 self._cfg_yaml[idx]["build-dirs"]
329 self._specification["environment"]["build-dirs"] = None
331 logging.info("Done.")
333 def _parse_debug(self):
334 """Parse debug specification in the specification YAML file.
337 if int(self.environment["configuration"]["CFG[DEBUG]"]) != 1:
340 logging.info("Parsing specification file: debug ...")
342 idx = self._get_type_index("debug")
344 self.environment["configuration"]["CFG[DEBUG]"] = 0
348 for key, value in self._cfg_yaml[idx]["general"].items():
349 self._specification["debug"][key] = value
351 self._specification["input"]["builds"] = dict()
352 for job, builds in self._cfg_yaml[idx]["builds"].items():
354 self._specification["input"]["builds"][job] = list()
356 self._specification["input"]["builds"][job].\
357 append({"build": build["build"],
358 "status": "downloaded",
359 "file-name": self._replace_tags(
361 self.environment["paths"])})
363 logging.warning("No build is defined for the job '{}'. "
364 "Trying to continue without it.".
368 raise PresentationError("No data to process.")
370 def _parse_input(self):
371 """Parse input specification in the specification YAML file.
373 :raises: PresentationError if there are no data to process.
376 logging.info("Parsing specification file: input ...")
378 idx = self._get_type_index("input")
380 raise PresentationError("No data to process.")
383 for key, value in self._cfg_yaml[idx]["general"].items():
384 self._specification["input"][key] = value
385 self._specification["input"]["builds"] = dict()
386 for job, builds in self._cfg_yaml[idx]["builds"].items():
388 self._specification["input"]["builds"][job] = list()
390 self._specification["input"]["builds"][job].\
391 append({"build": build, "status": None})
393 logging.warning("No build is defined for the job '{}'. "
394 "Trying to continue without it.".
397 raise PresentationError("No data to process.")
399 logging.info("Done.")
401 def _parse_output(self):
402 """Parse output specification in the specification YAML file.
404 :raises: PresentationError if there is no output defined.
407 logging.info("Parsing specification file: output ...")
409 idx = self._get_type_index("output")
411 raise PresentationError("No output defined.")
414 self._specification["output"] = self._cfg_yaml[idx]["format"]
416 raise PresentationError("No output defined.")
418 logging.info("Done.")
420 def _parse_static(self):
421 """Parse specification of the static content in the specification YAML
425 logging.info("Parsing specification file: static content ...")
427 idx = self._get_type_index("static")
429 logging.warning("No static content specified.")
431 for key, value in self._cfg_yaml[idx].items():
432 if isinstance(value, str):
434 self._cfg_yaml[idx][key] = self._replace_tags(
435 value, self._specification["environment"]["paths"])
439 self._specification["static"] = self._cfg_yaml[idx]
441 logging.info("Done.")
443 def _parse_elements(self):
444 """Parse elements (tables, plots) specification in the specification
448 logging.info("Parsing specification file: elements ...")
451 for element in self._cfg_yaml:
453 element["output-file"] = self._replace_tags(
454 element["output-file"],
455 self._specification["environment"]["paths"])
458 if element["type"] == "table":
459 logging.info(" {:3d} Processing a table ...".format(count))
461 element["template"] = self._replace_tags(
463 self._specification["environment"]["paths"])
466 self._specification["tables"].append(element)
468 elif element["type"] == "plot":
469 logging.info(" {:3d} Processing a plot ...".format(count))
470 self._specification["plots"].append(element)
472 elif element["type"] == "file":
473 logging.info(" {:3d} Processing a file ...".format(count))
475 element["dir-tables"] = self._replace_tags(
476 element["dir-tables"],
477 self._specification["environment"]["paths"])
480 self._specification["files"].append(element)
483 logging.info("Done.")
485 def read_specification(self):
486 """Parse specification in the specification YAML file.
488 :raises: PresentationError if an error occurred while parsing the
492 self._cfg_yaml = load(self._cfg_file)
493 except YAMLError as err:
494 raise PresentationError(msg="An error occurred while parsing the "
495 "specification file.",
504 self._parse_elements()
506 logging.debug("Specification: \n{}".
507 format(pformat(self._specification)))