]>
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)
41 class StringHandler(RenderHandler
):
42 argtype
= 'const char *'
43 decl
= Template('const char *$varname = NULL;')
44 code
= Template('$varname = (argv[_i]->type == WORD_TKN) ? argv[_i]->text : argv[_i]->arg;')
48 class LongHandler(RenderHandler
):
50 decl
= Template('long $varname = 0;')
53 $varname = strtol(argv[_i]->arg, &_end, 10);
54 _fail = (_end == argv[_i]->arg) || (*_end != '\\0');''')
56 # A.B.C.D/M (prefix_ipv4) and
57 # X:X::X:X/M (prefix_ipv6) are "compatible" and can merge into a
60 class PrefixBase(RenderHandler
):
61 def combine(self
, other
):
62 if type(self
) == type(other
):
64 if isinstance(other
, PrefixBase
):
65 return PrefixGenHandler(None)
66 return StringHandler(None)
68 class Prefix4Handler(PrefixBase
):
69 argtype
= 'const struct prefix_ipv4 *'
70 decl
= Template('struct prefix_ipv4 $varname = { };')
71 code
= Template('_fail = !str2prefix_ipv4(argv[_i]->arg, &$varname);')
72 class Prefix6Handler(PrefixBase
):
73 argtype
= 'const struct prefix_ipv6 *'
74 decl
= Template('struct prefix_ipv6 $varname = { };')
75 code
= Template('_fail = !str2prefix_ipv6(argv[_i]->arg, &$varname);')
76 class PrefixEthHandler(PrefixBase
):
77 argtype
= 'struct prefix_eth *'
78 decl
= Template('struct prefix_eth $varname = { };')
79 code
= Template('_fail = !str2prefix_eth(argv[_i]->arg, &$varname);')
80 class PrefixGenHandler(PrefixBase
):
81 argtype
= 'const struct prefix *'
82 decl
= Template('struct prefix $varname = { };')
83 code
= Template('_fail = !str2prefix(argv[_i]->arg, &$varname);')
85 # same for IP addresses. result is union sockunion.
86 class IPBase(RenderHandler
):
87 def combine(self
, other
):
88 if type(self
) == type(other
):
90 if type(other
) in [IP4Handler
, IP6Handler
, IPGenHandler
]:
91 return IPGenHandler(None)
92 return StringHandler(None)
93 class 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);')
97 class IP6Handler(IPBase
):
98 argtype
= 'struct in6_addr'
99 decl
= Template('struct in6_addr $varname = {};')
100 code
= Template('_fail = !inet_pton(AF_INET6, argv[_i]->arg, &$varname);')
101 class IPGenHandler(IPBase
):
102 argtype
= 'const union sockunion *'
103 decl
= Template('''union sockunion s__$varname = { .sa.sa_family = AF_UNSPEC }, *$varname = NULL;''')
105 if (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;
110 s__$varname.sa.sa_family = AF_INET;
111 _fail = !inet_aton(argv[_i]->arg, &s__$varname.sin.sin_addr);
112 $varname = &s__$varname;
115 def mix_handlers(handlers
):
120 return reduce(combine
, handlers
, None)
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
,
130 'MAC_TKN': PrefixEthHandler
,
131 'MAC_PREFIX_TKN': PrefixEthHandler
,
134 # core template invoked for each occurence of DEFPY.
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.
139 templ
= Template('''/* $fnname => "$cmddef" */
140 DEFUN_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)
147 DEFUN_CMD_FUNC_TEXT($fnname)
149 #if $nonempty /* anything to parse? */
151 #if $canfail /* anything that can fail? */
152 unsigned _fail = 0, _failcnt = 0;
155 for (_i = 0; _i < argc; _i++) {
156 if (!argv[_i]->varname)
158 #if $canfail /* anything that can fail? */
162 #if $canfail /* anything that can fail? */
164 vty_out (vty, "%% invalid input for %s: %s\\n",
165 argv[_i]->varname, argv[_i]->arg);
169 #if $canfail /* anything that can fail? */
174 return ${fnname}_magic(self, vty, argc, argv$arglist);
179 # invoked for each named parameter
180 argblock
= Template('''
181 if (!strcmp(argv[_i]->varname, \"$varname\")) {$strblock
185 def process_file(fn
, ofd
, dumpfd
, all_defun
):
186 filedata
= clippy
.parse(fn
)
188 for entry
in filedata
['data']:
189 if entry
['type'].startswith('DEFPY') or (all_defun
and entry
['type'].startswith('DEFUN')):
190 cmddef
= entry
['args'][2]
191 cmddef
= ''.join([i
[1:-1] for i
in cmddef
])
193 graph
= clippy
.Graph(cmddef
)
195 for token
, depth
in clippy
.graph_iterate(graph
):
196 if token
.type not in handlers
:
198 if token
.varname
is None:
200 arg
= args
.setdefault(token
.varname
, [])
201 arg
.append(handlers
[token
.type](token
))
208 params
= { 'cmddef': cmddef
, 'fnname': entry
['args'][0][0] }
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
))
222 if not at
.startswith('const '):
224 doc
.append('\t%-26s %s' % (at
, varname
))
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')
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
}))
240 if dumpfd
is not None:
242 dumpfd
.write('"%s":\n%s\n\n' % (cmddef
, '\n'.join(doc
)))
244 dumpfd
.write('"%s":\n\t---- no magic arguments ----\n\n' % (cmddef
))
246 params
['argdefs'] = ''.join(argdefs
)
247 params
['argdecls'] = ''.join(argdecls
)
248 params
['arglist'] = ''.join(arglist
)
249 params
['argblocks'] = ''.join(argblocks
)
250 params
['canfail'] = canfail
251 params
['nonempty'] = len(argblocks
)
252 ofd
.write(templ
.substitute(params
))
254 if __name__
== '__main__':
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()
268 if args
.o
is not None:
277 process_file(args
.cfile
, ofd
, dumpfd
, args
.all_defun
)
279 if args
.o
is not None:
280 clippy
.wrdiff(args
.o
, ofd
, [args
.cfile
, os
.path
.realpath(__file__
), sys
.executable
])