Skip to content

Commit

Permalink
Modify BaseCfgLine().append_to_family() behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
mpenning committed Nov 30, 2023
1 parent 064ed00 commit 19bc3c5
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 38 deletions.
132 changes: 117 additions & 15 deletions ciscoconfparse/ccp_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class BaseCfgLine(metaclass=ABCMeta):
parent = None
child_indent = 0
is_comment = None
children = []
_children = []
indent = 0 # assign indent in the self.text setter method
confobj = None # Reference to the list object which owns it
blank_line_keep = False # CiscoConfParse() uses blank_line_keep
Expand Down Expand Up @@ -72,11 +72,11 @@ def __init__(self, all_lines=None, line=DEFAULT_TEXT, comment_delimiter="!", **k
self.comment_delimiter = comment_delimiter
self._uncfgtext_to_be_deprecated = ""
self._text = DEFAULT_TEXT
self._children = []
self.linenum = -1
self.parent = self # by default, assign parent as itself
self.child_indent = 0
self.is_comment = None
self.children = []
self.indent = 0 # assign indent in the self.text setter method
self.confobj = None # Reference to the list object which owns it
self.blank_line_keep = False # CiscoConfParse() uses blank_line_keep
Expand Down Expand Up @@ -172,6 +172,27 @@ def __lt__(self, val):
return True
return False

@property
@logger.catch(reraise=True)
def children(self):
if isinstance(self._children, list):
return self._children
else:
error = f"Fatal: {type(self._children)} found as BaseCfgLine().children; it should be a list."
logger.critical(error)
raise NotImplementedError(error)

@children.setter
@logger.catch(reraise=True)
def children(self, arg):
if isinstance(arg, list):
self._children = arg
return self._children
else:
error = f"{type(arg)} cannot be assigned to BaseCfgLine().children"
logger.critical(error)
raise NotImplementedError(error)

# On BaseCfgLine()
@property
@logger.catch(reraise=True)
Expand Down Expand Up @@ -397,6 +418,10 @@ def text(self):
@logger.catch(reraise=True)
def text(self, newtext=None):
"""Set self.text, self.indent, self.line_id (and all comments' self.parent)"""
# Convert a config line to a string if this is a BaseCfgLine()...
if isinstance(newtext, BaseCfgLine):
newtext = newtext.text

# FIXME - children do not associate correctly if this is used as-is...
if not isinstance(newtext, str):
error = f"text=`{newtext}` is an invalid config line"
Expand Down Expand Up @@ -852,11 +877,23 @@ def insert_after(self, insertstr=None):

# On BaseCfgLine()
@junos_unsupported
@logger.catch(reraise=True)
def append_to_family(
self, insertstr, indent=-1, auto_indent_width=1, auto_indent=False
):
"""Append an :class:`~models_cisco.IOSCfgLine` object with ``insertstr``
as a child at the bottom of the current configuration family.
as a child at the top of the current configuration family.
``insertstr`` is inserted at the top of the family to ensure there are no
unintended object relationships created during the change. As an example,
it is possible that the last child is a grandchild (instead of a child) and
a simple append after that grandchild is risky to always get ``insertstr``
indent level correct. However, we always know the correct indent for a
child of this object.
If auto_indent is True, add ``insertstr`` with the correct left-indent
level automatically.
Parameters
----------
insertstr : str
Expand Down Expand Up @@ -909,38 +946,103 @@ def append_to_family(
!
>>>
"""

if auto_indent is True and indent > 0:
error = "indent and auto_indent are not supported together."
logger.error(error)
raise NotImplementedError(error)

if self.confobj is None:
error = "Cannot insert on a None BaseCfgLine().confobj"
logger.critical(error)
raise NotImplementedError(error)

insertstr_parent_indent = self.indent

# Build the string to insert with proper indentation...
if auto_indent:
insertstr = (" " * (self.indent + auto_indent_width)) + insertstr.lstrip()
insertstr = (" " * (insertstr_parent_indent + auto_indent_width)) + insertstr.lstrip()
elif indent > 0:
insertstr = (" " * (self.indent + indent)) + insertstr.lstrip()
else:
insertstr = insertstr.lstrip()
# do not modify insertstr
pass

# BaseCfgLine.append_to_family(), insert a single line after this
# object's children
# object...
this_obj = type(self)
newobj = this_obj(line=insertstr)
newobj_parent = self.find_parent_for(insertstr)
if isinstance(newobj_parent, BaseCfgLine):
try:
# find index of the new parent and return...
_idx = self.confobj.ConfigObjs.index(newobj_parent) - 1
retval = self.confobj.ConfigObjs.insert(_idx, newobj)
return retval
except BaseException as eee:
raise eee
else:
retval = self.confobj.ConfigObjs.insert(self.linenum + 1, newobj)
return retval
error = f"""Cannot append to family since this line has no parent with a lower indent: '''{insertstr}'''"""
logger.critical(error)
raise NotImplementedError(error)

# On BaseCfgLine()
@junos_unsupported
@logger.catch(reraise=True)
def find_parent_for(self, val):
"""Search all children of this object and return the most appropriate parent object for the indent associated with ``val``"""
if isinstance(val, str):
pass
elif isinstance(val, BaseCfgLine):
pass
else:
error = f"val must be a str or instance of BaseCfgLine(); however, BaseCfgLine().find_parent_for() got {type(val)}"
logger.critical(error)
raise NotImplementedError(error)

indent_dict = {}
for obj in sorted(self.all_children):
indent_dict[obj.indent] = obj
indent_dict[self.indent] = self

try:
last_child = self.all_children[-1]
retval = self.confobj.insert_after(last_child, insertstr, atomic=False)
except IndexError:
# The object has no children
retval = self.confobj.insert_after(self, insertstr, atomic=False)
return retval
val_indent = len(val) - len(val.lstrip())
if isinstance(val_indent, int):
for obj_indent in sorted(indent_dict.keys(), reverse=False):
if obj_indent > val_indent:
return indent_dict[obj_indent]
else:
error = f"Could not find indent for {val}"
logger.critical(error)
raise TypeError(error)
except Exception as eee:
logger.critical(str(eee))
raise eee

return self

# On BaseCfgLine()
@logger.catch(reraise=True)
def rstrip(self):
"""Implement rstrip() on the BaseCfgLine().text"""
"""Implement rstrip() on the BaseCfgLine().text; manually call CiscoConfParse().commit() after the rstrip()"""
self._text = self._text.rstrip()
return self._text

# On BaseCfgLine()
@logger.catch(reraise=True)
def lstrip(self):
"""Implement lstrip() on the BaseCfgLine().text"""
"""Implement lstrip() on the BaseCfgLine().text; manually call CiscoConfParse().commit() after the lstrip()"""
self._text = self._text.lstrip()
return self._text

# On BaseCfgLine()
@logger.catch(reraise=True)
def strip(self):
"""Implement strip() on the BaseCfgLine().text"""
"""Implement strip() on the BaseCfgLine().text; manually call CiscoConfParse().commit() after the strip()"""
self._text = self._text.strip()
return self._text

# On BaseCfgLine()
@junos_unsupported
Expand Down
43 changes: 31 additions & 12 deletions ciscoconfparse/ciscoconfparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,12 @@ def openargs(self):
retval = {"mode": "rU", "encoding": self.encoding}
return retval

# This method is on CiscoConfParse()
@property
@logger.catch(reraise=True)
def text(self):
return self.ioscfg

# This method is on CiscoConfParse()
@property
@logger.catch(reraise=True)
Expand Down Expand Up @@ -3290,7 +3296,14 @@ def count(self, val):
# This method is on ConfigList()
@ logger.catch(reraise=True)
def index(self, val, *args):
return self._list.index(val, *args)
try:
return self._list.index(val, *args)
except ValueError:
error = f"{val} is not in this ConfigList()"
logger.error(error)
raise ValueError(error)
except BaseException as eee:
raise eee

# This method is on ConfigList()
@ logger.catch(reraise=True)
Expand Down Expand Up @@ -3550,18 +3563,24 @@ def insert_after(self, exist_val=None, new_val=None, atomic=False, new_val_inden
#self._bootstrap_from_text()
self._list = self.bootstrap_obj_init_ng(self.ioscfg)
else:
## Just renumber lines...
# Just renumber lines...
self.reassign_linenums()

# This method is on ConfigList()
@ junos_unsupported
@ logger.catch(reraise=True)
def insert(self, ii, val):
if not isinstance(ii, int):
raise ValueError
error = f"The ConfigList() index must be an integer, but ConfigList().insert() got {type(ii)}"
logger.critical(error)
raise ValueError(error)

# Get the configuration line text if val is a BaseCfgLine() instance
if isinstance(val, BaseCfgLine):
val = val.text

# Coerce a string into the appropriate object
if getattr(val, "capitalize", False):
if isinstance(val, str):
if self.factory:
obj = config_line_factory(
text=val,
Expand All @@ -3576,18 +3595,18 @@ def insert(self, ii, val):
)

else:
err_txt = 'insert() cannot insert "{}"'.format(val)
logger.error(err_txt)
raise ValueError(err_txt)
error = f'''insert() cannot insert {type(val)} "{val}" with factory={self.factory}'''
logger.critical(error)
raise ValueError(error)
else:
err_txt = 'insert() cannot insert "{}"'.format(val)
logger.error(err_txt)
raise ValueError(err_txt)
error = f'''insert() cannot insert {type(val)} "{val}"'''
logger.critical(error)
raise TypeError(error)

## Insert something at index ii
# Insert the object at index ii
self._list.insert(ii, obj)

## Just renumber lines...
# Just renumber lines...
self.reassign_linenums()

# This method is on ConfigList()
Expand Down
Loading

0 comments on commit 19bc3c5

Please sign in to comment.