diff --git a/hier_config/base.py b/hier_config/base.py index f0f1d73..44ec367 100644 --- a/hier_config/base.py +++ b/hier_config/base.py @@ -287,6 +287,42 @@ def rebuild_children_dict(self) -> None: for child in self.children: self.children_dict.setdefault(child.text, child) + def delete_all_children(self) -> None: + """Delete all children""" + self.children.clear() + self.rebuild_children_dict() + + def unified_diff(self, target: Union[HConfig, HConfigChild]) -> Iterator[str]: + """ + provides a similar output to difflib.unified_diff() + + In its current state, this algorithm does not consider duplicate child differences. + e.g. two instances `endif` in an IOS-XR route-policy. It also does not respect the + order of commands where it may count, such as in ACLs. In the case of ACLs, they + should contain sequence numbers if order is important. + """ + # if a self child is missing from the target "- self_child.text" + for self_child in self.children: + self_iter = iter((f"{self_child.indentation}{self_child.text}",)) + if target_child := target.children_dict.get(self_child.text, None): + found = self_child.unified_diff(target_child) + if peek := next(found, None): + yield from chain(self_iter, (peek,), found) + else: + yield f"{self_child.indentation}- {self_child.text}" + yield from ( + f"{c.indentation}- {c.text}" + for c in self_child.all_children_sorted() + ) + # if a target child is missing from self "+ target_child.text" + for target_child in target.children: + if target_child.text not in self.children_dict: + yield f"{target_child.indentation}+ {target_child.text}" + yield from ( + f"{c.indentation}+ {c.text}" + for c in target_child.all_children_sorted() + ) + def _future( self, config: Union[HConfig, HConfigChild], @@ -313,13 +349,13 @@ def _future( elif self_child := config_child.idempotent_for(self.children): future_config.add_deep_copy_of(config_child) negated_or_recursed.add(self_child.text) - # The config_child being applied is already in self + # config_child is already in self elif self_child := self.get_child("equals", config_child.text): future_child = future_config.add_shallow_copy_of(self_child) # pylint: disable=protected-access self_child._future(config_child, future_child) negated_or_recursed.add(config_child.text) - # The a child is being negated + # config_child is being negated elif config_child.text.startswith(self._negation_prefix): unnegated_command = config_child.text[len(self._negation_prefix) :] if self.get_child("equals", unnegated_command): @@ -327,50 +363,17 @@ def _future( # Account for "no ..." commands in the running config else: future_config.add_shallow_copy_of(config_child) + # config_child is not in self and doesn't match a special case else: future_config.add_deep_copy_of(config_child) for self_child in self.children: + # self_child matched an above special case and should be ignored if self_child.text in negated_or_recursed: continue + # self_child was not modified above and should be present in the future config future_config.add_deep_copy_of(self_child) - def delete_all_children(self) -> None: - """Delete all children""" - self.children.clear() - self.rebuild_children_dict() - - def unified_diff(self, target: Union[HConfig, HConfigChild]) -> Iterator[str]: - """ - provides a similar output to difflib.unified_diff() - - In its current state, this algorithm does not consider duplicate child differences. - e.g. two instances `endif` in an IOS-XR route-policy. It also does not respect the - order of commands where it may count, such as in ACLs. In the case of ACLs, they - should contain sequence numbers if order is important. - """ - # if a self child is missing from the target "- self_child.text" - for self_child in self.children: - self_iter = iter((f"{self_child.indentation}{self_child.text}",)) - if target_child := target.children_dict.get(self_child.text, None): - found = self_child.unified_diff(target_child) - if peek := next(found, None): - yield from chain(self_iter, (peek,), found) - else: - yield f"{self_child.indentation}- {self_child.text}" - yield from ( - f"{c.indentation}- {c.text}" - for c in self_child.all_children_sorted() - ) - # if a target child is missing from self "+ target_child.text" - for target_child in target.children: - if target_child.text not in self.children_dict: - yield f"{target_child.indentation}+ {target_child.text}" - yield from ( - f"{c.indentation}+ {c.text}" - for c in target_child.all_children_sorted() - ) - def _with_tags( self, tags: Set[str], new_instance: Union[HConfig, HConfigChild] ) -> Union[HConfig, HConfigChild]: