]> git.proxmox.com Git - mirror_frr.git/blob - python/clidef.py
Merge pull request #813 from opensourcerouting/newline-redux
[mirror_frr.git] / python / clidef.py
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
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
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
29 class 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
40 class 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
46 class LongHandler(RenderHandler):
47 argtype = 'long'
48 decl = Template('long $varname = 0;')
49 code = Template('''\
50 char *_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
58 class 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 = '&'
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 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.
80 class 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)
87 class 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);')
91 class 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);')
95 class IPGenHandler(IPBase):
96 argtype = 'const union sockunion *'
97 decl = Template('''union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;''')
98 code = Template('''\
99 if (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
109 def 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
116 handlers = {
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.
127 templ = Template('''/* $fnname => "$cmddef" */
128 DEFUN_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)
134 funcdecl_$fnname;
135 DEFUN_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)
145 vty_out (vty, "%% invalid input for %s: %s\\n",
146 argv[_i]->varname, argv[_i]->arg);
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
157 argblock = Template('''
158 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
159 $code
160 }''')
161
162 def 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
228 if __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)