diff options
Diffstat (limited to 'python/vyos/xml/definition.py')
-rw-r--r-- | python/vyos/xml/definition.py | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py new file mode 100644 index 000000000..28a8e2a0c --- /dev/null +++ b/python/vyos/xml/definition.py @@ -0,0 +1,294 @@ +# Copyright (C) 2020 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or modify it under the terms of +# the GNU Lesser General Public License as published by the Free Software Foundation; +# either version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along with this library; +# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +from vyos.xml import kw + +# As we index by key, the name is first and then the data: +# {'dummy': { +# '[node]': '[tagNode]', +# 'address': { ... } +# } } + +# so when we encounter a tagNode, we are really encountering +# the tagNode data. + + +class XML(dict): + def __init__(self): + self[kw.tree] = {} + self[kw.priorities] = {} + self[kw.owners] = {} + self[kw.default] = {} + self[kw.tags] = [] + + dict.__init__(self) + + self.tree = self[kw.tree] + # the options which matched the last incomplete world we had + # or the last word in a list + self.options = [] + # store all the part of the command we processed + self.inside = [] + # should we check the data pass with the constraints + self.check = False + # are we still typing a word + self.filling = False + # do what have the tagNode value ? + self.filled = False + # last word seen + self.word = '' + # do we have all the data we want ? + self.final = False + # do we have too much data ? + self.extra = False + # what kind of node are we in plain vs data not + self.plain = True + + def reset(self): + self.tree = self[kw.tree] + self.options = [] + self.inside = [] + self.check = False + self.filling = False + self.filled = False + self.word = '' + self.final = False + self.extra = False + self.plain = True + + # from functools import lru_cache + # @lru_cache(maxsize=100) + # XXX: need to use cachetool instead - for later + + def traverse(self, cmd): + self.reset() + + # using split() intead of split(' ') eats the final ' ' + words = cmd.split(' ') + passed = [] + word = '' + data_node = False + space = False + + while words: + word = words.pop(0) + space = word == '' + perfect = False + if word in self.tree: + passed = [] + perfect = True + self.tree = self.tree[word] + data_node = self.tree[kw.node] + self.inside.append(word) + word = '' + continue + if word and data_node: + passed.append(word) + + is_valueless = self.tree.get(kw.valueless, False) + is_leafNode = data_node == kw.leafNode + is_dataNode = data_node in (kw.leafNode, kw.tagNode) + named_options = [_ for _ in self.tree if not kw.found(_)] + + if is_leafNode: + self.final = is_valueless or len(passed) > 0 + self.extra = is_valueless and len(passed) > 0 + self.check = len(passed) >= 1 + else: + self.final = False + self.extra = False + self.check = len(passed) == 1 and not space + + if self.final: + self.word = ' '.join(passed) + else: + self.word = word + + if self.final: + self.filling = True + else: + self.filling = not perfect and bool(cmd and word != '') + + self.filled = self.final or (is_dataNode and len(passed) > 0 and word == '') + + if is_dataNode and len(passed) == 0: + self.options = [] + elif word: + if data_node != kw.plainNode or len(passed) == 1: + self.options = [_ for _ in self.tree if _.startswith(word)] + else: + self.options = [] + else: + self.options = named_options + + self.plain = not is_dataNode + + # self.debug() + + return self.word + + def speculate(self): + if len(self.options) == 1: + self.tree = self.tree[self.options[0]] + self.word = '' + if self.tree.get(kw.node,'') not in (kw.tagNode, kw.leafNode): + self.options = [_ for _ in self.tree if not kw.found(_)] + + def checks(self, cmd): + # as we move thought the named node twice + # the first time we get the data with the node + # and the second with the pass parameters + xml = self[kw.tree] + + words = cmd.split(' ') + send = True + last = [] + while words: + word = words.pop(0) + if word in xml: + xml = xml[word] + send = True + last = [] + continue + if xml[kw.node] in (kw.tagNode, kw.leafNode): + if kw.constraint in xml: + if send: + yield (word, xml[kw.constraint]) + send = False + else: + last.append((word, None)) + if len(last) >= 2: + yield last[0] + + def summary(self): + yield ('enter', '[ summary ]', str(self.inside)) + + if kw.help not in self.tree: + yield ('skip', '[ summary ]', str(self.inside)) + return + + if self.filled: + return + + yield('', '', '\nHelp:') + + if kw.help in self.tree: + summary = self.tree[kw.help].get(kw.summary) + values = self.tree[kw.help].get(kw.valuehelp, []) + if summary: + yield(summary, '', '') + for value in values: + yield(value[kw.format], value[kw.description], '') + + def constraint(self): + yield ('enter', '[ constraint ]', str(self.inside)) + + if kw.help in self.tree: + yield ('skip', '[ constraint ]', str(self.inside)) + return + if kw.error not in self.tree: + yield ('skip', '[ constraint ]', str(self.inside)) + return + if not self.word or self.filling: + yield ('skip', '[ constraint ]', str(self.inside)) + return + + yield('', '', '\nData Constraint:') + + yield('', 'constraint', str(self.tree[kw.error])) + + def listing(self): + yield ('enter', '[ listing ]', str(self.inside)) + + # only show the details when we passed the tagNode data + if not self.plain and not self.filled: + yield ('skip', '[ listing ]', str(self.inside)) + return + + yield('', '', '\nPossible completions:') + + options = list(self.tree.keys()) + options.sort() + for option in options: + if kw.found(option): + continue + if not option.startswith(self.word): + continue + inner = self.tree[option] + prefix = '+> ' if inner.get(kw.node, '') != kw.leafNode else ' ' + if kw.help in inner: + h = inner[kw.help] + yield (prefix + option, h.get(kw.summary), '') + + def debug(self): + print('------') + print("word '%s'" % self.word) + print("filling " + str(self.filling)) + print("filled " + str(self.filled)) + print("final " + str(self.final)) + print("extra " + str(self.extra)) + print("plain " + str(self.plain)) + print("options " + str(self.options)) + + # from functools import lru_cache + # @lru_cache(maxsize=100) + # XXX: need to use cachetool instead - for later + + def defaults(self, lpath): + d = self[kw.default] + for k in lpath: + d = d[k] + r = {} + + def _flatten(prefix, d, r): + for k in d: + if isinstance(d[k],dict): + key = f'{k}_' if not prefix else f'{prefix}{k}_' + _flatten(key, d[k], r) + continue + key = prefix + k + r[key.replace('-','_')] = d[k] + + _flatten('', d, r) + return r + + # from functools import lru_cache + # @lru_cache(maxsize=100) + # XXX: need to use cachetool instead - for later + + def _get(self, lpath, tag): + tree = self[kw.tree] + spath = lpath.copy() + while spath: + p = spath.pop(0) + if p not in tree: + return None + tree = tree[p] + if tree[kw.node] == kw.tagNode and spath: + spath.pop(0) + if tag not in tree: + print(f'not in tree {lpath} {tag}') + return tree.get(tag, None) + + def is_multi(self, lpath): + return self._get(lpath, kw.multi) is True + + def is_tag(self, lpath): + return self._get(lpath, kw.node) == kw.tagNode + + def is_leaf(self, lpath): + return self._get(lpath, kw.node) == kw.leafNode + + def exists(self, lpath): + return self._get(lpath, kw.node) is not None |