def metadata_gen(config, meta_config=None, auto_type=False, prop_map=None): """Automatically guess the metadata for an application configuration.""" if prop_map is None: prop_map = {} metomi.rose.macro.standard_format_config(config) if meta_config is None: meta_config = metomi.rose.config.ConfigNode() for keylist, node in config.walk(): sect = keylist[0] if len(keylist) == 1: option = None else: option = keylist[1] if sect in [metomi.rose.CONFIG_SECT_CMD]: continue if keylist == [ metomi.rose.CONFIG_SECT_TOP, metomi.rose.CONFIG_OPT_META_TYPE, ]: continue meta_sect = metomi.rose.macro.REC_ID_STRIP.sub("", sect) modifier_sect = metomi.rose.macro.REC_ID_STRIP_DUPL.sub("", sect) if sect and option is None: if (modifier_sect != meta_sect and modifier_sect != sect and meta_config.get([modifier_sect]) is None and auto_type): meta_config.set( [modifier_sect, metomi.rose.META_PROP_DUPLICATE], metomi.rose.META_PROP_VALUE_TRUE, ) if meta_config.get([meta_sect]) is not None: continue meta_config.set([meta_sect]) if meta_sect != sect and auto_type: # Add duplicate = true at base and modifier level (if needed). meta_config.set( [meta_sect, metomi.rose.META_PROP_DUPLICATE], metomi.rose.META_PROP_VALUE_TRUE, ) for prop_key, prop_value in prop_map.items(): meta_config.set([meta_sect, prop_key], prop_value) if option is None: continue meta_key = metomi.rose.macro.REC_ID_STRIP_DUPL.sub('', option) meta_opt = meta_sect + "=" + meta_key if meta_config.get([meta_opt]) is not None: continue meta_config.set([meta_opt]) for prop_key, prop_value in prop_map.items(): meta_config.set([meta_opt, prop_key], prop_value) if auto_type: opt_type, length = type_gen(node.value) if opt_type is not None: meta_config.set([meta_opt, metomi.rose.META_PROP_TYPE], opt_type) if int(length) > 1: meta_config.set([meta_opt, metomi.rose.META_PROP_LENGTH], length) return meta_config
def transform(self, config, meta_config=None): """Apply metadata trigger expressions to variables.""" self.reports = [] meta_config = self._load_meta_config(config, meta_config) self._setup_triggers(meta_config) self.enabled_dict = {} self.ignored_dict = {} enabled = metomi.rose.config.ConfigNode.STATE_NORMAL trig_ignored = metomi.rose.config.ConfigNode.STATE_SYST_IGNORED user_ignored = metomi.rose.config.ConfigNode.STATE_USER_IGNORED state_map = { enabled: 'enabled ', trig_ignored: 'trig-ignored', user_ignored: 'user-ignored', } id_list = [] prev_ignoreds = {trig_ignored: [], user_ignored: []} for keylist, node in config.walk(): if len(keylist) == 1: n_id = keylist[0] else: n_id = self._get_id_from_section_option(*keylist) id_list.append(n_id) if node.state in prev_ignoreds: prev_ignoreds[node.state].append(n_id) ranked_ids = self._get_ranked_trigger_ids() for _, var_id in sorted(ranked_ids): self.update(var_id, config, meta_config) # Report any discrepancies in ignored status. for var_id in id_list: section, option = self._get_section_option_from_id(var_id) node = config.get([section, option]) old, new = None, None if var_id in self.ignored_dict: node.state = trig_ignored if not any(var_id in v for v in prev_ignoreds.values()): old, new = state_map[enabled], state_map[trig_ignored] elif var_id in prev_ignoreds[trig_ignored]: node.state = enabled old, new = state_map[trig_ignored], state_map[enabled] elif (var_id in prev_ignoreds[user_ignored] and var_id in self._trigger_involved_ids): node.state = enabled old, new = state_map[user_ignored], state_map[enabled] if old != new: info = self.WARNING_STATE_CHANGED.format(old, new) if option is None: value = None else: value = node.value self.add_report(section, option, value, info) return config, self.reports
def rename_setting(self, config, keys, new_keys, info=None): """Rename a setting in the configuration. Args: config (metomi.rose.config.ConfigNode): The application configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. new_keys (list): The new hierarchy of node.value 'keys'. info (str): A short string containing no new lines, describing the addition of the setting. Returns: None """ section, option = self._get_section_option_from_keys(keys) new_section, new_option = self._get_section_option_from_keys(new_keys) if option is None: if new_option is not None: raise TypeError(self.ERROR_RENAME_SECT_TO_OPT.format( section, new_section, new_option)) elif new_option is None: raise TypeError(self.ERROR_RENAME_OPT_TO_SECT.format( section, option, new_section)) node = config.get(keys) if node is None: return if info is None: if option is None: info = self.INFO_RENAMED_SECT.format(section, new_section) else: info = self.INFO_RENAMED_VAR.format(section, option, new_section, new_option) if option is None: if config.get([new_section]) is not None: self.remove_setting(config, [new_section]) self.add_setting(config, [new_section], value=None, forced=True, state=node.state, comments=node.comments, info=info) for option_keys, opt_node in config.walk([section]): renamed_option = option_keys[1] self.add_setting(config, [new_section, renamed_option], value=opt_node.value, forced=True, state=opt_node.state, comments=opt_node.comments, info=info) else: self.add_setting(config, new_keys, value=node.value, forced=True, state=node.state, comments=node.comments, info=info) self.remove_setting(config, keys)
def annotate_config_with_metadata( config, meta_config, ignore_regexes=None, metadata_properties=None ): """Add metadata to the metomi.rose.config.ConfigNode.comments attribute. config -- a metomi.rose.config.ConfigNode instance, containing app or suite data. meta_config -- a metomi.rose.config.ConfigNode instance, containing metadata for config. ignore_regexes -- (default None) a list of uncompiled regular expressions - if a setting contains any of these, don't include it in the annotated output. """ if ignore_regexes is None: ignore_regexes = [] ignore_recs = [re.compile(_) for _ in ignore_regexes] unset_keys = [] for keylist, node in config.walk(): section = keylist[0] option = None if len(keylist) > 1: option = keylist[1] id_ = metomi.rose.macro.get_id_from_section_option(section, option) if any(_.search(id_) for _ in ignore_recs): unset_keys.append(keylist) continue metadata = metomi.rose.macro.get_metadata_for_config_id( id_, meta_config ) metadata_text = format_metadata_as_text( metadata, only_these_options=metadata_properties ) metadata_lines = [" " + line for line in metadata_text.splitlines()] node.comments = metadata_lines + node.comments for keylist in unset_keys: config.unset(keylist) return config
def remove_setting(self, config, keys, info=None): """Remove a setting from the configuration. Args: config (metomi.rose.config.ConfigNode): The application configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. info (string - optional): A short string containing no new lines, describing the addition of the setting. Returns: None """ section, option = self._get_section_option_from_keys(keys) if option is None: if config.get([section]) is None: return False option_node_pairs = config.walk([section]) for opt_keys, _ in option_node_pairs: opt = opt_keys[1] self._remove_setting(config, [section, opt], info) return self._remove_setting(config, [section, option], info)
def add_setting(self, config, keys, value=None, forced=False, state=None, comments=None, info=None): """Add a setting to the configuration. Args: config (metomi.rose.config.ConfigNode): The application configuration. keys (list): A list defining a hierarchy of node.value 'keys'. A section will be a list of one keys, an option will have two. value (string - optional): String denoting the new setting value. Required for options but not for settings. forced (bool - optional) If True override value if the setting already exists. state (str - optional): The state of the new setting - should be one of the ``rose.config.ConfigNode`` states e.g. ``rose.config.ConfigNode.STATE_USER_IGNORED``. Defaults to ``rose.config.ConfigNode.STATE_NORMAL``. comments (list - optional): List of comment lines (strings) for the new setting or ``None``. info (string - optional): A short string containing no new lines, describing the addition of the setting. Returns: None """ section, option = self._get_section_option_from_keys(keys) id_ = self._get_id_from_section_option(section, option) if option is not None and value is None: value = "" if info is None: if option is None: info = self.INFO_ADDED_SECT else: info = self.INFO_ADDED_VAR.format(repr(value)) # Search for existing conflicting settings. conflict_id = None found_setting = False if config.get([section, option]) is None: for key in config.get_value(): existing_section = key if not existing_section.startswith(section): continue existing_base_section = ( metomi.rose.macro.REC_ID_STRIP.sub("", existing_section)) if option is None: # For section 'foo', look for 'foo', 'foo{bar}', 'foo(1)'. found_setting = (existing_section == section or existing_base_section == section) else: # For 'foo=bar', don't allow sections 'foo(1)', 'foo{bar}'. found_setting = (existing_section != section and existing_base_section == section) if found_setting: conflict_id = existing_section break if option is not None: for keys, _ in config.walk([existing_section]): existing_option = keys[1] existing_base_option = ( metomi.rose.macro.REC_ID_STRIP_DUPL.sub( "", existing_option) ) # For option 'foo', look for 'foo', 'foo(1)'. if (existing_section == section and (existing_option == option or existing_base_option == option)): found_setting = True conflict_id = self._get_id_from_section_option( existing_section, existing_option) break if found_setting: break else: found_setting = True conflict_id = None # If already added, quit, unless "forced". if found_setting: if forced and (conflict_id is None or id_ == conflict_id): # If forced, override settings for an identical id. return self.change_setting_value( config, keys, value, state, comments, info) if conflict_id: self.add_report( section, option, value, self.WARNING_ADD_CLASH.format(id_, conflict_id), is_warning=True ) return False # Add parent section if missing. if option is not None and config.get([section]) is None: self.add_setting(config, [section]) if value is not None and not isinstance(value, str): text = "New value {0} for {1} is not a string" raise ValueError(text.format(repr(value), id_)) # Set (add) the section/option. config.set([section, option], value=value, state=state, comments=comments) self.add_report(section, option, value, info)