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