diff options
author | Renato Westphal <renato@opensourcerouting.org> | 2016-03-01 19:31:28 +0100 |
---|---|---|
committer | Donald Sharp <sharpd@cumulusnetworks.com> | 2016-09-23 15:31:09 +0200 |
commit | eac6e3f027356c25a8c8fddf921f769b79945fcc (patch) | |
tree | 69d5a17fb3f95934bafa22f8a3aee28c92780d62 /tools | |
parent | ldpd: sun is a reserved word on Solaris (diff) | |
download | frr-eac6e3f027356c25a8c8fddf921f769b79945fcc.tar.xz frr-eac6e3f027356c25a8c8fddf921f769b79945fcc.zip |
ldpd: adapt the code for Quagga
Signed-off-by: Renato Westphal <renato@opensourcerouting.org>
Diffstat (limited to 'tools')
-rwxr-xr-x | tools/xml2cli.pl | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/tools/xml2cli.pl b/tools/xml2cli.pl new file mode 100755 index 000000000..43789131c --- /dev/null +++ b/tools/xml2cli.pl @@ -0,0 +1,436 @@ +#!/usr/bin/perl +## +## Parse a XML file containing a tree-like representation of Quagga CLI +## commands and generate a file with: +## +## - a DEFUN function for each command; +## - an initialization function. +## +## +## Copyright (C) 2012 Renato Westphal <renatow@digistar.com.br> +## This file is part of GNU Zebra. +## +## GNU Zebra is free software; you can redistribute it and/or modify it +## under the terms of the GNU General Public License as published by the +## Free Software Foundation; either version 2, or (at your option) any +## later version. +## +## GNU Zebra is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with GNU Zebra; see the file COPYING. If not, write to the Free +## Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +## 02111-1307, USA. +## + +use strict; +use warnings; +use Getopt::Std; +use vars qw($opt_d); +use File::Basename qw(fileparse); +use XML::LibXML; + +%::input_strs = ( + "ifname" => "IFNAME", + "word" => "WORD", + "line" => ".LINE", + "ipv4" => "A.B.C.D", + "ipv4m" => "A.B.C.D/M", + "ipv6" => "X:X::X:X", + "ipv6m" => "X:X::X:X/M", + "mtu" => "<1500-9180>", + # BGP specific + "rd" => "ASN:nn_or_IP-address:nn", + "asn" => "<1-4294967295>", + "community" => "AA:NN", + "clist" => "<1-500>", + # LDP specific + "disc_time" => "<1-65535>", + "session_time" => "<15-65535>", + "pwid" => "<1-4294967295>", + "hops" => "<1-254>" + ); + +# parse options node and store the corresponding information +# into a global hash of hashes +sub parse_options { + my $xml_node = $_[0]; + my @cmdstr; + + my $options_name = $xml_node->findvalue('./@name'); + if (not $options_name) { + die('error: "options" node without "name" attribute'); + } + + # initialize hash + $::options{$options_name}{'cmdstr'} = ""; + $::options{$options_name}{'help'} = ""; + + my @children = $xml_node->getChildnodes(); + foreach my $child(@children) { + # skip comments, random text, etc + if ($child->getType() != XML_ELEMENT_NODE) { + next; + } + + # check for error/special conditions + if ($child->getName() ne "option") { + die('error: invalid node type: "' . $child->getName() . '"'); + } + + my $name = $child->findvalue('./@name'); + my $input = $child->findvalue('./@input'); + my $help = $child->findvalue('./@help'); + if ($input) { + $name = $::input_strs{$input}; + } + + push (@cmdstr, $name); + $::options{$options_name}{'help'} .= "\n \"" . $help . "\\n\""; + } + $::options{$options_name}{'cmdstr'} = "(" . join('|', @cmdstr) . ")"; +} + +# given a subtree, replace all the corresponding include nodes by +# this subtree +sub subtree_replace_includes { + my $subtree = $_[0]; + + my $subtree_name = $subtree->findvalue('./@name'); + if (not $subtree_name) { + die("subtree without \"name\" attribute"); + } + + my $query = "//include[\@subtree='$subtree_name']"; + foreach my $include_node($::xml->findnodes($query)) { + my @children = $subtree->getChildnodes(); + foreach my $child(reverse @children) { + my $include_node_parent = $include_node->getParentNode(); + $include_node_parent->insertAfter($child->cloneNode(1), + $include_node); + } + $include_node->unbindNode(); + } + $subtree->unbindNode(); +} + +# generate arguments for a given command +sub generate_arguments { + my @nodes = @_; + my $arguments; + my $no_args = 1; + my $argc = 0; + + $arguments .= " struct vty_arg *args[] =\n"; + $arguments .= " {\n"; + for (my $i = 0; $i < @nodes; $i++) { + my %node = %{$nodes[$i]}; + my $arg_value; + + if (not $node{'arg'}) { + next; + } + $no_args = 0; + + # for input and select nodes, the value of the argument is an + # argv[] element. for the other types of nodes, the value of the + # argument is the name of the node + if ($node{'input'} or $node{'type'} eq "select") { + $arg_value = "argv[" . $argc++ . "]"; + } else { + $arg_value = '"' . $node{'name'} . '"'; + } + + if ($node{'input'} and $node{'input'} eq "line") { + # arguments of the type 'line' may have multiple spaces (i.e + # they don't fit into a single argv[] element). to properly + # handle these arguments, we need to provide direct access + # to the argv[] array and the argc variable. + my $argc_str = "argc" . (($argc > 1) ? " - " . ($argc - 1) : ""); + my $argv_str = "argv" . (($argc > 1) ? " + " . ($argc - 1) : ""); + $arguments .= " &(struct vty_arg) { " + . ".name = \"" . $node{'arg'} . "\", " + . ".argc = $argc_str, " + . ".argv = $argv_str },\n"; + } else { + # common case - each argument has a name and a single value + $arguments .= " &(struct vty_arg) { " + . ".name = \"" . $node{'arg'} . "\", " + . ".value = " . $arg_value . " },\n"; + } + } + $arguments .= " NULL\n"; + $arguments .= " };\n"; + + # handle special case + if ($no_args) { + return " struct vty_arg *args[] = { NULL };\n"; + } + + return $arguments; +} + +# generate C code +sub generate_code { + my @nodes = @_; + my $funcname = ''; + my $cmdstr = ''; + my $cmdname = ''; + my $helpstr = ''; + my $function = ''; + + for (my $i = 0; $i < @nodes; $i++) { + my %node = %{$nodes[$i]}; + if ($node{'input'}) { + $funcname .= $node{'input'} . " "; + $cmdstr .= $::input_strs{$node{'input'}} . " "; + $helpstr .= "\n \"" . $node{'help'} . "\\n\""; + } elsif ($node{'type'} eq "select") { + my $options_name = $node{'options'}; + $funcname .= $options_name . " "; + $cmdstr .= $::options{$options_name}{'cmdstr'} . " "; + $helpstr .= $::options{$options_name}{'help'}; + } else { + $funcname .= $node{'name'} . " "; + $cmdstr .= $node{'name'} . " "; + $helpstr .= "\n \"" . $node{'help'} . "\\n\""; + } + + # update the command string + if ($node{'function'} ne "inherited") { + $function = $node{'function'}; + } + } + + # rtrim + $funcname =~ s/\s+$//; + $cmdstr =~ s/\s+$//; + # lowercase + $funcname = lc($funcname); + # replace " " by "_" + $funcname =~ tr/ /_/; + # replace "-" by "_" + $funcname =~ tr/-/_/; + # add prefix + $funcname = $::cmdprefix . '_' . $funcname; + + # generate DEFUN + $cmdname = $funcname . "_cmd"; + + # don't generate same command more than once + if ($::commands{$cmdname}) { + return $cmdname; + } + $::commands{$cmdname} = "1"; + + print STDOUT "DEFUN (" . $funcname . ",\n" + . " " . $cmdname . ",\n" + . " \"" . $cmdstr . "\"," + . $helpstr . ")\n" + . "{\n" + . generate_arguments(@nodes) + . " return " . $function . " (vty, args);\n" + . "}\n\n"; + + return $cmdname; +} + +# parse tree node (recursive function) +sub parse_tree { + # get args + my $xml_node = $_[0]; + my @nodes = @{$_[1]}; + my $tree_name = $_[2]; + + # hash containing all the node attributes + my %node; + $node{'type'} = $xml_node->getName(); + + # check for error/special conditions + if ($node{'type'} eq "tree") { + goto end; + } + if ($node{'type'} eq "include") { + die('error: can not include "' + . $xml_node->findvalue('./@subtree') . '"'); + } + if (not $node{'type'} ~~ [qw(option select)]) { + die('error: invalid node type: "' . $node{'type'} . '"'); + } + if ($node{'type'} eq "select") { + my $options_name = $xml_node->findvalue('./@options'); + if (not $options_name) { + die('error: "select" node without "name" attribute'); + } + if (not $::options{$options_name}) { + die('error: can not find options'); + } + $node{'options'} = $options_name; + } + + # get node attributes + $node{'name'} = $xml_node->findvalue('./@name'); + $node{'input'} = $xml_node->findvalue('./@input'); + $node{'arg'} = $xml_node->findvalue('./@arg'); + $node{'help'} = $xml_node->findvalue('./@help'); + $node{'function'} = $xml_node->findvalue('./@function'); + $node{'ifdef'} = $xml_node->findvalue('./@ifdef'); + + # push node to stack + push (@nodes, \%node); + + # generate C code + if ($node{'function'}) { + my $cmdname = generate_code(@nodes); + push (@{$::trees{$tree_name}}, [0, $cmdname, 0]); + } + + if ($node{'ifdef'}) { + push (@{$::trees{$tree_name}}, [$node{'ifdef'}, 0, 0]); + } + +end: + # recursively process child nodes + my @children = $xml_node->getChildnodes(); + foreach my $child(@children) { + # skip comments, random text, etc + if ($child->getType() != XML_ELEMENT_NODE) { + next; + } + parse_tree($child, \@nodes, $tree_name); + } + + if ($node{'ifdef'}) { + push (@{$::trees{$tree_name}}, [0, 0, $node{'ifdef'}]); + } +} + +sub parse_node { + # get args + my $xml_node = $_[0]; + + my $node_name = $xml_node->findvalue('./@name'); + if (not $node_name) { + die('missing the "name" attribute'); + } + + my $install = $xml_node->findvalue('./@install'); + my $config_write = $xml_node->findvalue('./@config_write'); + if ($install and $install eq "1") { + print " install_node (&" .lc( $node_name) . "_node, " . $config_write . ");\n"; + } + + my $install_default = $xml_node->findvalue('./@install_default'); + if ($install_default and $install_default eq "1") { + print " install_default (" . $node_name . "_NODE);\n"; + } + + my @children = $xml_node->getChildnodes(); + foreach my $child(@children) { + # skip comments, random text, etc + if ($child->getType() != XML_ELEMENT_NODE) { + next; + } + + if ($child->getName() ne "include") { + die('error: invalid node type: "' . $child->getName() . '"'); + } + my $tree_name = $child->findvalue('./@tree'); + if (not $tree_name) { + die('missing the "tree" attribute'); + } + + foreach my $entry (@{$::trees{$tree_name}}) { + my ($ifdef, $cmdname, $endif) = @{$entry}; + + if ($ifdef) { + print ("#ifdef " . $ifdef . "\n"); + } + + if ($cmdname) { + print " install_element (" . $node_name . "_NODE, &" . $cmdname . ");\n"; + } + + if ($endif) { + print ("#endif /* " . $endif . " */\n"); + } + } + } +} + +# parse command-line arguments +if (not getopts('d')) { + die("Usage: xml2cli.pl [-d] FILE\n"); +} +my $file = shift; + +# initialize the XML parser +my $parser = new XML::LibXML; +$parser->keep_blanks(0); + +# parse XML file +$::xml = $parser->parse_file($file); +my $xmlroot = $::xml->getDocumentElement(); +if ($xmlroot->getName() ne "file") { + die('XML root element name must be "file"'); +} + +# read file attributes +my $init_function = $xmlroot->findvalue('./@init'); +if (not $init_function) { + die('missing the "init" attribute in the "file" node'); +} +$::cmdprefix = $xmlroot->findvalue('./@cmdprefix'); +if (not $::cmdprefix) { + die('missing the "cmdprefix" attribute in the "file" node'); +} +my $header = $xmlroot->findvalue('./@header'); +if (not $header) { + die('missing the "header" attribute in the "file" node'); +} + +# generate source header +print STDOUT "/* Auto-generated from " . fileparse($file) . ". */\n" + . "/* Do not edit! */\n\n" + . "#include <zebra.h>\n\n" + . "#include \"command.h\"\n" + . "#include \"vty.h\"\n" + . "#include \"$header\"\n\n"; + +# Parse options +foreach my $options($::xml->findnodes("/file/options")) { + parse_options($options); +} + +# replace include nodes by the corresponding subtrees +foreach my $subtree(reverse $::xml->findnodes("/file/subtree")) { + subtree_replace_includes($subtree); +} + +# Parse trees +foreach my $tree($::xml->findnodes("/file/tree")) { + my @nodes = (); + my $tree_name = $tree->findvalue('./@name'); + parse_tree($tree, \@nodes, $tree_name); +} + +# install function header +print STDOUT "void\n" + . $init_function . " (void)\n" + . "{\n"; + +# Parse nodes +foreach my $node($::xml->findnodes("/file/node")) { + parse_node($node); +} + +# closing braces for the install function +print STDOUT "}"; + +# print to stderr the expanded XML file if the debug flag (-d) is given +if ($opt_d) { + print STDERR $::xml->toString(1); +} |