Ejemplo n.º 1
0
Archivo: base.py Proyecto: jkrysl/fmf
 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")
Ejemplo n.º 2
0
 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))
Ejemplo n.º 3
0
    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)