diff options
Diffstat (limited to 'scripts/import-conf-mode-commands')
-rwxr-xr-x | scripts/import-conf-mode-commands | 255 |
1 files changed, 255 insertions, 0 deletions
diff --git a/scripts/import-conf-mode-commands b/scripts/import-conf-mode-commands new file mode 100755 index 000000000..996b31c9c --- /dev/null +++ b/scripts/import-conf-mode-commands @@ -0,0 +1,255 @@ +#!/usr/bin/env python3 +# +# build-command-template: converts old style commands definitions to XML +# +# Copyright (C) 2019 VyOS maintainers <maintainers@vyos.net> +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 +# USA + + +import os +import re +import sys + +from lxml import etree + + +# Node types +NODE = 0 +LEAF_NODE = 1 +TAG_NODE = 2 + +def parse_command_data(t): + regs = { + 'help': r'\bhelp:(.*)(?:\n|$)', + 'priority': r'\bpriority:(.*)(?:\n|$)', + 'type': r'\btype:(.*)(?:\n|$)', + 'syntax_expression_var': r'\bsyntax:expression: \$VAR\(\@\) in (.*)' + } + + data = {'multi': False, 'help': ""} + + for r in regs: + try: + data[r] = re.search(regs[r], t).group(1).strip() + except: + data[r] = None + + # val_help is special: there can be multiple instances + val_help_strings = re.findall(r'\bval_help:(.*)(?:\n|$)', t) + val_help = [] + for v in val_help_strings: + try: + fmt, msg = re.match(r'\s*(.*)\s*;\s*(.*)\s*(?:\n|$)', v).groups() + except: + fmt = "<text>" + msg = v + val_help.append((fmt, msg)) + data['val_help'] = val_help + + # multi is on/off + if re.match(r'\bmulti:', t): + data['multi'] = True + + return(data) + +def walk(tree, base_path, name): + path = os.path.join(base_path, name) + + contents = os.listdir(path) + + # Determine node type and create XML element for the node + # Tag node dirs will always have 'node.tag' subdir and 'node.def' file + # Leaf node dirs have nothing but a 'node.def' file + # Everything that doesn't match either of these patterns is a normal node + if 'node.tag' in contents: + print("Creating a tag node from {0}".format(path)) + elem = etree.Element('tagNode') + node_type = TAG_NODE + elif contents == ['node.def']: + print("Creating a leaf node from {0}".format(path)) + elem = etree.Element('leafNode') + node_type = LEAF_NODE + else: + print("Creating a node from {0}".format(path)) + elem = etree.Element('node') + node_type = NODE + + # Read and parse the command definition data (the 'node.def' file) + with open(os.path.join(path, 'node.def'), 'r') as f: + node_def = f.read() + data = parse_command_data(node_def) + + # Import the data into the properties element + props_elem = etree.Element('properties') + + if data['priority']: + # Priority values sometimes come with comments that explain the value choice + try: + prio, prio_comment = re.match(r'\s*(\d+)\s*#(.*)', data['priority']).groups() + except: + prio = data['priority'].strip() + prio_comment = None + prio_elem = etree.Element('priority') + prio_elem.text = prio + props_elem.append(prio_elem) + if prio_comment: + prio_comment_elem = etree.Comment(prio_comment) + props_elem.append(prio_comment_elem) + + if data['multi']: + multi_elem = etree.Element('multi') + props_elem.append(multi_elem) + + if data['help']: + help_elem = etree.Element('help') + help_elem.text = data['help'] + props_elem.append(help_elem) + + # For leaf nodes, absense of a type: tag means they take no values + # For any other nodes, it doesn't mean anything + if not data['type'] and (node_type == LEAF_NODE): + valueless = etree.Element('valueless') + props_elem.append(valueless) + + # There can be only one constraint element in the definition + # Create it now, we'll modify it in the next two cases, then append + constraint_elem = etree.Element('constraint') + has_constraint = False + + # Add regexp field for multiple options + if data['syntax_expression_var']: + regex = etree.Element('regex') + constraint_error=etree.Element('constraintErrorMessage') + values = re.search(r'(.+) ; (.+)', data['syntax_expression_var']).group(1) + message = re.search(r'(.+) ; (.+)', data['syntax_expression_var']).group(2) + values = re.findall(r'\"(.+?)\"', values) + regex.text = '|'.join(values) + constraint_error.text = re.sub('\".*?VAR.*?\"', '', message) + constraint_error.text = re.sub(r'[\"|\\]', '', message) + constraint_elem.append(regex) + props_elem.append(constraint_elem) + props_elem.append(constraint_error) + + if data['val_help']: + for vh in data['val_help']: + vh_elem = etree.Element('valueHelp') + + vh_fmt_elem = etree.Element('format') + # Many commands use special "u32:<start>-<end>" format for ranges + if re.match(r'u32:', vh[0]): + vh_fmt = re.match(r'u32:(.*)', vh[0]).group(1).strip() + + # If valid range of values is specified in val_help, we can automatically + # create a constraint for it + # Extracting it from syntax:expression: would be much more complicated + vh_validator = etree.Element('validator') + vh_validator.set("name", "numeric") + vh_validator.set("argument", "--range {0}".format(vh_fmt)) + constraint_elem.append(vh_validator) + has_constraint = True + else: + vh_fmt = vh[0] + vh_fmt_elem.text = vh_fmt + + vh_help_elem = etree.Element('description') + vh_help_elem.text = vh[1] + + vh_elem.append(vh_fmt_elem) + vh_elem.append(vh_help_elem) + props_elem.append(vh_elem) + + # Translate the "type:" to the new validator system + if data['type']: + t = data['type'] + if t == 'txt': + # Can't infer anything from the generic "txt" type + pass + else: + validator = etree.Element('validator') + if t == 'u32': + validator.set('name', 'numeric') + validator.set('argument', '--non-negative') + elif t == 'ipv4': + validator.set('name', 'ipv4-address') + elif t == 'ipv4net': + validator.set('name', 'ipv4-prefix') + elif t == 'ipv6': + validator.set('name', 'ipv6-address') + elif t == 'ipv6net': + validator.set('name', 'ipv6-prefix') + elif t == 'macaddr': + validator.set('name', 'mac-address') + else: + print("Warning: unsupported type \'{0}\'".format(t)) + validator = None + + if (validator is not None) and (not has_constraint): + # If has_constraint is true, it means a more specific validator + # was already extracted from another option + constraint_elem.append(validator) + has_constraint = True + + if has_constraint: + props_elem.append(constraint_elem) + + elem.append(props_elem) + + elem.set("name", name) + + if node_type != LEAF_NODE: + children = etree.Element('children') + + # Create the next level dir path, + # accounting for the "virtual" node.tag subdir for tag nodes + next_level = path + if node_type == TAG_NODE: + next_level = os.path.join(path, 'node.tag') + + # Walk the subdirs of the next level + for d in os.listdir(next_level): + dp = os.path.join(next_level, d) + if os.path.isdir(dp): + walk(children, next_level, d) + + elem.append(children) + + tree.append(elem) + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("Usage: {0} <base path>".format(sys.argv[0])) + sys.exit(1) + else: + base_path = sys.argv[1] + + root = etree.Element('interfaceDefinition') + contents = os.listdir(base_path) + elem = etree.Element('node') + elem.set('name', os.path.basename(base_path)) + children = etree.Element('children') + + for c in contents: + path = os.path.join(base_path, c) + if os.path.isdir(path): + walk(children, base_path, c) + + elem.append(children) + root.append(elem) + + xml_data = etree.tostring(root, pretty_print=True).decode() + with open('output.xml', 'w') as f: + f.write(xml_data) |