]> git.proxmox.com Git - mirror_frr.git/blame - python/clidef.py
bgpd: [7.1] Strip `delete` keyword when looking up for lcommuni… (#4786)
[mirror_frr.git] / python / clidef.py
CommitLineData
5578a14d
DL
1# FRR CLI preprocessor (DEFPY)
2#
3# Copyright (C) 2017 David Lamparter for NetDEF, Inc.
4#
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)
8# any later version.
9#
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
13# more details.
14#
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
18
19import clippy, traceback, sys, os
20from collections import OrderedDict
21from functools import reduce
22from pprint import pprint
23from string import Template
24from io import StringIO
25
26# the various handlers generate output C code for a particular type of
27# CLI token, choosing the most useful output C type.
28
29class RenderHandler(object):
30 def __init__(self, token):
31 pass
32 def combine(self, other):
33 if type(self) == type(other):
34 return other
35 return StringHandler(None)
36
37 deref = ''
38 drop_str = False
0ee0892b 39 canfail = True
5578a14d
DL
40
41class StringHandler(RenderHandler):
42 argtype = 'const char *'
43 decl = Template('const char *$varname = NULL;')
5254bb15 44 code = Template('$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;')
5578a14d 45 drop_str = True
0ee0892b 46 canfail = False
5578a14d
DL
47
48class LongHandler(RenderHandler):
49 argtype = 'long'
50 decl = Template('long $varname = 0;')
51 code = Template('''\
52char *_end;
53$varname = strtol(argv[_i]->arg, &_end, 10);
54_fail = (_end == argv[_i]->arg) || (*_end != '\\0');''')
55
56# A.B.C.D/M (prefix_ipv4) and
57# X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
58# struct prefix:
59
60class PrefixBase(RenderHandler):
61 def combine(self, other):
62 if type(self) == type(other):
63 return other
473196f6 64 if isinstance(other, PrefixBase):
5578a14d
DL
65 return PrefixGenHandler(None)
66 return StringHandler(None)
67 deref = '&'
68class Prefix4Handler(PrefixBase):
69 argtype = 'const struct prefix_ipv4 *'
3e300703 70 decl = Template('struct prefix_ipv4 $varname = { };')
5578a14d
DL
71 code = Template('_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);')
72class Prefix6Handler(PrefixBase):
73 argtype = 'const struct prefix_ipv6 *'
3e300703 74 decl = Template('struct prefix_ipv6 $varname = { };')
5578a14d 75 code = Template('_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);')
473196f6
QY
76class PrefixEthHandler(PrefixBase):
77 argtype = 'struct prefix_eth *'
3e300703 78 decl = Template('struct prefix_eth $varname = { };')
473196f6 79 code = Template('_fail = !str2prefix_eth(argv[_i]->arg, &$varname);')
5578a14d
DL
80class PrefixGenHandler(PrefixBase):
81 argtype = 'const struct prefix *'
3e300703 82 decl = Template('struct prefix $varname = { };')
5578a14d
DL
83 code = Template('_fail = !str2prefix(argv[_i]->arg, &$varname);')
84
85# same for IP addresses. result is union sockunion.
86class IPBase(RenderHandler):
87 def combine(self, other):
88 if type(self) == type(other):
89 return other
90 if type(other) in [IP4Handler, IP6Handler, IPGenHandler]:
91 return IPGenHandler(None)
92 return StringHandler(None)
93class IP4Handler(IPBase):
94 argtype = 'struct in_addr'
95 decl = Template('struct in_addr $varname = { INADDR_ANY };')
96 code = Template('_fail = !inet_aton(argv[_i]->arg, &$varname);')
97class IP6Handler(IPBase):
98 argtype = 'struct in6_addr'
3e300703 99 decl = Template('struct in6_addr $varname = {};')
5578a14d
DL
100 code = Template('_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);')
101class IPGenHandler(IPBase):
102 argtype = 'const union sockunion *'
103 decl = Template('''union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;''')
104 code = Template('''\
105if (argv[_i]->text[0] == 'X') {
106 s__$varname.sa.sa_family = AF_INET6;
107 _fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr);
108 $varname = &s__$varname;
109} else {
110 s__$varname.sa.sa_family = AF_INET;
111 _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
112 $varname = &s__$varname;
113}''')
114
115def mix_handlers(handlers):
116 def combine(a, b):
117 if a is None:
118 return b
119 return a.combine(b)
120 return reduce(combine, handlers, None)
121
122handlers = {
123 'WORD_TKN': StringHandler,
124 'VARIABLE_TKN': StringHandler,
125 'RANGE_TKN': LongHandler,
126 'IPV4_TKN': IP4Handler,
127 'IPV4_PREFIX_TKN': Prefix4Handler,
128 'IPV6_TKN': IP6Handler,
129 'IPV6_PREFIX_TKN': Prefix6Handler,
473196f6
QY
130 'MAC_TKN': PrefixEthHandler,
131 'MAC_PREFIX_TKN': PrefixEthHandler,
5578a14d
DL
132}
133
134# core template invoked for each occurence of DEFPY.
0ee0892b
DL
135#
136# the "#if $..." bits are there to keep this template unified into one
137# common form, without requiring a more advanced template engine (e.g.
138# jinja2)
5578a14d
DL
139templ = Template('''/* $fnname => "$cmddef" */
140DEFUN_CMD_FUNC_DECL($fnname)
141#define funcdecl_$fnname static int ${fnname}_magic(\\
142 const struct cmd_element *self __attribute__ ((unused)),\\
143 struct vty *vty __attribute__ ((unused)),\\
144 int argc __attribute__ ((unused)),\\
145 struct cmd_token *argv[] __attribute__ ((unused))$argdefs)
146funcdecl_$fnname;
147DEFUN_CMD_FUNC_TEXT($fnname)
148{
0ee0892b 149#if $nonempty /* anything to parse? */
5578a14d 150 int _i;
0ee0892b 151#if $canfail /* anything that can fail? */
5578a14d 152 unsigned _fail = 0, _failcnt = 0;
0ee0892b 153#endif
5578a14d
DL
154$argdecls
155 for (_i = 0; _i < argc; _i++) {
156 if (!argv[_i]->varname)
157 continue;
0ee0892b
DL
158#if $canfail /* anything that can fail? */
159 _fail = 0;
160#endif
161$argblocks
162#if $canfail /* anything that can fail? */
5578a14d 163 if (_fail)
5c7571d4 164 vty_out (vty, "%% invalid input for %s: %s\\n",
8e25c8ce 165 argv[_i]->varname, argv[_i]->arg);
5578a14d 166 _failcnt += _fail;
0ee0892b 167#endif
5578a14d 168 }
0ee0892b 169#if $canfail /* anything that can fail? */
5578a14d
DL
170 if (_failcnt)
171 return CMD_WARNING;
0ee0892b
DL
172#endif
173#endif
5578a14d
DL
174 return ${fnname}_magic(self, vty, argc, argv$arglist);
175}
176
177''')
178
179# invoked for each named parameter
180argblock = Template('''
181 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
182 $code
183 }''')
184
185def process_file(fn, ofd, dumpfd, all_defun):
186 filedata = clippy.parse(fn)
187
188 for entry in filedata['data']:
e31f4dbe 189 if entry['type'].startswith('DEFPY') or (all_defun and entry['type'].startswith('DEFUN')):
5578a14d 190 cmddef = entry['args'][2]
5578a14d
DL
191 cmddef = ''.join([i[1:-1] for i in cmddef])
192
193 graph = clippy.Graph(cmddef)
194 args = OrderedDict()
195 for token, depth in clippy.graph_iterate(graph):
196 if token.type not in handlers:
197 continue
198 if token.varname is None:
199 continue
200 arg = args.setdefault(token.varname, [])
201 arg.append(handlers[token.type](token))
202
203 #print('-' * 76)
204 #pprint(entry)
205 #clippy.dump(graph)
206 #pprint(args)
207
208 params = { 'cmddef': cmddef, 'fnname': entry['args'][0][0] }
209 argdefs = []
210 argdecls = []
211 arglist = []
212 argblocks = []
213 doc = []
0ee0892b 214 canfail = 0
5578a14d
DL
215
216 def do_add(handler, varname, attr = ''):
217 argdefs.append(',\\\n\t%s %s%s' % (handler.argtype, varname, attr))
218 argdecls.append('\t%s\n' % (handler.decl.substitute({'varname': varname}).replace('\n', '\n\t')))
219 arglist.append(', %s%s' % (handler.deref, varname))
220 if attr == '':
221 at = handler.argtype
222 if not at.startswith('const '):
223 at = '. . . ' + at
224 doc.append('\t%-26s %s' % (at, varname))
225
226 for varname in args.keys():
227 handler = mix_handlers(args[varname])
228 #print(varname, handler)
229 if handler is None: continue
230 do_add(handler, varname)
231 code = handler.code.substitute({'varname': varname}).replace('\n', '\n\t\t\t')
0ee0892b
DL
232 if handler.canfail:
233 canfail = 1
5578a14d
DL
234 strblock = ''
235 if not handler.drop_str:
236 do_add(StringHandler(None), '%s_str' % (varname), ' __attribute__ ((unused))')
237 strblock = '\n\t\t\t%s_str = argv[_i]->arg;' % (varname)
238 argblocks.append(argblock.substitute({'varname': varname, 'strblock': strblock, 'code': code}))
239
240 if dumpfd is not None:
241 if len(arglist) > 0:
242 dumpfd.write('"%s":\n%s\n\n' % (cmddef, '\n'.join(doc)))
243 else:
244 dumpfd.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef))
245
246 params['argdefs'] = ''.join(argdefs)
247 params['argdecls'] = ''.join(argdecls)
248 params['arglist'] = ''.join(arglist)
249 params['argblocks'] = ''.join(argblocks)
0ee0892b
DL
250 params['canfail'] = canfail
251 params['nonempty'] = len(argblocks)
5578a14d
DL
252 ofd.write(templ.substitute(params))
253
254if __name__ == '__main__':
255 import argparse
256
257 argp = argparse.ArgumentParser(description = 'FRR CLI preprocessor in Python')
258 argp.add_argument('--all-defun', action = 'store_const', const = True,
259 help = 'process DEFUN() statements in addition to DEFPY()')
260 argp.add_argument('--show', action = 'store_const', const = True,
261 help = 'print out list of arguments and types for each definition')
262 argp.add_argument('-o', type = str, metavar = 'OUTFILE',
263 help = 'output C file name')
264 argp.add_argument('cfile', type = str)
265 args = argp.parse_args()
266
267 dumpfd = None
268 if args.o is not None:
269 ofd = StringIO()
270 if args.show:
271 dumpfd = sys.stdout
272 else:
273 ofd = sys.stdout
274 if args.show:
275 dumpfd = sys.stderr
276
277 process_file(args.cfile, ofd, dumpfd, args.all_defun)
278
279 if args.o is not None:
b716e79a 280 clippy.wrdiff(args.o, ofd, [args.cfile, os.path.realpath(__file__), sys.executable])