1from __future__ import absolute_import 2from operator import methodcaller, attrgetter 3from abc import ABCMeta, abstractmethod 4from copy import deepcopy 5import inspect 6import re 7import os 8 9from ciscoconfparse.ccp_util import junos_unsupported, UnsupportedFeatureWarning 10from ciscoconfparse.ccp_util import IPv4Obj 11 12r""" ccp_abc.py - Parse, Query, Build, and Modify IOS-style configurations 13 14 Copyright (C) 2020-2021 David Michael Pennington at Cisco Systems 15 Copyright (C) 2019 David Michael Pennington at ThousandEyes 16 Copyright (C) 2014-2019 David Michael Pennington at Samsung Data Services 17 18 This program is free software: you can redistribute it and/or modify 19 it under the terms of the GNU General Public License as published by 20 the Free Software Foundation, either version 3 of the License, or 21 (at your option) any later version. 22 23 This program is distributed in the hope that it will be useful, 24 but WITHOUT ANY WARRANTY; without even the implied warranty of 25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 26 GNU General Public License for more details. 27 28 You should have received a copy of the GNU General Public License 29 along with this program. If not, see <http://www.gnu.org/licenses/>. 30 31 If you need to contact the author, you can do so by emailing: 32 mike [~at~] pennington [/dot\] net 33""" 34 35## 36##------------- Config Line ABC 37## 38 39 40class BaseCfgLine(object): 41 __metaclass__ = ABCMeta 42 43 def __init__(self, text="", comment_delimiter="!"): 44 """Accept an IOS line number and initialize family relationship 45 attributes""" 46 self.comment_delimiter = comment_delimiter 47 self.text = text 48 self.linenum = -1 49 self.parent = self 50 self.child_indent = 0 51 self.is_comment = None 52 self.children = list() 53 self.oldest_ancestor = False 54 self.indent = 0 # Whitespace indentation on the object 55 self.confobj = None # Reference to the list object which owns it 56 self.feature = "" # Major feature description 57 self.feature_param1 = "" # Parameter1 of the feature 58 self.feature_param2 = "" # Parameter2 of the feature (if req'd) 59 60 self.set_comment_bool() 61 62 def __repr__(self): 63 if not self.is_child: 64 return "<%s # %s '%s'>" % (self.classname, self.linenum, self.text) 65 else: 66 return "<%s # %s '%s' (parent is # %s)>" % ( 67 self.classname, 68 self.linenum, 69 self.text, 70 self.parent.linenum, 71 ) 72 73 def __str__(self): 74 return self.__repr__() 75 76 def __hash__(self): 77 ## I inlined the hash() argument below for speed... whenever I change 78 ## self.__eq__() I *must* change this 79 return hash(str(self.linenum) + self.text) 80 81 def __gt__(self, val): 82 if self.linenum > val.linenum: 83 return True 84 return False 85 86 def __eq__(self, val): 87 try: 88 ## try / except is much faster than isinstance(); 89 ## I added hash_arg() inline below for speed... whenever I change 90 ## self.__hash__() I *must* change this 91 return (str(self.linenum) + self.text) == (str(val.linenum) + val.text) 92 except: 93 return False 94 95 def __lt__(self, val): 96 # Ref: http://stackoverflow.com/a/7152796/667301 97 if self.linenum < val.linenum: 98 return True 99 return False 100 101 def set_comment_bool(self): 102 delimiters = set(self.comment_delimiter) 103 retval = None 104 ## Use this instead of a regex... nontrivial speed enhancement 105 tmp = self.text.lstrip() 106 for delimit_char in delimiters: 107 if len(tmp) > 0 and (delimit_char == tmp[len(delimit_char) - 1]): 108 retval = True 109 break 110 else: 111 retval = False 112 self.is_comment = retval 113 return retval 114 115 @property 116 def dna(self): 117 return self.classname 118 119 @property 120 def hash_children(self): 121 """Return a unique hash of all children (if the number of children > 0)""" 122 if len(self.children) > 0: 123 return hash(tuple(self.children)) 124 else: 125 return 0 126 127 @property 128 def family_endpoint(self): 129 if self.children == []: 130 return 0 131 else: 132 return self.children[-1].linenum 133 134 @property 135 def verbose(self): 136 if self.has_children: 137 return ( 138 "<%s # %s '%s' (child_indent: %s / len(children): %s / family_endpoint: %s)>" 139 % ( 140 self.classname, 141 self.linenum, 142 self.text, 143 self.child_indent, 144 len(self.children), 145 self.family_endpoint, 146 ) 147 ) 148 else: 149 return "<%s # %s '%s' (no_children / family_endpoint: %s)>" % ( 150 self.classname, 151 self.linenum, 152 self.text, 153 self.family_endpoint, 154 ) 155 156 @property 157 def all_parents(self): 158 retval = set([]) 159 me = self 160 while me.parent != me: 161 retval.add(me.parent) 162 me = me.parent 163 return sorted(retval) 164 165 @property 166 def all_children(self): 167 retval = set([]) 168 if self.has_children: 169 for child in self.children: 170 retval.add(child) 171 retval.update(child.all_children) 172 return sorted(retval) 173 174 @property 175 def classname(self): 176 return self.__class__.__name__ 177 178 @property 179 def has_children(self): 180 if len(self.children) > 0: 181 return True 182 return False 183 184 @property 185 def is_config_line(self): 186 """Return a boolean for whether this is a config statement; returns False if this object is a blank line, or a comment""" 187 if len(self.text.strip()) > 0 and not self.is_comment: 188 return True 189 return False 190 191 def _list_reassign_linenums(self): 192 # Call this when I want to reparse everything 193 # (which is very slow) 194 195 # NOTE - 1.5.30 removed this method (which was only called 196 # by confobj.delete()) in favor of a simpler approach 197 # in confobj.delete() 198 # 199 # def _list_reassign_linenums(self): 200 # self.confobj._reassign_linenums() 201 raise NotImplementedError() 202 203 @junos_unsupported 204 def add_parent(self, parentobj): 205 """Add a reference to parentobj, on this object""" 206 ## In a perfect world, I would check parentobj's type 207 ## with isinstance(), but I'm not ready to take the perf hit 208 self.parent = parentobj 209 return True 210 211 @junos_unsupported 212 def add_child(self, childobj): 213 """Add references to childobj, on this object""" 214 ## In a perfect world, I would check childobj's type 215 ## with isinstance(), but I'm not ready to take the perf hit 216 ## 217 ## Add the child, unless we already know it 218 if not (childobj in self.children): 219 self.children.append(childobj) 220 self.child_indent = childobj.indent 221 return True 222 else: 223 return False 224 225 @junos_unsupported 226 def add_uncfgtext(self, unconftext): 227 """unconftext is defined during special method calls. Do not assume it 228 is automatically populated.""" 229 ## remove any preceeding "no " 230 conftext = re.sub(r"\s*no\s+", "", unconftext) 231 myindent = self.parent.child_indent 232 self.uncfgtext = myindent * " " + "no " + conftext 233 234 @junos_unsupported 235 def delete(self, recurse=True): 236 """Delete this object. By default, if a parent object is deleted, the child objects are also deleted; this happens because ``recurse`` defaults True. 237 """ 238 if recurse: 239 # NOTE - 1.5.30 changed this from iterating over self.children 240 # to self.all_children 241 #for child in self.children: 242 for child in sorted(self.all_children, reverse=True): 243 child.delete() 244 245 ## Consistency check to refuse deletion of the wrong object... 246 ## only delete if the line numbers are consistent 247 text = self.text 248 linenum = self.linenum 249 if self.confobj._list[self.linenum].text == text: 250 del self.confobj._list[self.linenum] 251 252 # renumber remaining objects after this deletion... 253 # 254 # NOTE 1.5.30 removed self._list_reassign_linenums() to speed up 255 # obj.delete() behavior... instead we just iterate through 256 # the list of remaining objects and renumber them 257 # 258 #self._list_reassign_linenums() 259 for obj in self.confobj._list[self.linenum:]: 260 obj.linenum = linenum 261 linenum += 1 262 263 @junos_unsupported 264 def delete_children_matching(self, linespec): 265 """Delete any child :class:`~models_cisco.IOSCfgLine` objects which 266 match ``linespec``. 267 268 Parameters 269 ---------- 270 linespec : str 271 A string or python regular expression, which should be matched. 272 273 Returns 274 ------- 275 list 276 A list of :class:`~models_cisco.IOSCfgLine` objects which were deleted. 277 278 Examples 279 -------- 280 This example illustrates how you can use 281 :func:`~ccp_abc.delete_children_matching` to delete any description 282 on an interface. 283 284 .. code-block:: python 285 :emphasize-lines: 16 286 287 >>> from ciscoconfparse import CiscoConfParse 288 >>> config = [ 289 ... '!', 290 ... 'interface Serial1/0', 291 ... ' description Some lame description', 292 ... ' ip address 1.1.1.1 255.255.255.252', 293 ... '!', 294 ... 'interface Serial1/1', 295 ... ' description Another lame description', 296 ... ' ip address 1.1.1.5 255.255.255.252', 297 ... '!', 298 ... ] 299 >>> parse = CiscoConfParse(config) 300 >>> 301 >>> for obj in parse.find_objects(r'^interface'): 302 ... obj.delete_children_matching(r'description') 303 >>> 304 >>> for line in parse.ioscfg: 305 ... print(line) 306 ... 307 ! 308 interface Serial1/0 309 ip address 1.1.1.1 255.255.255.252 310 ! 311 interface Serial1/1 312 ip address 1.1.1.5 255.255.255.252 313 ! 314 >>> 315 """ 316 cobjs = filter(methodcaller("re_search", linespec), self.children) 317 retval = map(attrgetter("text"), cobjs) 318 # Delete the children 319 map(methodcaller("delete"), cobjs) 320 return retval 321 322 def has_child_with(self, linespec): 323 return bool(filter(methodcaller("re_search", linespec), self.children)) 324 325 @junos_unsupported 326 def insert_before(self, insertstr): 327 """Usage: 328 confobj.insert_before('! insert text before this confobj')""" 329 retval = None 330 331 calling_fn_index = 1 332 calling_filename = inspect.stack()[calling_fn_index].filename 333 calling_function = inspect.stack()[calling_fn_index].function 334 calling_lineno = inspect.stack()[calling_fn_index].lineno 335 error = "FATAL CALL: in %s line %s %s(insertstr='%s')" % (calling_filename, calling_lineno, calling_function, insertstr) 336 if isinstance(insertstr, str) is True: 337 retval = self.confobj.insert_before(self, insertstr, atomic=False) 338 339 elif isinstance(insertstr, IOSCfgLine) is True: 340 retval = self.confobj.insert_before(self, insertstr.text, atomic=False) 341 else: 342 raise ValueError(error) 343 #retval = self.confobj.insert_after(self, insertstr, atomic=False) 344 return retval 345 346 @junos_unsupported 347 def insert_after(self, insertstr): 348 """Usage: 349 confobj.insert_after('! insert text after this confobj')""" 350 351 retval = None 352 353 calling_fn_index = 1 354 calling_filename = inspect.stack()[calling_fn_index].filename 355 calling_function = inspect.stack()[calling_fn_index].function 356 calling_lineno = inspect.stack()[calling_fn_index].lineno 357 error = "FATAL CALL: in %s line %s %s(insertstr='%s')" % (calling_filename, calling_lineno, calling_function, insertstr) 358 if isinstance(insertstr, str) is True: 359 retval = self.confobj.insert_after(self, insertstr, atomic=False) 360 361 elif isinstance(insertstr, IOSCfgLine) is True: 362 retval = self.confobj.insert_after(self, insertstr.text, atomic=False) 363 else: 364 raise ValueError(error) 365 366 #retval = self.confobj.insert_after(self, insertstr, atomic=False) 367 return retval 368 369 @junos_unsupported 370 def append_to_family( 371 self, insertstr, indent=-1, auto_indent_width=1, auto_indent=False 372 ): 373 """Append an :class:`~models_cisco.IOSCfgLine` object with ``insertstr`` 374 as a child at the bottom of the current configuration family. 375 376 Parameters 377 ---------- 378 insertstr : str 379 A string which contains the text configuration to be apppended. 380 indent : int 381 The amount of indentation to use for the child line; by default, the number of left spaces provided with ``insertstr`` are respected. However, you can manually set the indent level when ``indent``>0. This option will be ignored, if ``auto_indent`` is True. 382 auto_indent_width : int 383 Amount of whitespace to automatically indent 384 auto_indent : bool 385 Automatically indent the child to ``auto_indent_width`` 386 387 Returns 388 ------- 389 str 390 The text matched by the regular expression group; if there is no match, None is returned. 391 392 Examples 393 -------- 394 This example illustrates how you can use 395 :func:`~ccp_abc.append_to_family` to add a 396 ``carrier-delay`` to each interface. 397 398 .. code-block:: python 399 :emphasize-lines: 14 400 401 >>> from ciscoconfparse import CiscoConfParse 402 >>> config = [ 403 ... '!', 404 ... 'interface Serial1/0', 405 ... ' ip address 1.1.1.1 255.255.255.252', 406 ... '!', 407 ... 'interface Serial1/1', 408 ... ' ip address 1.1.1.5 255.255.255.252', 409 ... '!', 410 ... ] 411 >>> parse = CiscoConfParse(config) 412 >>> 413 >>> for obj in parse.find_objects(r'^interface'): 414 ... obj.append_to_family(' carrier-delay msec 500') 415 ... 416 >>> parse.commit() 417 >>> 418 >>> for line in parse.ioscfg: 419 ... print(line) 420 ... 421 ! 422 interface Serial1/0 423 ip address 1.1.1.1 255.255.255.252 424 carrier-delay msec 500 425 ! 426 interface Serial1/1 427 ip address 1.1.1.5 255.255.255.252 428 carrier-delay msec 500 429 ! 430 >>> 431 """ 432 ## Build the string to insert with proper indentation... 433 if auto_indent: 434 insertstr = (" " * (self.indent + auto_indent_width)) + insertstr.lstrip() 435 elif indent > 0: 436 insertstr = (" " * (self.indent + indent)) + insertstr.lstrip() 437 438 ## BaseCfgLine.append_to_family(), insert a single line after this 439 ## object's children 440 try: 441 last_child = self.all_children[-1] 442 retval = self.confobj.insert_after(last_child, insertstr, atomic=False) 443 except IndexError: 444 # The object has no children 445 retval = self.confobj.insert_after(self, insertstr, atomic=False) 446 447 return retval 448 449 @junos_unsupported 450 def replace(self, linespec, replacestr, ignore_rgx=None): 451 """Replace all strings matching ``linespec`` with ``replacestr`` in 452 the :class:`~models_cisco.IOSCfgLine` object; however, if the 453 :class:`~models_cisco.IOSCfgLine` text matches ``ignore_rgx``, then 454 the text is *not* replaced. The ``replace()`` method is simply an 455 alias to the ``re_sub()`` method. 456 457 Parameters 458 ---------- 459 linespec : str 460 A string or python regular expression, which should be matched 461 replacestr : str 462 A string or python regular expression, which should replace the text matched by ``linespec``. 463 ignore_rgx : str 464 A string or python regular expression; the replacement is skipped if :class:`~models_cisco.IOSCfgLine` text matches ``ignore_rgx``. ``ignore_rgx`` defaults to None, which means no lines matching ``linespec`` are skipped. 465 466 Returns 467 ------- 468 str 469 The new text after replacement 470 471 Examples 472 -------- 473 This example illustrates how you can use 474 :func:`~models_cisco.IOSCfgLine.replace` to replace ``Serial1`` with 475 ``Serial0`` in a configuration... 476 477 .. code-block:: python 478 :emphasize-lines: 15 479 480 >>> from ciscoconfparse import CiscoConfParse 481 >>> config = [ 482 ... '!', 483 ... 'interface Serial1/0', 484 ... ' ip address 1.1.1.1 255.255.255.252', 485 ... '!', 486 ... 'interface Serial1/1', 487 ... ' ip address 1.1.1.5 255.255.255.252', 488 ... '!', 489 ... ] 490 >>> parse = CiscoConfParse(config) 491 >>> 492 >>> for obj in parse.find_objects('Serial'): 493 ... print("OLD {}".format(obj.text)) 494 ... obj.replace(r'Serial1', r'Serial0') 495 ... print(" NEW {}".format(obj.text)) 496 OLD interface Serial1/0 497 NEW interface Serial0/0 498 OLD interface Serial1/1 499 NEW interface Serial0/1 500 >>> 501 """ 502 503 # This is a little slower than calling BaseCfgLine.re_sub directly... 504 return self.re_sub(linespec, replacestr, ignore_rgx) 505 506 def re_sub(self, regex, replacergx, ignore_rgx=None): 507 """Replace all strings matching ``linespec`` with ``replacestr`` in the :class:`~models_cisco.IOSCfgLine` object; however, if the :class:`~models_cisco.IOSCfgLine` text matches ``ignore_rgx``, then the text is *not* replaced. 508 509 Parameters 510 ---------- 511 regex : str 512 A string or python regular expression, which should be matched. 513 replacergx : str 514 A string or python regular expression, which should replace the text matched by ``regex``. 515 ignore_rgx : str 516 A string or python regular expression; the replacement is skipped if :class:`~models_cisco.IOSCfgLine` text matches ``ignore_rgx``. ``ignore_rgx`` defaults to None, which means no lines matching ``regex`` are skipped. 517 518 519 Returns 520 ------- 521 str 522 The new text after replacement 523 524 Examples 525 -------- 526 This example illustrates how you can use 527 :func:`~models_cisco.IOSCfgLine.re_sub` to replace ``Serial1`` with 528 ``Serial0`` in a configuration... 529 530 .. code-block:: python 531 :emphasize-lines: 15 532 533 >>> from ciscoconfparse import CiscoConfParse 534 >>> config = [ 535 ... '!', 536 ... 'interface Serial1/0', 537 ... ' ip address 1.1.1.1 255.255.255.252', 538 ... '!', 539 ... 'interface Serial1/1', 540 ... ' ip address 1.1.1.5 255.255.255.252', 541 ... '!', 542 ... ] 543 >>> parse = CiscoConfParse(config) 544 >>> 545 >>> for obj in parse.find_objects('Serial'): 546 ... print("OLD {}".format(obj.text)) 547 ... obj.re_sub(r'Serial1', r'Serial0') 548 ... print(" NEW {}".format(obj.text)) 549 OLD interface Serial1/0 550 NEW interface Serial0/0 551 OLD interface Serial1/1 552 NEW interface Serial0/1 553 >>> 554 """ 555 # When replacing objects, check whether they should be deleted, or 556 # whether they are a comment 557 558 if ignore_rgx and re.search(ignore_rgx, self.text): 559 return self.text 560 561 retval = re.sub(regex, replacergx, self.text) 562 # Delete empty lines 563 if retval.strip() == "": 564 self.delete() 565 return 566 self.text = retval 567 self.set_comment_bool() 568 return retval 569 570 def re_match(self, regex, group=1, default=""): 571 r"""Use ``regex`` to search the :class:`~models_cisco.IOSCfgLine` text and return the regular expression group, at the integer index. 572 573 Parameters 574 ---------- 575 regex : str 576 A string or python regular expression, which should be matched. This regular expression should contain parenthesis, which bound a match group. 577 group : int 578 An integer which specifies the desired regex group to be returned. ``group`` defaults to 1. 579 default : str 580 The default value to be returned, if there is no match. By default an empty string is returned if there is no match. 581 582 Returns 583 ------- 584 str 585 The text matched by the regular expression group; if there is no match, ``default`` is returned. 586 587 Examples 588 -------- 589 This example illustrates how you can use 590 :func:`~models_cisco.IOSCfgLine..re_match` to store the mask of the 591 interface which owns "1.1.1.5" in a variable called ``netmask``. 592 593 .. code-block:: python 594 :emphasize-lines: 14 595 596 >>> from ciscoconfparse import CiscoConfParse 597 >>> config = [ 598 ... '!', 599 ... 'interface Serial1/0', 600 ... ' ip address 1.1.1.1 255.255.255.252', 601 ... '!', 602 ... 'interface Serial1/1', 603 ... ' ip address 1.1.1.5 255.255.255.252', 604 ... '!', 605 ... ] 606 >>> parse = CiscoConfParse(config) 607 >>> 608 >>> for obj in parse.find_objects(r'ip\saddress'): 609 ... netmask = obj.re_match(r'1\.1\.1\.5\s(\S+)') 610 >>> 611 >>> print("The netmask is", netmask) 612 The netmask is 255.255.255.252 613 >>> 614 """ 615 mm = re.search(regex, self.text) 616 if not (mm is None): 617 return mm.group(group) 618 return default 619 620 def re_search(self, regex, default=""): 621 """Use ``regex`` to search this :class:`~models_cisco.IOSCfgLine`'s 622 text. 623 624 Parameters 625 ---------- 626 regex : str 627 A string or python regular expression, which should be matched. 628 default : str 629 A value which is returned if :func:`~ccp_abc.re_search()` doesn't find a match while looking for ``regex``. 630 631 Returns 632 ------- 633 str 634 The :class:`~models_cisco.IOSCfgLine` text which matched. If there is no match, ``default`` is returned. 635 636 """ 637 ## TODO: use re.escape(regex) on all regex, instead of bare regex 638 mm = re.search(regex, self.text) 639 if not (mm is None): 640 return self.text 641 return default 642 643 def re_search_children(self, regex, recurse=False): 644 """Use ``regex`` to search the text contained in the children of 645 this :class:`~models_cisco.IOSCfgLine`. 646 647 Parameters 648 ---------- 649 regex : str 650 A string or python regular expression, which should be matched. 651 recurse : bool 652 Set True if you want to search all children (children, grand children, great grand children, etc...) 653 654 Returns 655 ------- 656 list 657 A list of matching :class:`~models_cisco.IOSCfgLine` objects which matched. If there is no match, an empty :py:func:`list` is returned. 658 659 """ 660 if recurse is False: 661 return [cobj for cobj in self.children if cobj.re_search(regex)] 662 else: 663 return [cobj for cobj in self.all_children if cobj.re_search(regex)] 664 665 def re_match_typed( 666 self, regex, group=1, untyped_default=False, result_type=str, default="" 667 ): 668 r"""Use ``regex`` to search the :class:`~models_cisco.IOSCfgLine` text 669 and return the contents of the regular expression group, at the 670 integer ``group`` index, cast as ``result_type``; if there is no match, 671 ``default`` is returned. 672 673 Parameters 674 ---------- 675 regex : str 676 A string or python regular expression, which should be matched. This regular expression should contain parenthesis, which bound a match group. 677 group : int 678 An integer which specifies the desired regex group to be returned. ``group`` defaults to 1. 679 result_type : type 680 A type (typically one of: ``str``, ``int``, ``float``, or ``IPv4Obj``). All returned values are cast as ``result_type``, which defaults to ``str``. 681 default : any 682 The default value to be returned, if there is no match. 683 untyped_default : bool 684 Set True if you don't want the default value to be typed 685 686 Returns 687 ------- 688 ``result_type`` 689 The text matched by the regular expression group; if there is no match, ``default`` is returned. All values are cast as ``result_type``, unless `untyped_default` is True. 690 691 Examples 692 -------- 693 This example illustrates how you can use 694 :func:`~models_cisco.IOSCfgLine.re_match_typed` to build an 695 association between an interface name, and its numerical slot value. 696 The name will be cast as :py:func:`str`, and the slot will be cast as 697 :py:func:`int`. 698 699 .. code-block:: python 700 :emphasize-lines: 15,16,17,18,19 701 702 >>> from ciscoconfparse import CiscoConfParse 703 >>> config = [ 704 ... '!', 705 ... 'interface Serial1/0', 706 ... ' ip address 1.1.1.1 255.255.255.252', 707 ... '!', 708 ... 'interface Serial2/0', 709 ... ' ip address 1.1.1.5 255.255.255.252', 710 ... '!', 711 ... ] 712 >>> parse = CiscoConfParse(config) 713 >>> 714 >>> slots = dict() 715 >>> for obj in parse.find_objects(r'^interface'): 716 ... name = obj.re_match_typed(regex=r'^interface\s(\S+)', 717 ... default='UNKNOWN') 718 ... slot = obj.re_match_typed(regex=r'Serial(\d+)', 719 ... result_type=int, 720 ... default=-1) 721 ... print("Interface {0} is in slot {1}".format(name, slot)) 722 ... 723 Interface Serial1/0 is in slot 1 724 Interface Serial2/0 is in slot 2 725 >>> 726 727 """ 728 mm = re.search(regex, self.text) 729 if not (mm is None): 730 if not (mm.group(group) is None): 731 return result_type(mm.group(group)) 732 733 if untyped_default: 734 return default 735 else: 736 return result_type(default) 737 738 def re_match_iter_typed( 739 self, 740 regex, 741 group=1, 742 result_type=str, 743 default="", 744 untyped_default=False, 745 recurse=False, 746 ): 747 r"""Use ``regex`` to search the children of 748 :class:`~models_cisco.IOSCfgLine` text and return the contents of 749 the regular expression group, at the integer ``group`` index, cast as 750 ``result_type``; if there is no match, ``default`` is returned. 751 752 Parameters 753 ---------- 754 regex : str 755 A string or python compiled regular expression, which should be matched. This regular expression should contain parenthesis, which bound a match group. 756 group : int 757 An integer which specifies the desired regex group to be returned. ``group`` defaults to 1. 758 result_type : type 759 A type (typically one of: ``str``, ``int``, ``float``, or :class:`~ccp_util.IPv4Obj`). All returned values are cast as ``result_type``, which defaults to ``str``. 760 default : any 761 The default value to be returned, if there is no match. 762 recurse : bool 763 Set True if you want to search all children (children, grand children, great grand children, etc...) 764 untyped_default : bool 765 Set True if you don't want the default value to be typed 766 767 Returns 768 ------- 769 ``result_type`` 770 The text matched by the regular expression group; if there is no match, ``default`` is returned. All values are cast as ``result_type``, unless `untyped_default` is True. 771 772 Notes 773 ----- 774 This loops through the children (in order) and returns when the regex hits its first match. 775 776 Examples 777 -------- 778 This example illustrates how you can use 779 :func:`~models_cisco.IOSCfgLine.re_match_iter_typed` to build an 780 :func:`~ccp_util.IPv4Obj` address object for each interface. 781 782 >>> import re 783 >>> from ciscoconfparse import CiscoConfParse 784 >>> from ciscoconfparse.ccp_util import IPv4Obj 785 >>> config = [ 786 ... '!', 787 ... 'interface Serial1/0', 788 ... ' ip address 1.1.1.1 255.255.255.252', 789 ... '!', 790 ... 'interface Serial2/0', 791 ... ' ip address 1.1.1.5 255.255.255.252', 792 ... '!', 793 ... ] 794 >>> parse = CiscoConfParse(config) 795 >>> INTF_RE = re.compile(r'interface\s\S+') 796 >>> ADDR_RE = re.compile(r'ip\saddress\s(\S+\s+\S+)') 797 >>> for obj in parse.find_objects(INTF_RE): 798 ... print("{} {}".format(obj.text, obj.re_match_iter_typed(ADDR_RE, result_type=IPv4Obj))) 799 interface Serial1/0 <IPv4Obj 1.1.1.1/30> 800 interface Serial2/0 <IPv4Obj 1.1.1.5/30> 801 >>> 802 """ 803 ## iterate through children, and return the matching value 804 ## (cast as result_type) from the first child.text that matches regex 805 806 # if (default is True): 807 ## Not using self.re_match_iter_typed(default=True), because I want 808 ## to be sure I build the correct API for match=False 809 ## 810 ## Ref IOSIntfLine.has_dtp for an example of how to code around 811 ## this while I build the API 812 # raise NotImplementedError 813 814 if recurse is False: 815 for cobj in self.children: 816 mm = re.search(regex, cobj.text) 817 if not (mm is None): 818 return result_type(mm.group(group)) 819 ## Ref Github issue #121 820 if untyped_default: 821 return default 822 else: 823 return result_type(default) 824 else: 825 for cobj in self.all_children: 826 mm = re.search(regex, cobj.text) 827 if not (mm is None): 828 return result_type(mm.group(group)) 829 ## Ref Github issue #121 830 if untyped_default: 831 return default 832 else: 833 return result_type(default) 834 835 def reset(self): 836 # For subclass APIs 837 raise NotImplementedError 838 839 def build_reset_string(self): 840 # For subclass APIs 841 raise NotImplementedError 842 843 @property 844 def ioscfg(self): 845 """Return a list with this the text of this object, and 846 with all children in the direct line.""" 847 retval = [self.text] 848 retval.extend(list(map(attrgetter("text"), self.all_children))) 849 return retval 850 851 @property 852 def lineage(self): 853 """Iterate through to the oldest ancestor of this object, and return 854 a list of all ancestors / children in the direct line. Cousins or 855 aunts / uncles are *not* returned. Note: all children of this 856 object are returned.""" 857 retval = self.all_parents 858 retval.append(self) 859 if self.children: 860 retval.extend(self.all_children) 861 return sorted(retval) 862 863 @property 864 def geneology(self): 865 """Iterate through to the oldest ancestor of this object, and return 866 a list of all ancestors in the direct line as well as this obj. 867 Cousins or aunts / uncles are *not* returned. Note: children of this 868 object are *not* returned.""" 869 retval = sorted(self.all_parents) 870 retval.append(self) 871 return retval 872 873 @property 874 def geneology_text(self): 875 """Iterate through to the oldest ancestor of this object, and return 876 a list of all ancestors in the direct line as well as this obj. 877 Cousins or aunts / uncles are *not* returned. Note: children of this 878 object are *not* returned.""" 879 retval = map(lambda x: x.text, sorted(self.all_parents)) 880 retval.append(self.text) 881 return retval 882 883 @property 884 def is_parent(self): 885 return bool(self.has_children) 886 887 @property 888 def is_child(self): 889 return not bool(self.parent == self) 890 891 @property 892 def siblings(self): 893 indent = self.indent 894 return [obj for obj in self.parent.children if (obj.indent == indent)] 895 896 @classmethod 897 def is_object_for(cls, line=""): 898 return False 899