''' generate json with all commands from xml for vyos documentation coverage ''' import sys import os import json import re import logging import datetime from io import BytesIO from lxml import etree as ET import shutil default_constraint_err_msg = "Invalid value" validator_dir = "" input_data = [ { "kind": "cfgcmd", "input_dir": "_include/vyos-1x/interface-definitions/", "schema_file": "_include/vyos-1x/schema/interface_definition.rng", "files": [] }, { "kind": "opcmd", "input_dir": "_include/vyos-1x/op-mode-definitions/", "schema_file": "_include/vyos-1x/schema/op-mode-definition.rng", "files": [] } ] vyos_commands_dir = "_include/coverage" node_data = { 'cfgcmd': {}, 'opcmd': {}, } def get_vyos_commands(): return_data = None for (dirpath, dirnames, filenames) in os.walk(vyos_commands_dir): for file in filenames: with open(f"{vyos_commands_dir}/{file}") as f: data = json.load(f) if not return_data: return_data = data # find latestes export if datetime.datetime.fromisoformat(return_data['date']) < datetime.datetime.fromisoformat(data['date']): return_data = data return return_data def get_properties(p): props = {} props['valueless'] = False try: if p.find("valueless") is not None: props['valueless'] = True except: pass if p is None: return props # Get the help string try: props["help"] = p.find("help").text except: pass # Get value help strings try: vhe = p.findall("valueHelp") vh = [] for v in vhe: vh.append( (v.find("format").text, v.find("description").text) ) props["val_help"] = vh except: props["val_help"] = [] # Get the constraint statements error_msg = default_constraint_err_msg # Get the error message if it's there try: error_msg = p.find("constraintErrorMessage").text except: pass vce = p.find("constraint") vc = [] if vce is not None: # The old backend doesn't support multiple validators in OR mode # so we emulate it regexes = [] regex_elements = vce.findall("regex") if regex_elements is not None: regexes = list(map(lambda e: e.text.strip(), regex_elements)) if "" in regexes: print("Warning: empty regex, node will be accepting any value") validator_elements = vce.findall("validator") validators = [] if validator_elements is not None: for v in validator_elements: v_name = os.path.join(validator_dir, v.get("name")) # XXX: lxml returns None for empty arguments v_argument = None try: v_argument = v.get("argument") except: pass if v_argument is None: v_argument = "" validators.append("{0} {1}".format(v_name, v_argument)) regex_args = " ".join(map(lambda s: "--regex \\\'{0}\\\'".format(s), regexes)) validator_args = " ".join(map(lambda s: "--exec \\\"{0}\\\"".format(s), validators)) validator_script = '${vyos_libexec_dir}/validate-value.py' validator_string = "exec \"{0} {1} {2} --value \\\'$VAR(@)\\\'\"; \"{3}\"".format(validator_script, regex_args, validator_args, error_msg) props["constraint"] = validator_string # Get the completion help strings try: che = p.findall("completionHelp") ch = "" for c in che: scripts = c.findall("script") paths = c.findall("path") lists = c.findall("list") # Current backend doesn't support multiple allowed: tags # so we get to emulate it comp_exprs = [] for i in lists: comp_exprs.append("echo \"{0}\"".format(i.text)) for i in paths: comp_exprs.append("/bin/cli-shell-api listNodes {0}".format(i.text)) for i in scripts: comp_exprs.append("sh -c \"{0}\"".format(i.text)) comp_help = " && ".join(comp_exprs) props["comp_help"] = comp_help except: props["comp_help"] = [] # Get priority try: props["priority"] = p.find("priority").text except: pass # Get "multi" if p.find("multi") is not None: props["multi"] = True # Get "valueless" if p.find("valueless") is not None: props["valueless"] = True return props def process_node(n, f): props_elem = n.find("properties") children = n.find("children") command = n.find("command") children_nodes = [] owner = n.get("owner") node_type = n.tag defaultvalue = n.find("defaultValue") if defaultvalue is not None: defaultvalue = defaultvalue.text name = n.get("name") props = get_properties(props_elem) if node_type != "node": if "valueless" not in props.keys(): props["type"] = "txt" if node_type == "tagNode": props["tag"] = "True" if node_type == "node" and children is not None: inner_nodes = children.iterfind("*") index_child = 0 for inner_n in inner_nodes: children_nodes.append(process_node(inner_n, f)) index_child = index_child + 1 if node_type == "tagNode" and children is not None: inner_nodes = children.iterfind("*") index_child = 0 for inner_n in inner_nodes: children_nodes.append(process_node(inner_n, f)) index_child = index_child + 1 else: # This is a leaf node pass if command is not None: test_command = True else: test_command = False node = { 'name': name, 'type': node_type, 'children': children_nodes, 'props': props, 'command': test_command, 'filename': f, 'defaultvalue': defaultvalue } return node def create_commands(data, parent_list=[], level=0): result = [] command = { 'name': [], 'help': None, 'tag_help': [], 'level': level, 'no_childs': False, 'filename': None, 'defaultvalue': None, } command['filename'] = data['filename'] command['defaultvalue'] = data['defaultvalue'] command['name'].extend(parent_list) command['name'].append(data['name']) if data['type'] == 'tagNode': command['name'].append("<" + data['name'] + ">") if 'val_help' in data['props'].keys(): for val_help in data['props']['val_help']: command['tag_help'].append(val_help) if len(data['children']) == 0: command['no_childs'] = True if data['command']: command['no_childs'] = True try: help_text = data['props']['help'] command['help'] = re.sub(r"[\n\t]*", "", help_text) except: command['help'] = "" command['valueless'] = data['props']['valueless'] if 'children' in data.keys(): children_bool = True for child in data['children']: result.extend(create_commands(child, command['name'], level + 1)) if command['no_childs']: result.append(command) return result def include_file(line, input_dir): string = "" if "#include 1: override_element(v) def override_element(l: list): if len(l) < 2: return # assemble list of leafNodes of overriding defaultValues, for later removal parents = [] for el in l[1:]: parents.append(el.getparent()) # replace element with final override l[0].getparent().replace(l[0], l[-1]) # remove all but overridden element for el in parents: el.getparent().remove(el) if __name__ == "__main__": get_vyos_commands()