]>
git.proxmox.com Git - mirror_frr.git/blob - python/clidef.py
1 # FRR CLI preprocessor (DEFPY)
3 # Copyright (C) 2017 David Lamparter for NetDEF, Inc.
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the Free
7 # Software Foundation; either version 2 of the License, or (at your option)
10 # This program is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 # You should have received a copy of the GNU General Public License along
16 # with this program; see the file COPYING; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 import clippy
, traceback
, sys
, os
20 from collections
import OrderedDict
21 from functools
import reduce
22 from pprint
import pprint
23 from string
import Template
24 from io
import StringIO
26 # the various handlers generate output C code for a particular type of
27 # CLI token, choosing the most useful output C type.
29 class RenderHandler(object):
30 def __init__(self
, token
):
32 def combine(self
, other
):
33 if type(self
) == type(other
):
35 return StringHandler(None)
40 class StringHandler(RenderHandler
):
41 argtype
= 'const char *'
42 decl
= Template('const char *$varname = NULL;')
43 code
= Template('$varname = argv[_i]->arg;')
46 class LongHandler(RenderHandler
):
48 decl
= Template('long $varname = 0;')
51 $varname = strtol(argv[_i]->arg, &_end, 10);
52 _fail = (_end == argv[_i]->arg) || (*_end != '\\0');''')
54 # A.B.C.D/M (prefix_ipv4) and
55 # X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
58 class PrefixBase(RenderHandler
):
59 def combine(self
, other
):
60 if type(self
) == type(other
):
62 if isinstance(other
, PrefixBase
):
63 return PrefixGenHandler(None)
64 return StringHandler(None)
66 class Prefix4Handler(PrefixBase
):
67 argtype
= 'const struct prefix_ipv4 *'
68 decl
= Template('struct prefix_ipv4 $varname = { };')
69 code
= Template('_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);')
70 class Prefix6Handler(PrefixBase
):
71 argtype
= 'const struct prefix_ipv6 *'
72 decl
= Template('struct prefix_ipv6 $varname = { };')
73 code
= Template('_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);')
74 class PrefixEthHandler(PrefixBase
):
75 argtype
= 'struct prefix_eth *'
76 decl
= Template('struct prefix_eth $varname = { };')
77 code
= Template('_fail = !str2prefix_eth(argv[_i]->arg, &$varname);')
78 class PrefixGenHandler(PrefixBase
):
79 argtype
= 'const struct prefix *'
80 decl
= Template('struct prefix $varname = { };')
81 code
= Template('_fail = !str2prefix(argv[_i]->arg, &$varname);')
83 # same for IP addresses. result is union sockunion.
84 class IPBase(RenderHandler
):
85 def combine(self
, other
):
86 if type(self
) == type(other
):
88 if type(other
) in [IP4Handler
, IP6Handler
, IPGenHandler
]:
89 return IPGenHandler(None)
90 return StringHandler(None)
91 class IP4Handler(IPBase
):
92 argtype
= 'struct in_addr'
93 decl
= Template('struct in_addr $varname = { INADDR_ANY };')
94 code
= Template('_fail = !inet_aton(argv[_i]->arg, &$varname);')
95 class IP6Handler(IPBase
):
96 argtype
= 'struct in6_addr'
97 decl
= Template('struct in6_addr $varname = IN6ADDR_ANY_INIT;')
98 code
= Template('_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);')
99 class IPGenHandler(IPBase
):
100 argtype
= 'const union sockunion *'
101 decl
= Template('''union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;''')
103 if (argv[_i]->text[0] == 'X') {
104 s__$varname.sa.sa_family = AF_INET6;
105 _fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr);
106 $varname = &s__$varname;
108 s__$varname.sa.sa_family = AF_INET;
109 _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
110 $varname = &s__$varname;
113 def mix_handlers(handlers
):
118 return reduce(combine
, handlers
, None)
121 'WORD_TKN': StringHandler
,
122 'VARIABLE_TKN': StringHandler
,
123 'RANGE_TKN': LongHandler
,
124 'IPV4_TKN': IP4Handler
,
125 'IPV4_PREFIX_TKN': Prefix4Handler
,
126 'IPV6_TKN': IP6Handler
,
127 'IPV6_PREFIX_TKN': Prefix6Handler
,
128 'MAC_TKN': PrefixEthHandler
,
129 'MAC_PREFIX_TKN': PrefixEthHandler
,
132 # core template invoked for each occurence of DEFPY.
133 templ
= Template('''/* $fnname => "$cmddef" */
134 DEFUN_CMD_FUNC_DECL($fnname)
135 #define funcdecl_$fnname static int ${fnname}_magic(\\
136 const struct cmd_element *self __attribute__ ((unused)),\\
137 struct vty *vty __attribute__ ((unused)),\\
138 int argc __attribute__ ((unused)),\\
139 struct cmd_token *argv[] __attribute__ ((unused))$argdefs)
141 DEFUN_CMD_FUNC_TEXT($fnname)
144 unsigned _fail = 0, _failcnt = 0;
146 for (_i = 0; _i < argc; _i++) {
147 if (!argv[_i]->varname)
151 vty_out (vty, "%% invalid input for %s: %s\\n",
152 argv[_i]->varname, argv[_i]->arg);
157 return ${fnname}_magic(self, vty, argc, argv$arglist);
162 # invoked for each named parameter
163 argblock
= Template('''
164 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
168 def process_file(fn
, ofd
, dumpfd
, all_defun
):
169 filedata
= clippy
.parse(fn
)
171 for entry
in filedata
['data']:
172 if entry
['type'] == 'DEFPY' or (all_defun
and entry
['type'].startswith('DEFUN')):
173 cmddef
= entry
['args'][2]
175 assert i
.startswith('"') and i
.endswith('"')
176 cmddef
= ''.join([i
[1:-1] for i
in cmddef
])
178 graph
= clippy
.Graph(cmddef
)
180 for token
, depth
in clippy
.graph_iterate(graph
):
181 if token
.type not in handlers
:
183 if token
.varname
is None:
185 arg
= args
.setdefault(token
.varname
, [])
186 arg
.append(handlers
[token
.type](token
))
193 params
= { 'cmddef': cmddef
, 'fnname': entry
['args'][0][0] }
200 def do_add(handler
, varname
, attr
= ''):
201 argdefs
.append(',\\\n\t%s %s%s' % (handler
.argtype
, varname
, attr
))
202 argdecls
.append('\t%s\n' % (handler
.decl
.substitute({'varname': varname
}).replace('\n', '\n\t')))
203 arglist
.append(', %s%s' % (handler
.deref
, varname
))
206 if not at
.startswith('const '):
208 doc
.append('\t%-26s %s' % (at
, varname
))
210 for varname
in args
.keys():
211 handler
= mix_handlers(args
[varname
])
212 #print(varname, handler)
213 if handler
is None: continue
214 do_add(handler
, varname
)
215 code
= handler
.code
.substitute({'varname': varname
}).replace('\n', '\n\t\t\t')
217 if not handler
.drop_str
:
218 do_add(StringHandler(None), '%s_str' % (varname
), ' __attribute__ ((unused))')
219 strblock
= '\n\t\t\t%s_str = argv[_i]->arg;' % (varname
)
220 argblocks
.append(argblock
.substitute({'varname': varname
, 'strblock': strblock
, 'code': code
}))
222 if dumpfd
is not None:
224 dumpfd
.write('"%s":\n%s\n\n' % (cmddef
, '\n'.join(doc
)))
226 dumpfd
.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef
))
228 params
['argdefs'] = ''.join(argdefs
)
229 params
['argdecls'] = ''.join(argdecls
)
230 params
['arglist'] = ''.join(arglist
)
231 params
['argblocks'] = ''.join(argblocks
)
232 ofd
.write(templ
.substitute(params
))
234 if __name__
== '__main__':
237 argp
= argparse
.ArgumentParser(description
= 'FRR CLI preprocessor in Python')
238 argp
.add_argument('--all-defun', action
= 'store_const', const
= True,
239 help = 'process DEFUN() statements in addition to DEFPY()')
240 argp
.add_argument('--show', action
= 'store_const', const
= True,
241 help = 'print out list of arguments and types for each definition')
242 argp
.add_argument('-o', type = str, metavar
= 'OUTFILE',
243 help = 'output C file name')
244 argp
.add_argument('cfile', type = str)
245 args
= argp
.parse_args()
248 if args
.o
is not None:
257 process_file(args
.cfile
, ofd
, dumpfd
, args
.all_defun
)
259 if args
.o
is not None:
260 clippy
.wrdiff(args
.o
, ofd
, [args
.cfile
, os
.path
.realpath(__file__
)])