]> git.proxmox.com Git - mirror_frr.git/blob - python/clidef.py
Merge pull request #1037 from donaldsharp/eigrp_split_horizon
[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 isinstance(other, PrefixBase):
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 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);')
82
83 # same for IP addresses. result is union sockunion.
84 class IPBase(RenderHandler):
85 def combine(self, other):
86 if type(self) == type(other):
87 return 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;''')
102 code = Template('''\
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;
107 } else {
108 s__$varname.sa.sa_family = AF_INET;
109 _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
110 $varname = &s__$varname;
111 }''')
112
113 def mix_handlers(handlers):
114 def combine(a, b):
115 if a is None:
116 return b
117 return a.combine(b)
118 return reduce(combine, handlers, None)
119
120 handlers = {
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,
130 }
131
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)
140 funcdecl_$fnname;
141 DEFUN_CMD_FUNC_TEXT($fnname)
142 {
143 int _i;
144 unsigned _fail = 0, _failcnt = 0;
145 $argdecls
146 for (_i = 0; _i < argc; _i++) {
147 if (!argv[_i]->varname)
148 continue;
149 _fail = 0;$argblocks
150 if (_fail)
151 vty_out (vty, "%% invalid input for %s: %s\\n",
152 argv[_i]->varname, argv[_i]->arg);
153 _failcnt += _fail;
154 }
155 if (_failcnt)
156 return CMD_WARNING;
157 return ${fnname}_magic(self, vty, argc, argv$arglist);
158 }
159
160 ''')
161
162 # invoked for each named parameter
163 argblock = Template('''
164 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
165 $code
166 }''')
167
168 def process_file(fn, ofd, dumpfd, all_defun):
169 filedata = clippy.parse(fn)
170
171 for entry in filedata['data']:
172 if entry['type'] == 'DEFPY' or (all_defun and entry['type'].startswith('DEFUN')):
173 cmddef = entry['args'][2]
174 for i in cmddef:
175 assert i.startswith('"') and i.endswith('"')
176 cmddef = ''.join([i[1:-1] for i in cmddef])
177
178 graph = clippy.Graph(cmddef)
179 args = OrderedDict()
180 for token, depth in clippy.graph_iterate(graph):
181 if token.type not in handlers:
182 continue
183 if token.varname is None:
184 continue
185 arg = args.setdefault(token.varname, [])
186 arg.append(handlers[token.type](token))
187
188 #print('-' * 76)
189 #pprint(entry)
190 #clippy.dump(graph)
191 #pprint(args)
192
193 params = { 'cmddef': cmddef, 'fnname': entry['args'][0][0] }
194 argdefs = []
195 argdecls = []
196 arglist = []
197 argblocks = []
198 doc = []
199
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))
204 if attr == '':
205 at = handler.argtype
206 if not at.startswith('const '):
207 at = '. . . ' + at
208 doc.append('\t%-26s %s' % (at, varname))
209
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')
216 strblock = ''
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}))
221
222 if dumpfd is not None:
223 if len(arglist) > 0:
224 dumpfd.write('"%s":\n%s\n\n' % (cmddef, '\n'.join(doc)))
225 else:
226 dumpfd.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef))
227
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))
233
234 if __name__ == '__main__':
235 import argparse
236
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()
246
247 dumpfd = None
248 if args.o is not None:
249 ofd = StringIO()
250 if args.show:
251 dumpfd = sys.stdout
252 else:
253 ofd = sys.stdout
254 if args.show:
255 dumpfd = sys.stderr
256
257 process_file(args.cfile, ofd, dumpfd, args.all_defun)
258
259 if args.o is not None:
260 clippy.wrdiff(args.o, ofd, [args.cfile, os.path.realpath(__file__)])