Rewrote the configuration file parser generator.

Converted from Perl to Python for a better
and cleaner object oriented design.
This commit is contained in:
Jan Vidar Krey 2013-04-22 21:58:06 +02:00
parent 22292e493e
commit cf9be754aa
4 changed files with 269 additions and 291 deletions

View File

@ -1,289 +0,0 @@
#!/usr/bin/perl -w
use strict;
use XML::DOM;
sub write_c_header(@);
sub write_sql_dump(@);
sub get_data($);
my $dump_to_sql = 0;
# initialize parser and read the file
my $input = "./config.xml";
my $parser = new XML::DOM::Parser;
my $tree = $parser->parsefile($input) || die "Unable to parse XML file.";
# Get data
my $nodes = $tree->getElementsByTagName("option");
my @options = ();
for (my $i = 0; $i < $nodes->getLength; $i++)
{
my @data = get_data($nodes->item($i));
push @options, \@data;
}
write_c_header(@options);
write_sql_dump(@options) if ($dump_to_sql);
my $config_defaults = "void config_defaults(struct hub_config* config)\n{\n";
my $config_apply = "static int apply_config(struct hub_config* config, char* key, char* data, int line_count)\n{\n\tint max = 0;\n\tint min = 0;\n\n";
my $config_free = "void free_config(struct hub_config* config)\n{\n";
my $config_dump = "void dump_config(struct hub_config* config, int ignore_defaults)\n{\n";
foreach my $option (@options)
{
my ($type, $name, $default, $advanced, $short, $desc, $since, $example, $check, $ifdef) = @$option;
my $string = ($type =~ /(string|file|message)/);
my $min = undef;
my $max = undef;
my $regexp = undef;
if (defined $check)
{
$min = $check->getAttribute("min");
$max = $check->getAttribute("max");
$regexp = $check->getAttribute("regexp");
$max = undef if ($max eq "");
$min = undef if ($min eq "");
$regexp = undef if ($regexp eq "");
}
$config_defaults .= "#ifdef $ifdef\n" if ($ifdef ne "");
$config_defaults .= "\tconfig->$name = ";
$config_defaults .= "hub_strdup(\"" if ($string);
$config_defaults .= $default;
$config_defaults .= "\")" if ($string);
$config_defaults .= ";\n";
$config_defaults .= "#endif /* $ifdef */\n" if ($ifdef ne "");
$config_apply .= "#ifdef $ifdef\n" if ($ifdef ne "");
$config_apply .= "\tif (!strcmp(key, \"" . $name . "\"))\n\t{\n";
if ($type eq "int")
{
$config_apply .= "\t\tmin = $min;\n" if (defined $min);
$config_apply .= "\t\tmax = $max;\n" if (defined $max);
$config_apply .= "\t\tif (!apply_integer(key, data, &config->$name, ";
if (defined $min) { $config_apply .= "&min"; } else { $config_apply .= "0"; }
$config_apply .= ", ";
if (defined $max) { $config_apply .= "&max"; } else { $config_apply .= "0"; }
$config_apply .= "))\n";
}
elsif ($type eq "boolean")
{
$config_apply .= "\t\tif (!apply_boolean(key, data, &config->$name))\n";
}
elsif ($string)
{
$config_apply .="\t\tif (!apply_string(key, data, &config->$name, (char*) \"\"))\n";
}
$config_apply .= "\t\t{\n" .
"\t\t\tLOG_ERROR(\"Configuration parse error on line %d\", line_count);\n" .
"\t\t\treturn -1;\n" .
"\t\t}\n" .
"\t\treturn 0;\n" .
"\t}\n";
$config_apply .= "#endif /* $ifdef */\n" if ($ifdef ne "");
$config_apply .= "\n";
if ($string)
{
$config_free .= "#ifdef $ifdef\n" if ($ifdef ne "");
$config_free .= "\thub_free(config->" . $name . ");\n";
$config_free .= "#endif /* $ifdef */\n" if ($ifdef ne "");
$config_free .= "\n";
}
my $out = "%s";
my $val = "config->$name";
my $test = "config->$name != $default";
$out = "%d" if ($type eq "int");
$val = "config->$name ? \"yes\" : \"no\"" if ($type eq "boolean");
if ($string)
{
$out = "\\\"%s\\\"";
$test = "strcmp(config->$name, \"$default\") != 0";
}
$config_dump .= "#ifdef $ifdef\n" if ($ifdef ne "");
$config_dump .= "\tif (!ignore_defaults || $test)\n";
$config_dump .= "\t\tfprintf(stdout, \"$name = $out\\n\", $val);\n";
$config_dump .= "#endif /* $ifdef */\n" if ($ifdef ne "");
$config_dump .= "\n";
}
$config_apply .= "\t/* Still here -- unknown directive */\n";
$config_apply .= "\tLOG_ERROR(\"Unknown configuration directive: '%s'\", key);\n";
$config_apply .= "\treturn -1;\n";
$config_apply .= "}\n\n";
$config_defaults .= "}\n\n";
$config_free .= "}\n\n";
$config_dump .= "}\n\n";
open GENIMPL, ">gen_config.c" || die "Unable to write source file";
print GENIMPL "/* THIS FILE IS AUTOGENERATED - DO NOT CHANGE IT! */\n\n";
print GENIMPL $config_defaults;
print GENIMPL $config_apply;
print GENIMPL $config_free;
print GENIMPL $config_dump;
sub get_data($)
{
my $p = shift;
my $short = "";
my $example = "";
my $description = "";
my $since = "";
my $ifdef = "";
$short = $p->getElementsByTagName("short")->item(0)->getFirstChild()->getData() if ($p->getElementsByTagName("short")->getLength());
$since = $p->getElementsByTagName("since")->item(0)->getFirstChild()->getData() if ($p->getElementsByTagName("since")->getLength());
$example = $p->getElementsByTagName("example")->item(0)->getFirstChild()->getData() if ($p->getElementsByTagName("example")->getLength());
$description = $p->getElementsByTagName("description")->item(0)->getFirstChild()->getData() if ($p->getElementsByTagName("description")->getLength());
my $check = $p->getElementsByTagName("check")->item(0);
$ifdef = $p->getElementsByTagName("ifdef")->item(0)->getFirstChild()->getData() if ($p->getElementsByTagName("ifdef")->getLength());
my @data = (
$p->getAttribute("type"),
$p->getAttribute("name"),
$p->getAttribute("default"),
$p->getAttribute("advanced"),
$short,
$description,
$since,
$example,
$check,
$ifdef
);
return @data;
}
# Write header file
sub write_c_header(@)
{
my @data = @_;
open GENHEAD, ">gen_config.h" || die "Unable to write header file";
print GENHEAD "/* THIS FILE IS AUTOGENERATED - DO NOT CHANGE IT! */\n\n";
print GENHEAD "struct hub_config\n{\n";
foreach my $option (@data)
{
my ($type, $name, $default, $advanced, $short, $desc, $since, $example, $check, $ifdef) = @$option;
my $string = ($type =~ /(string|file|message)/);
print GENHEAD "#ifdef $ifdef\n" if ($ifdef ne "");
print GENHEAD "\t";
print GENHEAD "int " if ($type eq "int");
print GENHEAD "int " if ($type eq "boolean");
print GENHEAD "char*" if ($string);
print GENHEAD " " . $name . ";";
my $comment = "";
if ($type eq "message")
{
$comment = "\"" . $default . "\"";
}
elsif (defined $short && length $short > 0)
{
$comment = $short;
if (defined $default)
{
$comment .= " (default: ";
$comment .= "\"" if ($string);
$comment .= $default;
$comment .= "\"" if ($string);
$comment .= ")";
}
}
if (length $comment > 0)
{
my $pad = "";
for (my $i = length $name; $i < 32; $i++)
{
$pad .= " ";
}
$comment = $pad . "/*<<< " . $comment . " */";
}
print GENHEAD $comment . "\n";
print GENHEAD "#endif /* $ifdef */\n" if ($ifdef ne "");
}
print GENHEAD "};\n\n";
}
sub write_sql_dump(@)
{
my @data = @_;
# Write SQL dump code
open GENSQL, ">gen_config.sql" || die "Unable to write SQL dump";
print GENSQL "START TRANSACTION;\n\n
DROP TABLE uhub_config IF EXISTS;\n\n
CREATE TABLE uhub_config (
name VARCHAR(32) UNIQUE NOT NULL,
defaultValue TINYTEXT NOT NULL,
description LONGTEXT NOT NULL,
type TINYTEXT NOT NULL,
advanced BOOLEAN,
example LONGTEXT,
since TINYTEXT
);\n\n";
foreach my $option (@data)
{
my ($type, $name, $default, $advanced, $short, $desc, $since, $example, $check, $ifdef) = @$option;
if ($type =~ /(string|file|message)/ )
{
$default = "\\\"$default\\\"";
}
$desc =~ s/\"/\\\"/g;
$type =~ s/^int$/integer/;
my $stmt = "INSERT INTO uhub_config VALUES(";
$stmt .= "\"$name\", ";
$stmt .= "\"$default\", ";
$stmt .= "\"$desc\", ";
$stmt .= "\"$type\", ";
if (defined $example)
{
my $example_str = $example;
$example_str =~ s/\\/\\\\/g;
$example_str =~ s/\"/\\\"/g;
$stmt .= "\"$example_str\", ";
} else {
$stmt .= "NULL, ";
}
if (defined $since) {
$stmt .= "\"$since\", ";
} else {
$stmt .= "NULL, ";
}
if (defined $advanced) {
$stmt .= "\"$advanced\"";
} else {
$stmt .= "NULL";
}
$stmt .= ");\n";
print GENSQL $stmt;
}
print GENSQL "\n\nCOMMIT;\n\n";
}

