summaryrefslogtreecommitdiff
path: root/scripts/import-conf-mode-commands
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/import-conf-mode-commands')
-rwxr-xr-xscripts/import-conf-mode-commands255
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)