]> git.proxmox.com Git - mirror_frr.git/blame - python/clidef.py
lib: move \n vs. \r\n handling into vty code
[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
39
40class StringHandler(RenderHandler):
41 argtype = 'const char *'
42 decl = Template('const char *$varname = NULL;')
43 code = Template('$varname = argv[_i]->arg;')
44 drop_str = True
45
46class LongHandler(RenderHandler):
47 argtype = 'long'
48 decl = Template('long $varname = 0;')
49 code = Template('''\
50char *_end;
51$varname = strtol(argv[_i]->arg, &_end, 10);
52_fail = (_end == argv[_i]->arg) || (*_end != '\\0');''')
53
54# A.B.C.D/M (prefix_ipv4) and
55# X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
56# struct prefix:
57
58class PrefixBase(RenderHandler):
59 def combine(self, other):
60 if type(self) == type(other):
61 return other
62 if type(other) in [Prefix4Handler, Prefix6Handler, PrefixGenHandler]:
63 return PrefixGenHandler(None)
64 return StringHandler(None)
65 deref = '&'
66class 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);')
70class 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);')
74class PrefixGenHandler(PrefixBase):
75 argtype = 'const struct prefix *'
76 decl = Template('struct prefix $varname = { };')
77 code = Template('_fail = !str2prefix(argv[_i]->arg, &$varname);')
78
79# same for IP addresses. result is union sockunion.
80class IPBase(RenderHandler):
81 def combine(self, other):
82 if type(self) == type(other):
83 return other
84 if type(other) in [IP4Handler, IP6Handler, IPGenHandler]:
85 return IPGenHandler(None)
86 return StringHandler(None)
87class IP4Handler(IPBase):
88 argtype = 'struct in_addr'
89 decl = Template('struct in_addr $varname = { INADDR_ANY };')
90 code = Template('_fail = !inet_aton(argv[_i]->arg, &$varname);')
91class IP6Handler(IPBase):
92 argtype = 'struct in6_addr'
93 decl = Template('struct in6_addr $varname = IN6ADDR_ANY_INIT;')
94 code = Template('_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);')
95class IPGenHandler(IPBase):
96 argtype = 'const union sockunion *'
97 decl = Template('''union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;''')
98 code = Template('''\
99if (argv[_i]->text[0] == 'X') {
100 s__$varname.sa.sa_family = AF_INET6;
101 _fail = !inet_pton(AF_INET6, argv[_i]->arg, &s__$varname.sin6.sin6_addr);
102 $varname = &s__$varname;
103} else {
104 s__$varname.sa.sa_family = AF_INET;
105 _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
106 $varname = &s__$varname;
107}''')
108
109def mix_handlers(handlers):
110 def combine(a, b):
111 if a is None:
112 return b
113 return a.combine(b)
114 return reduce(combine, handlers, None)
115
116handlers = {
117 'WORD_TKN': StringHandler,
118 'VARIABLE_TKN': StringHandler,
119 'RANGE_TKN': LongHandler,
120 'IPV4_TKN': IP4Handler,
121 'IPV4_PREFIX_TKN': Prefix4Handler,
122 'IPV6_TKN': IP6Handler,
123 'IPV6_PREFIX_TKN': Prefix6Handler,
124}
125
126# core template invoked for each occurence of DEFPY.
127templ = Template('''/* $fnname => "$cmddef" */
128DEFUN_CMD_FUNC_DECL($fnname)
129#define funcdecl_$fnname static int ${fnname}_magic(\\
130 const struct cmd_element *self __attribute__ ((unused)),\\
131 struct vty *vty __attribute__ ((unused)),\\
132 int argc __attribute__ ((unused)),\\
133 struct cmd_token *argv[] __attribute__ ((unused))$argdefs)
134funcdecl_$fnname;
135DEFUN_CMD_FUNC_TEXT($fnname)
136{
137 int _i;
138 unsigned _fail = 0, _failcnt = 0;
139$argdecls
140 for (_i = 0; _i < argc; _i++) {
141 if (!argv[_i]->varname)
142 continue;
143 _fail = 0;$argblocks
144 if (_fail)
8e25c8ce
QY
145 vty_outln (vty, "%% invalid input for %s: %s",
146 argv[_i]->varname, argv[_i]->arg);
5578a14d
DL
147 _failcnt += _fail;
148 }
149 if (_failcnt)
150 return CMD_WARNING;
151 return ${fnname}_magic(self, vty, argc, argv$arglist);
152}
153
154''')
155
156# invoked for each named parameter
157argblock = Template('''
158 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
159 $code
160 }''')
161
162def process_file(fn, ofd, dumpfd, all_defun):
163 filedata = clippy.parse(fn)
164
165 for entry in filedata['data']:
166 if entry['type'] == 'DEFPY' or (all_defun and entry['type'].startswith('DEFUN')):
167 cmddef = entry['args'][2]
168 for i in cmddef:
169 assert i.startswith('"') and i.endswith('"')
170 cmddef = ''.join([i[1:-1] for i in cmddef])
171
172 graph = clippy.Graph(cmddef)
173 args = OrderedDict()
174 for token, depth in clippy.graph_iterate(graph):
175 if token.type not in handlers:
176 continue
177 if token.varname is None:
178 continue
179 arg = args.setdefault(token.varname, [])
180 arg.append(handlers[token.type](token))
181
182 #print('-' * 76)
183 #pprint(entry)
184 #clippy.dump(graph)
185 #pprint(args)
186
187 params = { 'cmddef': cmddef, 'fnname': entry['args'][0][0] }
188 argdefs = []
189 argdecls = []
190 arglist = []
191 argblocks = []
192 doc = []
193
194 def do_add(handler, varname, attr = ''):
195 argdefs.append(',\\\n\t%s %s%s' % (handler.argtype, varname, attr))
196 argdecls.append('\t%s\n' % (handler.decl.substitute({'varname': varname}).replace('\n', '\n\t')))
197 arglist.append(', %s%s' % (handler.deref, varname))
198 if attr == '':
199 at = handler.argtype
200 if not at.startswith('const '):
201 at = '. . . ' + at
202 doc.append('\t%-26s %s' % (at, varname))
203
204 for varname in args.keys():
205 handler = mix_handlers(args[varname])
206 #print(varname, handler)
207 if handler is None: continue
208 do_add(handler, varname)
209 code = handler.code.substitute({'varname': varname}).replace('\n', '\n\t\t\t')
210 strblock = ''
211 if not handler.drop_str:
212 do_add(StringHandler(None), '%s_str' % (varname), ' __attribute__ ((unused))')
213 strblock = '\n\t\t\t%s_str = argv[_i]->arg;' % (varname)
214 argblocks.append(argblock.substitute({'varname': varname, 'strblock': strblock, 'code': code}))
215
216 if dumpfd is not None:
217 if len(arglist) > 0:
218 dumpfd.write('"%s":\n%s\n\n' % (cmddef, '\n'.join(doc)))
219 else:
220 dumpfd.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef))
221
222 params['argdefs'] = ''.join(argdefs)
223 params['argdecls'] = ''.join(argdecls)
224 params['arglist'] = ''.join(arglist)
225 params['argblocks'] = ''.join(argblocks)
226 ofd.write(templ.substitute(params))
227
228if __name__ == '__main__':
229 import argparse
230
231 argp = argparse.ArgumentParser(description = 'FRR CLI preprocessor in Python')
232 argp.add_argument('--all-defun', action = 'store_const', const = True,
233 help = 'process DEFUN() statements in addition to DEFPY()')
234 argp.add_argument('--show', action = 'store_const', const = True,
235 help = 'print out list of arguments and types for each definition')
236 argp.add_argument('-o', type = str, metavar = 'OUTFILE',
237 help = 'output C file name')
238 argp.add_argument('cfile', type = str)
239 args = argp.parse_args()
240
241 dumpfd = None
242 if args.o is not None:
243 ofd = StringIO()
244 if args.show:
245 dumpfd = sys.stdout
246 else:
247 ofd = sys.stdout
248 if args.show:
249 dumpfd = sys.stderr
250
251 process_file(args.cfile, ofd, dumpfd, args.all_defun)
252
253 if args.o is not None:
254 clippy.wrdiff(args.o, ofd)