255
src/core/config.py Executable file
View File

@ -0,0 +1,255 @@
#!/usr/bin/env python
"""
uhub - A tiny ADC p2p connection hub
Copyright (C) 2007-2013, Jan Vidar Krey
"""
from xml.dom import minidom, Node
from datetime import datetime
import argparse
class OptionParseError(Exception):
pass
class Option(object):
def _get(self, node, name):
self.__dict__[name] = None
if (node.getElementsByTagName(name)):
self.__dict__[name] = node.getElementsByTagName(name)[0].firstChild.nodeValue
def _attr(self, node, name, required = False):
try:
return node.attributes[name].value
except Exception:
pass
if (required):
raise OptionParseError("Option %s is required but not found!" % name)
return None
def __init__(self, node):
self.otype = self._attr(node, 'type', True)
# Verify that the type is known
if not self.otype in ["int", "boolean", "string", "message", "file"]:
raise OptionParseError("Option %s has unknown type" % self.name)
self.name = self._attr(node, 'name', True)
self.default = self._attr(node, 'default', True)
self.advanced = self._attr(node, 'advanced', False)
self.is_string = self.otype in ["string", "message", "file"]
self._get(node, "short");
self._get(node, "description");
self._get(node, "syntax");
self._get(node, "since");
self._get(node, "example");
check = node.getElementsByTagName("check")
if (check):
check = node.getElementsByTagName("check")[0]
self.check_min = self._attr(check, 'min', False)
self.check_max = self._attr(check, 'max', False)
self.check_regexp = self._attr(check, 'regexp', False)
else:
self.check_min = None
self.check_max = None
self.check_regexp = None
def c_type(self):
if self.otype == "boolean":
return "int"
elif self.is_string:
return "char*"
else:
return self.otype
def sql_type(self):
if self.otype == "int":
return "integer"
return self.otype
def c_comment(self):
comment = ""
if (self.otype == "message"):
comment = self.formatted_default()
elif len(self.short):
comment = "%s (default: %s)" % (self.short, self.formatted_default())
return comment
def formatted_default(self):
if self.is_string:
return "\"%s\"" % self.default
return self.default
class SourceGenerator(object):
def __init__(self, filename, cppStyle = True):
print "Generating %s..." % filename
self.f = open(filename, 'w');
def write_header(self, Comment = True):
if Comment:
s = "/*\n * uhub - A tiny ADC p2p connection hub\n"
s += " * Copyright (C) 2007-%s, Jan Vidar Krey\n *\n" % datetime.now().strftime("%Y")
s += " * THIS FILE IS AUTOGENERATED - DO NOT MODIFY\n"
s += " * Created %s, by config.py\n */\n\n" % datetime.now().strftime("%Y-%m-%d %H:%M")
self.f.write(s)
class CHeaderGenerator(SourceGenerator):
def __init__(self, filename):
super(CHeaderGenerator, self).__init__(filename)
def _write_declaration(self, option):
comment = ' ' * (32 - len(option.name)) + "/*<<< %s */" % option.c_comment()
ptype = option.c_type() + (5 - len(option.c_type())) * ' '
self.f.write("\t%(type)s %(name)s;%(comment)s\n" % {
"type": ptype,
"name": option.name,
"comment": comment})
def write(self, options):
self.write_header()
self.f.write("struct hub_config\n{\n")
for option in options:
self._write_declaration(option)
self.f.write("};\n\n")
class CSourceGenerator(SourceGenerator):
def __init__(self, filename):
super(CSourceGenerator, self).__init__(filename)
def _write_default_impl(self, option):
s = "\tconfig->%s = " % option.name
if option.is_string:
s += "hub_strdup(%s);\n" % option.formatted_default()
else:
s += option.formatted_default() + ";\n"
self.f.write(s)
def _write_apply_impl(self, option):
s = "\tif (!strcmp(key, \"%s\"))\n\t{\n" % option.name
if option.otype == "int":
s_min = "0"
s_max = "0"
if (option.check_min):
s += "\t\tmin = %s;\n" % option.check_min
s_min = "&min"
if (option.check_max):
s += "\t\tmax = %s;\n" % option.check_max
s_max = "&max"
s+= "\t\tif (!apply_integer(key, data, &config->%s, %s, %s))\n" % (option.name, s_min, s_max)
elif option.otype == "boolean":
s += "\t\tif (!apply_boolean(key, data, &config->%s))\n" % option.name
elif option.is_string:
s += "\t\tif (!apply_string(key, data, &config->%s, (char*) \"\"))\n" % option.name
s += "\t\t{\n\t\t\tLOG_ERROR(\"Configuration parse error on line %d\", line_count);\n\t\t\treturn -1;\n\t\t}\n\t\treturn 0;\n\t}\n\n"
self.f.write(s)
def _write_free_impl(self, option):
if option.is_string:
self.f.write("\thub_free(config->%s);\n\n" % option.name)
def _write_dump_impl(self, option):
s = ""
fmt = "%s"
val = "config->%s" % option.name
test = "config->%s != %s" % (option.name, option.default)
if (option.otype == "int"):
fmt = "%d"
elif (option.otype == "boolean"):
val = "config->%s ? \"yes\" : \"no\"" % option.name
elif (option.is_string):
fmt = "\\\"%s\\\"";
test = "strcmp(config->%s, %s) != 0" % (option.name, option.formatted_default())
s += "\tif (!ignore_defaults || %s)\n" % test;
s += "\t\tfprintf(stdout, \"%s = %s\\n\", %s);\n\n" % (option.name, fmt, val)
self.f.write(s)
def write(self, options):
self.write_header()
self.f.write("void config_defaults(struct hub_config* config)\n{\n")
for option in options:
self._write_default_impl(option)
self.f.write("}\n\n")
self.f.write("static int apply_config(struct hub_config* config, char* key, char* data, int line_count)\n{\n\tint max = 0;\n\tint min = 0;\n\n")
for option in options:
self._write_apply_impl(option)
self.f.write("\t/* Still here -- unknown directive */\n\tLOG_ERROR(\"Unknown configuration directive: '%s'\", key);\n\treturn -1;\n}\n\n")
self.f.write("void free_config(struct hub_config* config)\n{\n")
for option in options:
self._write_free_impl(option)
self.f.write("}\n\n")
self.f.write("void dump_config(struct hub_config* config, int ignore_defaults)\n{\n")
for option in options:
self._write_dump_impl(option)
self.f.write("}\n\n")
class SqlWebsiteDocsGenerator(SourceGenerator):
def __init__(self, filename, sqlite_support = False):
self.sqlite_support = sqlite_support
super(SqlWebsiteDocsGenerator, self).__init__(filename)
def _sql_escape(self, s):
if self.sqlite_support:
return s.replace("\"", "\"\"")
return s.replace("\"", "\\\"")
def _write_or_null(self, s):
if (not s or len(s) == 0):
return "NULL"
return "\"%s\"" % self._sql_escape(s)
def write(self, options):
self.write_header(False)
table = "uhub_config"
s = ""
if not self.sqlite_support:
s += "START TRANSACTION;\n\nDROP TABLE %(table)s IF EXISTS;" % { "table": table }
s += "\n\nCREATE TABLE %(table)s (\n\tname VARCHAR(32) UNIQUE NOT NULL,\n\tdefaultValue TINYTEXT NOT NULL,\n\tdescription LONGTEXT NOT NULL,\n\ttype TINYTEXT NOT NULL,\n\tadvanced BOOLEAN,\n\texample LONGTEXT,\n\tsince TINYTEXT\n);\n\n" % { "table": table }
self.f.write(s)
for option in options:
s = "INSERT INTO %(table)s VALUES(\"%(name)s\", \"%(default)s\", \"%(description)s\", \"%(type)s\", %(example)s, %(since)s, %(advanced)s);\n" % {
"table": table,
"name": self._sql_escape(option.name),
"default": self._sql_escape(option.formatted_default()),
"description": self._sql_escape(option.description),
"type": option.sql_type(),
"example": self._write_or_null(option.example),
"since": self._write_or_null(option.since),
"advanced": self._write_or_null(option.example),
}
self.f.write(s)
if not self.sqlite_support:
self.f.write("\n\nCOMMIT;\n\n")
if __name__ == "__main__":
# parser = argparse.ArgumentParser(description = "Configuration file parser and source generator")
# parser.add_argument("--in", nargs=1, type=argparse.FileType('r'), default="config.xml", help="Input file (config.xml)", required = True)
# parser.add_argument("--c-decl", nargs=1, type=argparse.FileType('w'), default="gen_config.h", help="Output file for C declarations (gen_config.h)")
# parser.add_argument("--c-impl", nargs=1, type=argparse.FileType('w'), default="gen_config.c", help="Output file for C implementation (gen_config.c)")
# parser.add_argument("--doc-sql", nargs=1, type=argparse.FileType('w'), help="Output file for SQL documentation")
# args = parser.parse_args()
xmldoc = minidom.parse("./config.xml")
opt_tags = xmldoc.getElementsByTagName('option')
options = []
for option in opt_tags:
opt = Option(option)
options.append(opt)
header = CHeaderGenerator("./gen_config.h");
header.write(options);
source = CSourceGenerator("./gen_config.c");
source.write(options);
#sql = SqlWebsiteDocsGenerator("./gen_config.sql", True);
#sql.write(options);

View File

@ -1,4 +1,10 @@
/* THIS FILE IS AUTOGENERATED - DO NOT CHANGE IT! */
/*
* uhub - A tiny ADC p2p connection hub
* Copyright (C) 2007-2013, Jan Vidar Krey
*
* THIS FILE IS AUTOGENERATED - DO NOT MODIFY
* Created 2013-04-22 21:57, by config.py
*/
void config_defaults(struct hub_config* config)
{

View File

@ -1,4 +1,10 @@
/* THIS FILE IS AUTOGENERATED - DO NOT CHANGE IT! */
/*
* uhub - A tiny ADC p2p connection hub
* Copyright (C) 2007-2013, Jan Vidar Krey
*
* THIS FILE IS AUTOGENERATED - DO NOT MODIFY
* Created 2013-04-22 21:57, by config.py
*/
struct hub_config
{