]> git.proxmox.com Git - mirror_frr.git/blob - tools/xml2cli.pl
tools: update .gitignore
[mirror_frr.git] / tools / xml2cli.pl
1 #!/usr/bin/perl
2 ##
3 ## Parse a XML file containing a tree-like representation of Quagga CLI
4 ## commands and generate a file with:
5 ##
6 ## - a DEFUN function for each command;
7 ## - an initialization function.
8 ##
9 ##
10 ## Copyright (C) 2012 Renato Westphal <renatow@digistar.com.br>
11 ## This file is part of GNU Zebra.
12 ##
13 ## GNU Zebra is free software; you can redistribute it and/or modify it
14 ## under the terms of the GNU General Public License as published by the
15 ## Free Software Foundation; either version 2, or (at your option) any
16 ## later version.
17 ##
18 ## GNU Zebra is distributed in the hope that it will be useful, but
19 ## WITHOUT ANY WARRANTY; without even the implied warranty of
20 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 ## General Public License for more details.
22 ##
23 ## You should have received a copy of the GNU General Public License
24 ## along with GNU Zebra; see the file COPYING. If not, write to the Free
25 ## Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
26 ## 02111-1307, USA.
27 ##
28
29 use strict;
30 use warnings;
31 use Getopt::Std;
32 use vars qw($opt_d);
33 use File::Basename qw(fileparse);
34 use XML::LibXML;
35
36 %::input_strs = (
37 "ifname" => "IFNAME",
38 "word" => "WORD",
39 "line" => ".LINE",
40 "ipv4" => "A.B.C.D",
41 "ipv4m" => "A.B.C.D/M",
42 "ipv6" => "X:X::X:X",
43 "ipv6m" => "X:X::X:X/M",
44 "mtu" => "(1500-9180)",
45 "acl_range" => "(1-199)",
46 "acl_expanded_range" => "(1300-2699)",
47 # BGP specific
48 "rd" => "ASN:nn_or_IP-address:nn",
49 "asn" => "(1-4294967295)",
50 "community" => "AA:NN",
51 "clist" => "(1-500)",
52 # LDP specific
53 "disc_time" => "(1-65535)",
54 "session_time" => "(15-65535)",
55 "pwid" => "(1-4294967295)",
56 "hops" => "(1-254)"
57 );
58
59 # parse options node and store the corresponding information
60 # into a global hash of hashes
61 sub parse_options {
62 my $xml_node = $_[0];
63 my @cmdstr;
64
65 my $options_name = $xml_node->findvalue('./@name');
66 if (not $options_name) {
67 die('error: "options" node without "name" attribute');
68 }
69
70 # initialize hash
71 $::options{$options_name}{'cmdstr'} = "";
72 $::options{$options_name}{'help'} = "";
73
74 my @children = $xml_node->getChildnodes();
75 foreach my $child(@children) {
76 # skip comments, random text, etc
77 if ($child->getType() != XML_ELEMENT_NODE) {
78 next;
79 }
80
81 # check for error/special conditions
82 if ($child->getName() ne "option") {
83 die('error: invalid node type: "' . $child->getName() . '"');
84 }
85
86 my $name = $child->findvalue('./@name');
87 my $input = $child->findvalue('./@input');
88 my $help = $child->findvalue('./@help');
89 if ($input) {
90 $name = $::input_strs{$input};
91 }
92
93 push (@cmdstr, $name);
94 $::options{$options_name}{'help'} .= "\n \"" . $help . "\\n\"";
95 }
96 $::options{$options_name}{'cmdstr'} = "<" . join('|', @cmdstr) . ">";
97 }
98
99 # given a subtree, replace all the corresponding include nodes by
100 # this subtree
101 sub subtree_replace_includes {
102 my $subtree = $_[0];
103
104 my $subtree_name = $subtree->findvalue('./@name');
105 if (not $subtree_name) {
106 die("subtree without \"name\" attribute");
107 }
108
109 my $query = "//include[\@subtree='$subtree_name']";
110 foreach my $include_node($::xml->findnodes($query)) {
111 my @children = $subtree->getChildnodes();
112 foreach my $child(reverse @children) {
113 my $include_node_parent = $include_node->getParentNode();
114 $include_node_parent->insertAfter($child->cloneNode(1),
115 $include_node);
116 }
117 $include_node->unbindNode();
118 }
119 $subtree->unbindNode();
120 }
121
122 # generate arguments for a given command
123 sub generate_arguments {
124 my @nodes = @_;
125 my $arguments;
126 my $no_args = 1;
127 my $argc = -1;
128
129 $arguments .= " struct vty_arg *args[] =\n";
130 $arguments .= " {\n";
131 for (my $i = 0; $i < @nodes; $i++) {
132 my %node = %{$nodes[$i]};
133 my $arg_value;
134
135 $argc++;
136 if (not $node{'arg'}) {
137 next;
138 }
139 $no_args = 0;
140
141 # for input and select nodes, the value of the argument is an
142 # argv[] element. for the other types of nodes, the value of the
143 # argument is the name of the node
144 if ($node{'input'} or $node{'type'} eq "select") {
145 $arg_value = "argv[" . $argc . "]->arg";
146 } elsif ($node{'optional'}) {
147 $arg_value = "(argc > " . $argc . " ? argv[" . $argc. "]->arg : NULL)";
148 } else {
149 $arg_value = '"' . $node{'name'} . '"';
150 }
151
152 if ($node{'input'} and $node{'input'} eq "line") {
153 # arguments of the type 'line' may have multiple spaces (i.e
154 # they don't fit into a single argv[] element). to properly
155 # handle these arguments, we need to provide direct access
156 # to the argv[] array and the argc variable.
157 my $argc_str = "argc" . (($argc > 1) ? " - " . ($argc - 1) : "");
158 my $argv_str = "argv" . (($argc > 1) ? " + " . ($argc - 1) : "");
159 $arguments .= " &(struct vty_arg) { "
160 . ".name = \"" . $node{'arg'} . "\", "
161 . ".argc = $argc_str, "
162 . ".argv = $argv_str },\n";
163 } else {
164 # common case - each argument has a name and a single value
165 $arguments .= " &(struct vty_arg) { "
166 . ".name = \"" . $node{'arg'} . "\", "
167 . ".value = " . $arg_value . " },\n";
168 }
169 }
170 $arguments .= " NULL\n";
171 $arguments .= " };\n";
172
173 # handle special case
174 if ($no_args) {
175 return " struct vty_arg *args[] = { NULL };\n";
176 }
177
178 return $arguments;
179 }
180
181 # generate C code
182 sub generate_code {
183 my @nodes = @_;
184 my $funcname = '';
185 my $cmdstr = '';
186 my $cmdname = '';
187 my $helpstr = '';
188 my $function = '';
189
190 for (my $i = 0; $i < @nodes; $i++) {
191 my %node = %{$nodes[$i]};
192 if ($node{'input'}) {
193 $funcname .= $node{'input'} . " ";
194 $cmdstr .= $::input_strs{$node{'input'}} . " ";
195 $helpstr .= "\n \"" . $node{'help'} . "\\n\"";
196 } elsif ($node{'type'} eq "select") {
197 my $options_name = $node{'options'};
198 $funcname .= $options_name . " ";
199 $cmdstr .= $::options{$options_name}{'cmdstr'} . " ";
200 $helpstr .= $::options{$options_name}{'help'};
201 } else {
202 $funcname .= $node{'name'} . " ";
203 if ($node{'optional'}) {
204 $cmdstr .= "[" . $node{'name'} . "] ";
205 } else {
206 $cmdstr .= $node{'name'} . " ";
207 }
208 $helpstr .= "\n \"" . $node{'help'} . "\\n\"";
209 }
210
211 # update the command string
212 if ($node{'function'} ne "inherited" and $node{'function'}) {
213 $function = $node{'function'};
214 }
215 }
216
217 # rtrim
218 $funcname =~ s/\s+$//;
219 $cmdstr =~ s/\s+$//;
220 # lowercase
221 $funcname = lc($funcname);
222 # replace " " by "_"
223 $funcname =~ tr/ /_/;
224 # replace "-" by "_"
225 $funcname =~ tr/-/_/;
226 # add prefix
227 $funcname = $::cmdprefix . '_' . $funcname;
228
229 # generate DEFUN
230 $cmdname = $funcname . "_cmd";
231
232 # don't generate same command more than once
233 if ($::commands{$cmdname}) {
234 return $cmdname;
235 }
236 $::commands{$cmdname} = "1";
237
238 print STDOUT "DEFUN (" . $funcname . ",\n"
239 . " " . $cmdname . ",\n"
240 . " \"" . $cmdstr . "\","
241 . $helpstr . ")\n"
242 . "{\n"
243 . generate_arguments(@nodes)
244 . " return " . $function . " (vty, args);\n"
245 . "}\n\n";
246
247 return $cmdname;
248 }
249
250 # parse tree node (recursive function)
251 sub parse_tree {
252 # get args
253 my $xml_node = $_[0];
254 my @nodes = @{$_[1]};
255 my $tree_name = $_[2];
256
257 # hash containing all the node attributes
258 my %node;
259 $node{'type'} = $xml_node->getName();
260
261 # check for error/special conditions
262 if ($node{'type'} eq "tree") {
263 goto end;
264 }
265 if ($node{'type'} eq "include") {
266 die('error: can not include "'
267 . $xml_node->findvalue('./@subtree') . '"');
268 }
269 if (not $node{'type'} ~~ [qw(option select)]) {
270 die('error: invalid node type: "' . $node{'type'} . '"');
271 }
272 if ($node{'type'} eq "select") {
273 my $options_name = $xml_node->findvalue('./@options');
274 if (not $options_name) {
275 die('error: "select" node without "name" attribute');
276 }
277 if (not $::options{$options_name}) {
278 die('error: can not find options');
279 }
280 $node{'options'} = $options_name;
281 }
282
283 # get node attributes
284 $node{'name'} = $xml_node->findvalue('./@name');
285 $node{'input'} = $xml_node->findvalue('./@input');
286 $node{'arg'} = $xml_node->findvalue('./@arg');
287 $node{'help'} = $xml_node->findvalue('./@help');
288 $node{'function'} = $xml_node->findvalue('./@function');
289 $node{'ifdef'} = $xml_node->findvalue('./@ifdef');
290 $node{'optional'} = $xml_node->findvalue('./@optional');
291
292 # push node to stack
293 push (@nodes, \%node);
294
295 # generate C code
296 if ($node{'function'}) {
297 my $cmdname = generate_code(@nodes);
298 push (@{$::trees{$tree_name}}, [0, $cmdname, 0]);
299 }
300
301 if ($node{'ifdef'}) {
302 push (@{$::trees{$tree_name}}, [$node{'ifdef'}, 0, 0]);
303 }
304
305 end:
306 # recursively process child nodes
307 my @children = $xml_node->getChildnodes();
308 foreach my $child(@children) {
309 # skip comments, random text, etc
310 if ($child->getType() != XML_ELEMENT_NODE) {
311 next;
312 }
313 parse_tree($child, \@nodes, $tree_name);
314 }
315
316 if ($node{'ifdef'}) {
317 push (@{$::trees{$tree_name}}, [0, 0, $node{'ifdef'}]);
318 }
319 }
320
321 sub parse_node {
322 # get args
323 my $xml_node = $_[0];
324
325 my $node_name = $xml_node->findvalue('./@name');
326 if (not $node_name) {
327 die('missing the "name" attribute');
328 }
329
330 my $install = $xml_node->findvalue('./@install');
331 my $config_write = $xml_node->findvalue('./@config_write');
332 if ($install and $install eq "1") {
333 print " install_node (&" .lc( $node_name) . "_node, " . $config_write . ");\n";
334 }
335
336 my $install_default = $xml_node->findvalue('./@install_default');
337 if ($install_default and $install_default eq "1") {
338 print " install_default (" . $node_name . "_NODE);\n";
339 }
340
341 my @children = $xml_node->getChildnodes();
342 foreach my $child(@children) {
343 # skip comments, random text, etc
344 if ($child->getType() != XML_ELEMENT_NODE) {
345 next;
346 }
347
348 if ($child->getName() ne "include") {
349 die('error: invalid node type: "' . $child->getName() . '"');
350 }
351 my $tree_name = $child->findvalue('./@tree');
352 if (not $tree_name) {
353 die('missing the "tree" attribute');
354 }
355
356 foreach my $entry (@{$::trees{$tree_name}}) {
357 my ($ifdef, $cmdname, $endif) = @{$entry};
358
359 if ($ifdef) {
360 print ("#ifdef " . $ifdef . "\n");
361 }
362
363 if ($cmdname) {
364 print " install_element (" . $node_name . "_NODE, &" . $cmdname . ");\n";
365 }
366
367 if ($endif) {
368 print ("#endif /* " . $endif . " */\n");
369 }
370 }
371 }
372 }
373
374 # parse command-line arguments
375 if (not getopts('d')) {
376 die("Usage: xml2cli.pl [-d] FILE\n");
377 }
378 my $file = shift;
379
380 # initialize the XML parser
381 my $parser = new XML::LibXML;
382 $parser->keep_blanks(0);
383
384 # parse XML file
385 $::xml = $parser->parse_file($file);
386 my $xmlroot = $::xml->getDocumentElement();
387 if ($xmlroot->getName() ne "file") {
388 die('XML root element name must be "file"');
389 }
390
391 # read file attributes
392 my $init_function = $xmlroot->findvalue('./@init');
393 if (not $init_function) {
394 die('missing the "init" attribute in the "file" node');
395 }
396 $::cmdprefix = $xmlroot->findvalue('./@cmdprefix');
397 if (not $::cmdprefix) {
398 die('missing the "cmdprefix" attribute in the "file" node');
399 }
400 my $header = $xmlroot->findvalue('./@header');
401 if (not $header) {
402 die('missing the "header" attribute in the "file" node');
403 }
404
405 # generate source header
406 print STDOUT "/* Auto-generated from " . fileparse($file) . ". */\n"
407 . "/* Do not edit! */\n\n"
408 . "#include <zebra.h>\n\n"
409 . "#include \"command.h\"\n"
410 . "#include \"vty.h\"\n"
411 . "#include \"$header\"\n\n";
412
413 # Parse options
414 foreach my $options($::xml->findnodes("/file/options")) {
415 parse_options($options);
416 }
417
418 # replace include nodes by the corresponding subtrees
419 foreach my $subtree(reverse $::xml->findnodes("/file/subtree")) {
420 subtree_replace_includes($subtree);
421 }
422
423 # Parse trees
424 foreach my $tree($::xml->findnodes("/file/tree")) {
425 my @nodes = ();
426 my $tree_name = $tree->findvalue('./@name');
427 parse_tree($tree, \@nodes, $tree_name);
428 }
429
430 # install function header
431 print STDOUT "void\n"
432 . $init_function . " (void)\n"
433 . "{\n";
434
435 # Parse nodes
436 foreach my $node($::xml->findnodes("/file/node")) {
437 parse_node($node);
438 }
439
440 # closing braces for the install function
441 print STDOUT "}";
442
443 # print to stderr the expanded XML file if the debug flag (-d) is given
444 if ($opt_d) {
445 print STDERR $::xml->toString(1);
446 }