diff options
Diffstat (limited to 'tools/argv_translator.py')
| -rwxr-xr-x | tools/argv_translator.py | 958 |
1 files changed, 958 insertions, 0 deletions
diff --git a/tools/argv_translator.py b/tools/argv_translator.py new file mode 100755 index 0000000000..5325969fdf --- /dev/null +++ b/tools/argv_translator.py @@ -0,0 +1,958 @@ +#!/usr/bin/env python + +""" +Usage: + + argv_translator.py rebuild-defuns [<text>] + +Help: + rebuild-defuns : foo + +""" + +# This script did different things at different times as we migrated code +# to quentin's parse engine. The following were its rebuild-defuns phases: +# +# - originally was used to change all of the argv[2] to argv[4]->arg. This +# calculated the new argv index and added the ->arg. +# - next it was used to replace the 4 in argv[4]->arg with an idx_foo variable +# +# idx-logic +# - used to take a command string and build out an idx_ logic skeleton +# + +import argparse +import re +import sys +import os +import subprocess +from collections import OrderedDict +from copy import deepcopy +from pprint import pformat, pprint +from network_docopt import NetworkDocopt, get_network_docopt_info + + +def token_is_variable(line_number, token): + + if token.isdigit(): + return True + + if token.startswith('('): + assert token.endswith(')') or token.endswith(')...'), "%d: token %s should end with )" % (line_number, token) + return True + + if token.startswith('['): + assert token.endswith(']'), "%d: token %s should end with ]" % (line_number, token) + return True + + if token.startswith('<'): + assert token.endswith('>'), "%d: token %s should end with >" % (line_number, token) + return True + + if token.startswith('{'): + # I don't really care about checking for this I just put + # these asserts in here to bug sharpd + assert token.endswith('}'), "%d: token %s should end with }" % (line_number, token) + return True + + assert '|' not in token, "%d: Weird token %s has a | but does not start with [ or (" % (line_number, token) + + if token in ('WORD', + '.LINE', + '.AA:NN', + 'A.B.C.D', + 'A.B.C.D/M', + 'X:X::X:X', + 'X:X::X:X/M', + 'ASN:nn_or_IP-address:nn'): + return True + + # Anything in all caps in a variable + if token.upper() == token: + return True + + re_number_range = re.search('^<\d+-\d+>$', token) + if re_number_range: + return True + + return False + + +def line_to_tokens(line_number, text): + """ + Most of the time whitespace can be used to split tokens + (set|clear) <interface> clagd-enable (no|yes) + + tokens + - (set|clear) + - <interface> + - clagd-enable + - (no|yes) + + But if we are dealing with multiword keywords, such as "soft in", that approach + does not work. We can only split on whitespaces if we are not inside a () or [] + bgp (<ipv4>|<ipv6>|<interface>|*) [soft in|soft out] + + tokens: + - bgp + - (<ipv4>|<ipv6>|<interface>|*) + - [soft in|soft out] + """ + tokens = [] + token_index = 0 + token_text = [] + parens = 0 + curlys = 0 + brackets = 0 + less_greater = 0 + + for char in text: + if char == ' ': + if parens == 0 and brackets == 0 and curlys == 0 and less_greater == 0: + if token_text: + tokens.append(''.join(token_text)) + token_index += 1 + token_text = [] + else: + token_text.append(char) + else: + if char == '(': + parens += 1 + + elif char == ')': + parens -= 1 + + elif char == '[': + brackets += 1 + + elif char == ']': + brackets -= 1 + + elif char == '{': + curlys += 1 + + elif char == '}': + curlys -= 1 + + elif char == '<': + less_greater += 1 + + elif char == '>': + less_greater -= 1 + + if char: + token_text.append(char) + + if token_text: + tokens.append(''.join(token_text)) + + return tokens + + +''' +# No longer used now that all indexes have been updated +def get_argv_translator(line_number, line): + table = {} + line = line.strip() + assert line.startswith('"'), "%d: line does not start with \"\n%s" % (line_number, line) + assert line.endswith('",'), "%d: line does not end with \",\n%s" % (line_number, line) + + line = line[1:-2] + + funky_chars = ('+', '"') + for char in funky_chars: + if char in line: + raise Exception("%d: Add support for tokens in\n%s\n\nsee BGP_INSTANCE_CMD down below" % (line_number, line)) + + old_style_index = 0 + for (token_index, token) in enumerate(line_to_tokens(line)): + if not token: + continue + + if token_is_variable(line_number, token): + # print "%s is a token" % token + table[old_style_index] = token_index + old_style_index += 1 + else: + # print "%s is NOT a token" % token + pass + + return table +''' + +def get_command_string_variable_indexes(line_number, line): + indexes = {} + + line = line.strip() + assert line.startswith('"'), "%d: line does not start with \"\n%s" % (line_number, line) + assert line.endswith('",'), "%d: line does not end with \",\n%s" % (line_number, line) + line = line[1:-2] + max_index = 0 + + for (token_index, token) in enumerate(line_to_tokens(line_number, line)): + if not token: + raise Exception("%d: empty token" % line_number) + + if token_is_variable(line_number, token): + # print "%s is a token" % token + indexes[token_index] = True + max_index = token_index + + return (max_index, indexes) + + +def get_token_index_variable_name(line_number, token): + + re_range = re.search('\(\d+-\d+\)', token) + + if token.startswith('['): + assert token.endswith(']'), "Token %s should end with ]" % token + token = token[1:-1] + + if token.startswith('<'): + assert token.endswith('>'), "Token %s should end with >" % token + token = token[1:-1] + + if token == 'A.B.C.D': + return 'idx_ipv4' + + elif token == 'A.B.C.D/M': + return 'idx_ipv4_prefixlen' + + elif token == 'X:X::X:X': + return 'idx_ipv6' + + elif token == 'X:X::X:X/M': + return 'idx_ipv6_prefixlen' + + elif token == 'ASN:nn_or_IP-address:nn': + return 'idx_ext_community' + + elif token == '.AA:NN': + return 'idx_community' + + elif token == 'WORD': + return 'idx_word' + + elif token == 'json': + return 'idx_json' + + elif token == '.LINE': + return 'idx_regex' + + elif token == 'A.B.C.D|INTERFACE': + return 'idx_ipv4_ifname' + + elif token == 'A.B.C.D|INTERFACE|null0': + return 'idx_ipv4_ifname_null' + + elif token == 'X:X::X:X|INTERFACE': + return 'idx_ipv6_ifname' + + elif token == 'reject|blackhole': + return 'idx_reject_blackhole' + + elif token == 'route-map NAME': + return 'idx_route_map' + + elif token == 'recv|send|detail': + return 'idx_recv_send' + + elif token == 'recv|send': + return 'idx_recv_send' + + elif token == 'up|down': + return 'idx_up_down' + + elif token == 'off-link': + return 'idx_off_link' + + elif token == 'no-autoconfig': + return 'idx_no_autoconfig' + + elif token == 'router-address': + return 'idx_router_address' + + elif token == 'high|medium|low': + return 'idx_high_medium_low' + + elif token == '(0-4294967295)|infinite': + return 'idx_number_infinite' + + elif token == '(1-199)|(1300-2699)|WORD': + return 'idx_acl' + + elif token == 'A.B.C.D|X:X::X:X': + return 'idx_ip' + + elif token == 'in|out': + return 'idx_in_out' + + elif token == 'deny|permit': + return 'idx_permit_deny' + + elif token == 'view|vrf': + return 'idx_view_vrf' + + elif token == 'unicast|multicast': + return 'idx_safi' + + elif token == 'bestpath|multipath': + return 'idx_bestpath' + + elif token == 'egp|igp|incomplete': + return 'idx_origin' + + elif token == 'cisco|zebra' or token == 'cisco|ibm|shortcut|standard': + return 'idx_vendor' + + elif token == 'as-set|no-as-set': + return 'idx_as_set' + + elif token == 'confed|missing-as-worst': + return 'idx_med_knob' + + elif token == 'both|send|receive' or token == 'send|recv': + return 'idx_send_recv' + + elif token == 'both|extended|standard' or token == '1|2': + return 'idx_type' + + elif token == 'A.B.C.D|WORD' or token == 'A.B.C.D/M|WORD': + return 'idx_ipv4_word' + + elif token == 'advertise-queue|advertised-routes|packet-queue': + return 'idx_type' + + elif token == 'ospf|table': + return 'idx_ospf_table' + + elif token == 'as-path|next-hop|med' or token == 'next-hop|med' or token == 'as-path|med' or token == 'as-path|next-hop': + return 'idx_attribute' + + elif token == '(1-4294967295)|external|internal' or token == '(1-4294967295)|internal|external': + return 'idx_remote_as' + + elif token == '(1-500)|WORD' or token == '(1-99)|(100-500)|WORD': + return 'idx_comm_list' + + elif token == 'ipv4|ipv6' or token == 'ip|ipv6': + return 'idx_afi' + + elif token == 'md5|clear' or token == 'null|message-digest' or token == 'md5|text': + return 'idx_encryption' + + elif token == 'IFNAME|default': + return 'idx_ifname' + + elif token == 'type-1|type-2': + return 'idx_external' + + elif token == 'table|intra-area|inter-area|memory': + return 'idx_type' + + elif token == 'translate-candidate|translate-never|translate-always': + return 'idx_translate' + + elif token == 'intra-area (1-255)|inter-area (1-255)|external (1-255)': + return 'idx_area_distance' + + elif token == 'metric (0-16777214)|metric-type <1|2>|route-map WORD' or token == 'always|metric (0-16777214)|metric-type <1|2>|route-map WORD': + return 'idx_redist_param' + + elif token == 'default|enable|disable' or token == 'enable|disable': + return 'idx_enable_disable' + + elif token == 'unknown|hello|dbdesc|lsreq|lsupdate|lsack|all' or token == 'hello|dd|ls-request|ls-update|ls-ack|all': + return 'idx_packet' + + elif token == 'router|network|inter-prefix|inter-router|as-external|link|intra-prefix|unknown' or token == 'intra-area|inter-area|external-1|external-2' or token == 'router|network|inter-prefix|inter-router|as-external|group-membership|type-7|link|intra-prefix' or token == 'asbr-summary|external|network|router|summary|nssa-external|opaque-link|opaque-area|opaque-as': + return 'idx_lsa' + + elif token == 'broadcast|point-to-point' or token == 'broadcast|non-broadcast|point-to-multipoint|point-to-point': + return 'idx_network' + + elif token == 'A.B.C.D|(0-4294967295)': + return 'idx_ipv4_number' + + elif token == 'narrow|transition|wide': + return 'idx_metric_style' + + elif token == 'area-password|domain-password': + return 'idx_password' + + elif token == 'param': + return 'idx_param' + + elif token == 'advertised-routes|received-routes': + return 'idx_adv_rcvd_routes' + + elif token == 'encap|multicast|unicast|vpn' or token == 'unicast|multicast|vpn|encap': + return 'idx_safi' + + elif token == 'AA:NN|local-AS|no-advertise|no-export': + return 'idx_community' + + elif token == 'all|all-et|updates|updates-et|routes-mrt': + return 'idx_dump_routes' + + elif token == 'A.B.C.D|X:X::X:X|WORD': + return 'idx_peer' + + elif token == 'A.B.C.D/M|X:X::X:X/M': + return 'idx_ipv4_ipv6_prefixlen' + + elif token == 'level-1|level-2' or token == 'level-1|level-1-2|level-2-only': + return 'idx_level' + + elif token == 'metric (0-16777215)|route-map WORD' or token == 'always|metric (0-16777215)|route-map WORD': + return 'idx_metric_rmap' + + elif token == 'urib-only|mrib-only|mrib-then-urib|lower-distance|longer-prefix': + return 'idx_rpf_lookup_mode' + + elif token == 'hello|joins': + return 'idx_hello_join' + + elif token == 'nocache|wrongvif|wholepkt': + return 'idx_type' + + elif token == 'file|memory|terminal': + return 'idx_type' + + elif token == 'prefix': + return 'idx_prefix' + + elif token == 'A.B.C.D/M|any': + return 'idx_ipv4_any' + + elif token == 'X:X::X:X/M|any': + return 'idx_ipv6_any' + + elif token == '(1-99)|(1300-1999)' or token == '(100-199)|(2000-2699)' or token == '(1-99)|(100-199)|(1300-1999)|(2000-2699)|WORD': + return 'idx_acl' + + elif token == 'kern|user|mail|daemon|auth|syslog|lpr|news|uucp|cron|local0|local1|local2|local3|local4|local5|local6|local7': + return 'idx_target' + + + elif token in ('kernel|connected|static|rip|ospf|isis|pim|table', + 'kernel|connected|static|ripng|ospf6|isis|table', + 'kernel|connected|static|rip|isis|bgp|pim|table', + 'kernel|connected|static|rip|ospf|isis|bgp|pim|table', + 'kernel|connected|static|rip|ospf|isis|bgp|pim|table', + 'kernel|connected|static|rip|ospf|isis|bgp|pim|table|any', + 'kernel|connected|static|ripng|ospf6|isis|bgp|table|any', + 'kernel|connected|static|ripng|ospf6|isis|bgp|table', + 'kernel|connected|static|ospf6|isis|bgp|table', + 'kernel|connected|static|ospf|isis|bgp|pim|table', + 'kernel|connected|static|ripng|isis|bgp|table', + # '', + 'zebra|ripd|ripngd|ospfd|ospf6d|bgpd|isisd|pimd', + 'zebra|ripd|ripngd|ospfd|ospf6d|bgpd|isisd', + 'bgp|ospf|rip|ripng|isis|ospf6|connected|system|kernel|static', + 'kernel|connected|static|rip|ripng|ospf|ospf6|bgp|pim|table'): + return 'idx_protocol' + + elif '|' in token: + raise Exception("%d: what variable name for %s" % (line_number, token)) + + elif re_range: + return 'idx_number' + + elif token.upper() == token: + return 'idx_%s' % token.lower() + + else: + raise Exception("%d: what variable name for %s" % (line_number, token)) + + +def get_command_string_index_variable_table(line_number, line): + """ + Return a table that maps an index position to a variable name such as 'idx_ipv4' + """ + indexes = OrderedDict() + + line = line.strip() + assert line.startswith('"'), "line does not start with \"\n%s" % (line) + assert line.endswith('",'), "line does not end with \",\n%s" % (line) + line = line[1:-2] + max_index = 0 + + for (token_index, token) in enumerate(line_to_tokens(line_number, line)): + if not token: + raise Exception("%d: empty token" % line_number) + + if token_is_variable(line_number, token): + # print "%s is a token" % token + idx_variable_name = get_token_index_variable_name(line_number, token) + count = 0 + for tmp in indexes.itervalues(): + if tmp == idx_variable_name: + count += 1 + elif re.search('^%s_\d+' % idx_variable_name, tmp): + count += 1 + if count: + idx_variable_name = "%s_%d" % (idx_variable_name, count + 1) + indexes[token_index] = idx_variable_name + + return indexes + + +def expand_command_string(line): + + # in the middle + line = line.replace('" CMD_AS_RANGE "', '(1-4294967295)') + line = line.replace('" DYNAMIC_NEIGHBOR_LIMIT_RANGE "', '(1-5000)') + line = line.replace('" BGP_INSTANCE_CMD "', '<view|vrf> WORD') + line = line.replace('" BGP_INSTANCE_ALL_CMD "', '<view|vrf> all') + line = line.replace('" CMD_RANGE_STR(1, MULTIPATH_NUM) "', '(1-255)') + line = line.replace('" QUAGGA_IP_REDIST_STR_BGPD "', '<kernel|connected|static|rip|ospf|isis|pim|table>') + line = line.replace('" QUAGGA_IP6_REDIST_STR_BGPD "', '<kernel|connected|static|ripng|ospf6|isis|table>') + line = line.replace('" OSPF_LSA_TYPES_CMD_STR "', 'asbr-summary|external|network|router|summary|nssa-external|opaque-link|opaque-area|opaque-as') + line = line.replace('" QUAGGA_REDIST_STR_OSPFD "', '<kernel|connected|static|rip|isis|bgp|pim|table>') + line = line.replace('" VRF_CMD_STR "', 'vrf NAME') + line = line.replace('" VRF_ALL_CMD_STR "', 'vrf all') + line = line.replace('" QUAGGA_IP_PROTOCOL_MAP_STR_ZEBRA "', '<kernel|connected|static|rip|ospf|isis|bgp|pim|table|any>') + line = line.replace('" QUAGGA_IP6_PROTOCOL_MAP_STR_ZEBRA "', '<kernel|connected|static|ripng|ospf6|isis|bgp|table|any>') + line = line.replace('" QUAGGA_REDIST_STR_RIPNGD "', '<kernel|connected|static|ospf6|isis|bgp|table>') + line = line.replace('" QUAGGA_REDIST_STR_RIPD "', '<kernel|connected|static|ospf|isis|bgp|pim|table>') + line = line.replace('" QUAGGA_REDIST_STR_OSPF6D "', '<kernel|connected|static|ripng|isis|bgp|table>') + line = line.replace('" QUAGGA_REDIST_STR_ISISD "', '<kernel|connected|static|rip|ripng|ospf|ospf6|bgp|pim|table>') + line = line.replace('" LOG_FACILITIES "', '<kern|user|mail|daemon|auth|syslog|lpr|news|uucp|cron|local0|local1|local2|local3|local4|local5|local6|local7>') + line = line.replace('" LOG_LEVELS "', ' <emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>') + + # endswith + line = line.replace('" CMD_AS_RANGE,', ' (1-4294967295)",') + line = line.replace('" DYNAMIC_NEIGHBOR_LIMIT_RANGE,', ' (1-5000)",') + line = line.replace('" BGP_INSTANCE_CMD,', ' <view|vrf> WORD",') + line = line.replace('" BGP_INSTANCE_ALL_CMD,', ' <view|vrf> all",') + line = line.replace('" CMD_RANGE_STR(1, MULTIPATH_NUM),', '(1-255)",') + line = line.replace('" CMD_RANGE_STR(1, MAXTTL),', '(1-255)",') + line = line.replace('" BFD_CMD_DETECT_MULT_RANGE BFD_CMD_MIN_RX_RANGE BFD_CMD_MIN_TX_RANGE,', '(2-255) (50-60000) (50-60000)",') + line = line.replace('" OSPF_LSA_TYPES_CMD_STR,', + ' asbr-summary|external|network|router|summary|nssa-external|opaque-link|opaque-area|opaque-as",') + line = line.replace('" BGP_UPDATE_SOURCE_REQ_STR,', ' <A.B.C.D|X:X::X:X|WORD>",') + line = line.replace('" BGP_UPDATE_SOURCE_OPT_STR,', ' [A.B.C.D|X:X::X:X|WORD]",') + line = line.replace('" QUAGGA_IP_REDIST_STR_BGPD,', ' <kernel|connected|static|rip|ospf|isis|pim|table>",') + line = line.replace('" QUAGGA_IP6_REDIST_STR_BGPD,', ' <kernel|connected|static|ripng|ospf6|isis|table>",') + line = line.replace('" QUAGGA_REDIST_STR_OSPFD,', ' <kernel|connected|static|rip|isis|bgp|pim|table>",') + line = line.replace('" VRF_CMD_STR,', ' vrf NAME",') + line = line.replace('" VRF_ALL_CMD_STR,', ' vrf all",') + line = line.replace('" QUAGGA_IP_REDIST_STR_ZEBRA,', ' <kernel|connected|static|rip|ospf|isis|bgp|pim|table>",') + line = line.replace('" QUAGGA_IP6_REDIST_STR_ZEBRA,', ' <kernel|connected|static|ripng|ospf6|isis|bgp|table>",') + line = line.replace('" QUAGGA_IP_PROTOCOL_MAP_STR_ZEBRA,', ' <kernel|connected|static|rip|ospf|isis|bgp|pim|table|any>",') + line = line.replace('" QUAGGA_IP6_PROTOCOL_MAP_STR_ZEBRA,', ' <kernel|connected|static|ripng|ospf6|isis|bgp|table|any>",') + line = line.replace('" QUAGGA_REDIST_STR_RIPNGD,', ' <kernel|connected|static|ospf6|isis|bgp|table>",') + line = line.replace('" QUAGGA_REDIST_STR_RIPD,', ' <kernel|connected|static|ospf|isis|bgp|pim|table>",') + line = line.replace('" PIM_CMD_IP_MULTICAST_ROUTING,', ' ip multicast-routing",') + line = line.replace('" PIM_CMD_IP_IGMP_QUERY_INTERVAL,', ' ip igmp query-interval",') + line = line.replace('" PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC,', ' ip igmp query-max-response-time-dsec",') + line = line.replace('" PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME,', ' ip igmp query-max-response-time",') + line = line.replace('" QUAGGA_REDIST_STR_OSPF6D,', ' <kernel|connected|static|ripng|isis|bgp|table>",') + line = line.replace('" QUAGGA_REDIST_STR_ISISD,', ' <kernel|connected|static|rip|ripng|ospf|ospf6|bgp|pim|table>",') + line = line.replace('" LOG_FACILITIES,', ' <kern|user|mail|daemon|auth|syslog|lpr|news|uucp|cron|local0|local1|local2|local3|local4|local5|local6|local7>",') + line = line.replace('" LOG_LEVELS,', ' <emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>",') + + # startswith + line = line.replace('LISTEN_RANGE_CMD "', '"bgp listen range <A.B.C.D/M|X:X::X:X/M> ') + line = line.replace('NO_NEIGHBOR_CMD2 "', '"no neighbor <A.B.C.D|X:X::X:X|WORD> ') + line = line.replace('NEIGHBOR_CMD2 "', '"neighbor <A.B.C.D|X:X::X:X|WORD> ') + line = line.replace('NO_NEIGHBOR_CMD "', '"no neighbor <A.B.C.D|X:X::X:X> ') + line = line.replace('NEIGHBOR_CMD "', '"neighbor <A.B.C.D|X:X::X:X> ') + line = line.replace('PIM_CMD_NO "', '"no ') + line = line.replace('PIM_CMD_IP_IGMP_QUERY_INTERVAL "', '"ip igmp query-interval ') + line = line.replace('PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME "', '"ip igmp query-max-response-time ') + line = line.replace('PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC "', '"ip igmp query-max-response-time-dsec ') + line = line.replace('LOG_LEVELS "', '"<emergencies|alerts|critical|errors|warnings|notifications|informational|debugging> ') + + # solo + line = line.replace('NO_NEIGHBOR_CMD2,', '"no neighbor <A.B.C.D|X:X::X:X|WORD>",') + line = line.replace('NEIGHBOR_CMD2,', '"neighbor <A.B.C.D|X:X::X:X|WORD>",') + line = line.replace('NO_NEIGHBOR_CMD,', '"no neighbor <A.B.C.D|X:X::X:X>",') + line = line.replace('NEIGHBOR_CMD,', '"neighbor <A.B.C.D|X:X::X:X>",') + line = line.replace('PIM_CMD_IP_MULTICAST_ROUTING,', '"ip multicast-routing",') + + if line.rstrip().endswith('" ,'): + line = line.replace('" ,', '",') + + # compress duplicate white spaces + re_space = re.search('^(\s*).*(\s*)$', line) + line = re_space.group(1) + ' '.join(line.split()) + re_space.group(2) + + return line + + +class DEFUN(object): + + def __init__(self, line_number, command_string_expanded, lines): + # name, name_cmd, command_string, help_strings, guts): + self.line_number = line_number + self.name = None + self.name_cmd = None + self.command_string = None + self.command_string_expanded = command_string_expanded + self.help_strings = [] + self.guts = [] + + ''' +DEFUN (no_bgp_maxmed_onstartup, + no_bgp_maxmed_onstartup_cmd, + "no bgp max-med on-startup", + NO_STR + BGP_STR + "Advertise routes with max-med\n" + "Effective on a startup\n") + + ''' + state = 'HEADER' + for (line_number, line) in enumerate(lines): + + if state == 'HEADER': + if line_number == 0: + re_name = re.search('DEFUN \((.*),', line.strip()) + self.name = re_name.group(1) + + elif line_number == 1: + self.name_cmd = line.strip()[0:-1] # chop the trailing comma + + elif line_number == 2: + self.command_string = line + state = 'HELP' + + elif state == 'HELP': + if line.strip() == '{': + # self.guts.append(line) + state = 'BODY' + else: + self.help_strings.append(line) + + elif state == 'BODY': + if line.rstrip() == '}': + # self.guts.append(line) + state = None + else: + self.guts.append(line) + + else: + raise Exception("invalid state %s" % state) + + # print "%d %7s: %s" % (line_number, state, line.rstrip()) + + assert self.command_string, "No command string for\n%s" % pformat(lines) + + def __str__(self): + return self.name + + def sanity_check(self): + (max_index, variable_indexes) = get_command_string_variable_indexes(self.line_number, self.command_string_expanded) + + # sanity check that each argv index matches a variable in the command string + for line in self.guts: + if 'argv[' in line and '->arg' in line: + tmp_line = deepcopy(line) + re_argv = re.search('^.*?argv\[(\d+)\]->arg(.*)$', tmp_line) + + while re_argv: + index = int(re_argv.group(1)) + if index not in variable_indexes and index <= max_index: + print "%d: index %s is not a variable in the command string" % (self.line_number, index) + tmp_line = re_argv.group(2) + re_argv = re.search('^.*?argv\[(\d+)\]->arg(.*)$', tmp_line) + + def get_new_command_string(self): + line = self.command_string + # Change <1-255> to (1-255) + # Change (foo|bar) to <foo|bar> + # Change {wazzup} to [wazzup]....there shouldn't be many of these + + line = line.replace('(', '<') + line = line.replace(')', '>') + line = line.replace('{', '[') + line = line.replace('}', ']') + re_range = re.search('^(.*?)<(\d+-\d+)>(.*)$', line) + + # A one off to handle "CMD_RANGE_STR(1, MULTIPATH_NUM)" + if 'CMD_RANGE_STR<' in line: + line = line.replace('CMD_RANGE_STR<', 'CMD_RANGE_STR(') + line = line.replace('>', ')') + + while re_range: + line = "%s(%s)%s" % (re_range.group(1), re_range.group(2), re_range.group(3)) + re_range = re.search('^(.*?)<(\d+-\d+)>(.*)$', line) + + if not line.endswith('\n'): + line += '\n' + + if '|<' in line: + print "%d: ERROR |< is illegal in '%s'" % (self.line_number, line) + + if '|[' in line: + print "%d: ERROR |[ is illegal in '%s'" % (self.line_number, line) + + # compress duplicate whitespaces + re_space = re.search('^(\s*).*(\s*)$', line) + line = re_space.group(1) + ' '.join(line.split()) + re_space.group(2) + + ''' + # I ran this once and cleaned them all up...this spews many + # false positives so we don't want to leave this on + for token in line_to_tokens(self.line_number, line): + token = token.strip() + + if token.endswith('",'): + token = token[0:-2] + + if token.startswith('[') and '|' in token: + if not token.startswith('[<') or not token.endswith('>]'): + print "%s: suspend token '%s'" % (self.line_number, token) + ''' + + return line + + def get_used_idx_variables(self, idx_table): + used = {} + + # sanity check that each argv index matches a variable in the command string + for line in self.guts: + if 'argv[' in line and '->arg' in line: + tmp_line = deepcopy(line) + re_argv = re.search('^.*?argv\[(\w+)\]->arg(.*)$', tmp_line) + + while re_argv: + index = re_argv.group(1) + + if index.isdigit(): + index = int(index) + if index in idx_table: + used[index] = idx_table[index] + else: + print "%d: could not find idx variable for %d" % (self.line_number, index) + else: + for (key, value) in idx_table.iteritems(): + if value == index: + used[key] = value + break + + tmp_line = re_argv.group(2) + re_argv = re.search('^.*?argv\[(\w+)\]->arg(.*)$', tmp_line) + + return used + + def uses_argc(self): + for line in self.guts: + if 'CHECK ME argc referenced below' in line: + return False + + if 'use_json (argc, argv)' in line: + continue + + if 'use_json(argc, argv)' in line: + continue + + if 'bgp_get_argv_vrf (argc,)' in line: + continue + + if 'bgp_get_argv_afi_safi (argc,' in line: + continue + + if 'zebra_vty_ip_route_tdv_helper (argc,' in line: + continue + + if 'argc' in line: + return True + return False + + def dump(self): + # new_command_string = self.get_new_command_string() + # new_command_string_expanded = expand_command_string(new_command_string) + new_command_string_expanded = self.get_new_command_string() + lines = [] + + lines.append("DEFUN (%s,\n" % self.name) + lines.append(" %s,\n" % self.name_cmd) + # lines.append(new_command_string) + lines.append(new_command_string_expanded) + lines.extend(self.help_strings) + lines.append('{\n') + + # if self.uses_argc(): + # lines.append(" /* CHECK ME argc referenced below */\n") + lines.extend(self.guts) + + ''' + This is no longer used but was used to do the "- next it was used to + replace the 4 in argv[4]->arg with an idx_foo variable" run mentioned + at the top of this file. + + # only print the variables that will be used else we get a compile error + idx_table = get_command_string_index_variable_table(self.line_number, new_command_string_expanded) + idx_table_used = self.get_used_idx_variables(idx_table) + + for index in sorted(idx_table_used.keys()): + idx_variable = idx_table_used[index] + lines.append(" int %s = %d;\n" % (idx_variable, index)) + + # sanity check that each argv index matches a variable in the command string + for line in self.guts: + if line.startswith(' int idx_'): + pass + elif 'argv[' in line and '->arg' in line: + for (index, idx_variable) in idx_table.iteritems(): + line = line.replace("argv[%d]->arg" % index, "argv[%s]->arg" % idx_variable) + lines.append(line) + else: + lines.append(line) + ''' + + lines.append('}\n') + return ''.join(lines) + + +def update_argvs(filename): + lines = [] + + with open(filename, 'r') as fh: + state = None + defun_line_number = None + cmd_string = None + # argv_translator = {} + # print_translator = False + variable_indexes = {} + max_index = 0 + defun_lines = [] + defuns = {} + command_string = None + + for (line_number, line) in enumerate(fh.readlines()): + # new_line = line + + if state is None: + if line.startswith('DEFUN ('): + assert line.count(',') == 1, "%d: Too many commas in\n%s" % (line_number, line) + state = 'DEFUN_HEADER' + defun_line_number = line_number + defun_lines.append(line) + + elif state == 'DEFUN_HEADER': + defun_lines.append(line) + + if line.startswith('DEFUN'): + raise Exception("ERROR on line %d, found DEFUN inside DEFUN" % line_number) + + elif line.startswith('ALIAS'): + raise Exception("ERROR on line %d, found ALIAS inside DEFUN" % line_number) + + elif line.strip() == '{': + state = 'DEFUN_BODY' + + elif line_number == defun_line_number + 2: + line = expand_command_string(line) + command_string = line + + ''' + # No longer used now that all indexes have been updated + argv_translator = get_argv_translator(line_number, line) + print_translator = True + ''' + + elif state == 'DEFUN_BODY': + defun_lines.append(line) + + if line.rstrip() == '}': + new_defun = DEFUN(defun_line_number, command_string, defun_lines) + defuns[new_defun.name] = new_defun + state = None + command_string = None + defun_lines = [] + + # cmd_string = None + # defun_line_number = None + # argv_translator = {} + + ''' + # No longer used now that all indexes have been updated + elif 'argv[' in new_line and '->arg' not in new_line: + for index in reversed(argv_translator.keys()): + old_argv = "argv[%d]" % index + new_argv = "argv[%d]->arg" % argv_translator[index] + new_line = new_line.replace(old_argv, new_argv) + ''' + + # uncomment to debug state machine + # print "%5d %12s: %s" % (line_number, state, line.rstrip()) + + ''' + # No longer used now that all indexes have been updated + if print_translator: + print "%s\n" % pformat(argv_translator) + print_translator = False + ''' + + lines.append(line) + + for defun in defuns.itervalues(): + defun.sanity_check() + + # Now write the file but allow the DEFUN object to update the contents of the DEFUN () + with open(filename, 'w') as fh: + state = None + + for (line_number, line) in enumerate(lines): + + if state is None: + if 'The following ALIASes need to be implemented in this DEFUN' in line: + state = 'CHANGE ME' + fh.write(line) + + elif line.startswith('DEFUN ('): + state = 'DEFUN_HEADER' + re_name = re.search('DEFUN \((.*),', line.strip()) + name = re_name.group(1) + defun = defuns.get(name) + fh.write(defun.dump()) + else: + fh.write(line) + + elif state == 'CHANGE ME': + if line.strip() == '*/': + state = None + fh.write(line) + elif line.strip().startswith('* ') and not line.strip().startswith('* '): + new_line = expand_command_string(line[3:]) # chop the leading " * " + fh.write(" * %s" % new_line) + else: + fh.write(line) + + elif state == 'DEFUN_HEADER': + if line.strip() == '{': + state = 'DEFUN_BODY' + + elif state == 'DEFUN_BODY': + if line.rstrip() == '}': + state = None + + # uncomment to debug state machine + # print "%5d %12s: %s" % (line_number, state, line.rstrip()) + + +if __name__ == '__main__': + (print_options, ended_with_space, sys.argv) = get_network_docopt_info(sys.argv) + cli = NetworkDocopt(__doc__) + + if print_options: + cli.print_options(ended_with_space) + else: + cli.run() + + if cli.get('rebuild-defuns'): + if cli.get('<text>'): + filename = cli.get('<text>') + update_argvs(filename) + + else: + output = subprocess.check_output("grep -l DEFUN *.c", shell=True).splitlines() + for filename in output: + filename = filename.strip() + print "crunching %s" % filename + update_argvs(filename) |
