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