def _initialize(self, path): """ Find metadata tree root, detect format version """ # Find the tree root root = os.path.abspath(path) try: while ".fmf" not in next(os.walk(root))[1]: if root == "/": raise utils.RootError( "Unable to find tree root for '{0}'.".format( os.path.abspath(path))) root = os.path.abspath(os.path.join(root, os.pardir)) except StopIteration: raise utils.FileError("Invalid directory path: {0}".format(root)) log.info("Root directory found: {0}".format(root)) self.root = root # Detect format version try: with open(os.path.join(self.root, ".fmf", "version")) as version: self.version = int(version.read()) log.info("Format version detected: {0}".format(self.version)) except IOError as error: raise utils.FormatError( "Unable to detect format version: {0}".format(error)) except ValueError: raise utils.FormatError("Invalid version format")
def update(self, data): """ Update metadata, handle virtual hierarchy """ # Nothing to do if no data if data is None: return for key, value in sorted(data.items()): # Ensure there are no 'None' keys if key is None: raise utils.FormatError("Invalid key 'None'.") # Handle child attributes if key.startswith('/'): name = key.lstrip('/') # Handle deeper nesting (e.g. keys like /one/two/three) by # extracting only the first level of the hierarchy as name match = re.search("([^/]+)(/.*)", name) if match: name = match.groups()[0] value = {match.groups()[1]: value} # Update existing child or create a new one self.child(name, value) # Update regular attributes else: self.data[key] = value log.debug("Data for '{0}' updated.".format(self)) log.data(pretty(self.data))
def adjust(self, context, key='adjust', undecided='skip'): """ Adjust tree data based on provided context and rules The 'context' should be an instance of the fmf.context.Context class describing the environment context. By default, the key 'adjust' of each node is inspected for possible rules that should be applied. Provide 'key' to use a custom key instead. Optional 'undecided' parameter can be used to specify what should happen when a rule condition cannot be decided because context dimension is not defined. By default, such rules are skipped. In order to raise the fmf.context.CannotDecide exception in such cases use undecided='raise'. """ # Check context sanity if not isinstance(context, fmf.context.Context): raise utils.GeneralError("Invalid adjust context: '{}'.".format( type(context).__name__)) # Adjust rules should be a dictionary or a list of dictionaries try: rules = copy.deepcopy(self.data[key]) log.debug("Applying adjust rules for '{}'.".format(self)) log.data(rules) if isinstance(rules, dict): rules = [rules] if not isinstance(rules, list): raise utils.FormatError( "Invalid adjust rule format in '{}'. " "Should be a dictionary or a list of dictionaries, " "got '{}'.".format(self.name, type(rules).__name__)) except KeyError: rules = [] # Check and apply each rule for rule in rules: # Rule must be a dictionary if not isinstance(rule, dict): raise utils.FormatError("Adjust rule should be a dictionary.") # There must be a condition defined try: condition = rule.pop('when') except KeyError: raise utils.FormatError("No condition defined in adjust rule.") # The optional 'continue' key should be a bool continue_ = rule.pop('continue', True) if not isinstance(continue_, bool): raise utils.FormatError("The 'continue' value should be bool, " "got '{}'.".format(continue_)) # The 'because' key is reserved for optional comments (ignored) rule.pop('because', None) # Apply remaining rule attributes if context matches try: if context.matches(condition): self._merge_special(self.data, rule) # First matching rule wins, skip the rest unless continue if not continue_: break # Handle undecided rules as requested except fmf.context.CannotDecide: if undecided == 'skip': continue elif undecided == 'raise': raise else: raise utils.GeneralError( "Invalid value for the 'undecided' parameter. Should " "be 'skip' or 'raise', got '{}'.".format(undecided)) # Adjust all child nodes as well for child in self.children.values(): child.adjust(context, key, undecided)