package Build::Config; #----------------------------------------------------------------------------------------------- # # SYNOPSIS # # use Build::Config; # # # read the configuration definition and defaults xml files. # my $cfg = Build::Config->new("config_definition.xml", "config_setup.xml"); # # # set configuration parameters # $cfg->set('dyn', 'fv'); # $cfg->set('hgrid', '2x2.5'); # # # query configuration parameters # my $exe = $cfg->get('cam_exe'); # # # write a configuration cache file # $cfg->write_file("config_cache.xml", "configure commandline"); # # DESCRIPTION # # Build::Config objects are used to represent features of a model # configuration that must be specified at build time. # # new() Reads xml files that contain the configuration definition and # default values of the configuration parameters. # # The "config_definition.xml" file contains all the allowable # parameters with a description of each one. Where appropriate a # list of valid values of a parameter is given. Generic default # values that are useful across many model configurations are # provided for some parameters. # # The generic default values that are provided in the definition file # may be overridden by values in a setup file ("config_setup.xml") # that is assumed to provide appropriate values for a specific model # configuration. This setup file is optional. # # ======================== # Query methods: # ======================== # # is_valid_name() Returns true if the specified parameter name is contained in # the configuration definition file. # # is_valid_value() Returns true if the specified parameter name is contained in # the configuration definition file, and either 1) the specified value is # listed as a valid_value in the definition file, or 2) the definition file # doesn't specify the valid values. # # get() Return the value of the specified configuration parameter. Triggers # an exception if the parameter name is not valid. # ***NOTE*** If you don't want to trap exceptions, then use the query # functions before calling this routine. # # get_names() Return the list of valid names in the configuration definition file. # # get_valid_values( ) Returns list of valid values for the specified parameter name. # # ======================== # Put and I/O methods: # ======================== # # set() Sets values of the configuration parameters. It takes the # parameter name and its value as arguments. An invalid parameter # name (i.e., a name not present in the definition file) triggers an # exception. If the definition file contains valid values of a # parameter, then the set method checks for a valid input value. If # and invalid value is found then an exception is thrown. # ***NOTE*** If you don't want to trap exceptions, then use the query # functions before calling this routine. # # write_file() Write a configuration xml file. The first argument is the # filename. The second argument, if present, is the commandline of the # configure command that was invoked to produce the output configuration # file. It is written to the output file to help document the procedure # used to configure the executable. # # print() Print the configuration to STDOUT. # # COLLABORATORS # # IO::File # XML::Lite #----------------------------------------------------------------------------------------------- # # Date Author Modification #----------------------------------------------------------------------------------------------- # 2007-Aug Erik Kluzek Add get_names method # 2006-Nov Brian Eaton Original version #----------------------------------------------------------------------------------------------- use strict; #use warnings; #use diagnostics; use IO::File; use XML::Lite; sub new { my $class = shift; my ($definition_file, $default_file) = @_; # bless the object here so the initialization has access to object methods my $cfg = {}; bless( $cfg, $class ); # Initialize the object with the configuration definition and its initial setup. if ( defined($definition_file) ) { $cfg->_initialize($definition_file, $default_file); } else { die "ERROR: $class new method requires a definition file\n"; } return $cfg; } #----------------------------------------------------------------------------------------------- sub is_valid_name { # Return true if the requested name is contained in the configuration definition. my ($self, $name) = @_; return defined($self->{$name}) ? 1 : 0; } #----------------------------------------------------------------------------------------------- sub get { # Return requested value. my ($self, $name) = @_; defined($self->{$name}) or die "ERROR: unknown parameter name: $name\n"; return $self->{$name}->{'value'}; } #----------------------------------------------------------------------------------------------- sub get_names { # Return list of valid names. my ($self) = @_; my @names = sort( keys( %$self ) ); return @names; } #----------------------------------------------------------------------------------------------- sub is_valid_value { # Return true if the specified parameter name is contained in # the configuration definition file, and either 1) the specified value is # listed as a valid_value in the definition file, or 2) the definition file # doesn't specify the valid values. my ($self, $id, $value) = @_; # Check that the parameter name is in the configuration definition unless ($self->is_valid_name($id)) { return 0; } # Check that a list value is not supplied when parameter takes a scalar value. my $is_list_value = $self->{$id}->{'list'}; unless ($is_list_value) { # this conditional is satisfied when the list attribute is false, i.e., for scalars if ($value =~ /.*,.*/) { return 0; } # the pattern matches when $value contains a comma, i.e., is a list } # Check that the value is valid my @valid_values = $self->get_valid_values( $id ); if ( @valid_values ) { # if no valid values are specified, then $value is automatically valid if ($is_list_value) { unless (_list_value_ok($value, @valid_values)) { return 0; } } else { unless (_value_ok($value, @valid_values)) { return 0; } } } return 1; } #----------------------------------------------------------------------------------------------- sub get_valid_values { # Return list of valid values for a given id my ($self, $id) = @_; my @return_array; # Check that the parameter name is in the configuration definition if ($self->is_valid_name($id)) { my $valid_values = $self->{$id}->{'valid_values'}; if ( $valid_values ) { @return_array = split /,/, $valid_values; #/ } } return( @return_array ); } #----------------------------------------------------------------------------------------------- sub set { # Set requested value. # # This routine handles errors by throwing exceptions. It will report exactly what problem was # found in either the parameter name or requested value. # # To avoid dealing with exceptions use the is_valid_name(), is_valid_value() methods to get a # true/false return before calling the set method. my ($self, $id, $value) = @_; # Check that the parameter name is in the configuration definition $self->is_valid_name($id) or die "ERROR: parameter name $id is not in the configuration definition\n"; # Check that the value is valid my $valid_values = $self->{$id}->{'valid_values'}; $self->is_valid_value($id, $value) or die "ERROR: $value is not a valid value for parameter $id: valid values are $valid_values\n"; # Add the new value to the object's internal data structure. $self->{$id}->{'value'} = $value; return 1; } #----------------------------------------------------------------------------------------------- sub write_file { # Write a configuration definition file. my ($self, $filename, $commandline) = @_; my $fh = IO::File->new($filename, '>') or die "** can't open file: $filename\n"; # head of the xml file print $fh <<"EOD"; EOD # add commandline if present if (defined $commandline) { print $fh <<"EOD"; $commandline EOD } # add the entry elements my @ids = keys %$self; foreach my $id (sort @ids) { print $fh <<"EOD"; $self->{$id}->{'definition'} EOD } # tail of the xml file print $fh <<"EOD"; EOD } #----------------------------------------------------------------------------------------------- sub print { # Print the configuration to STDOUT. my ($self) = @_; my @ids = keys %$self; foreach my $id (sort @ids) { printf "%12s = %s\n", $id, $self->{$id}->{'value'}; } } #----------------------------------------------------------------------------------------------- # Private methods #----------------------------------------------------------------------------------------------- sub _initialize { # Read the configuration definition file. Create an anonymous hash with the following # structure: # { id1 => {value => "xxx", list => "XXX", valid_values => "yyy", definition => "zzz"}, # id2 => {value => "xxx", list => "XXX", valid_values => "yyy", definition => "zzz"}, # ... # idn => {value => "xxx", list => "XXX", valid_values => "yyy", definition => "zzz"}, # } my ($self, $definition_file, $setup_file) = @_; # Process the definition file my $xml = XML::Lite->new( $definition_file ); my $root = $xml->root_element(); # Check for valid root node my $name = $root->get_name(); $name eq "config_definition" or die "ERROR: $definition_file is not a configuration definition file\n"; # Each parameter is contained in an "entry" element. Get all these elements. my @elements = $xml->elements_by_name('entry'); # Loop over the elements... foreach my $e (@elements) { # and extract the attributes and element content. my %attributes = $e->get_attributes(); my $content = $e->get_content(); # if present strip initial and final newlines from content $content =~ s/^\n{1}//; $content =~ s/\n{1}$//; # Look for the specific attributes that are contained in the configuration definition. my $id = $attributes{'id'}; my $value = $attributes{'value'}; my $list = $attributes{'list'}; my $valid_values = defined $attributes{'valid_values'} ? $attributes{'valid_values'} : ""; # Now add the attributes and content to the object's internal data structure. $self->{$id} = {'value' => $value, 'list' => $list, 'valid_values' => $valid_values, 'definition' => $content}; } # Process the setup file if (defined $setup_file) { my $xml = XML::Lite->new( $setup_file ); my $root = $xml->root_element(); # Check for valid root node my $name = $root->get_name(); $name eq "config_definition" or die "ERROR: $definition_file is not a configuration definition file\n"; # Each parameter is contained in an "entry" element. Get all these elements. @elements = (); @elements = $xml->elements_by_name('entry'); # Loop over the elements... foreach my $e (@elements) { # and extract the attributes my %attributes = $e->get_attributes(); # just get the parameter name and value my $id = $attributes{'id'}; my $value = $attributes{'value'}; # set new value $self->set($id, $value); } } # end processing setup file } #----------------------------------------------------------------------------------------------- sub _list_value_ok { # Check that all input values ($values_in may be a comma separated list) # are contained in the list of valid values (@valid_values). # Return 1 (true) if all input values are valid, Otherwise return 0 (false). my ($values_in, @valid_values) = @_; my @values = split /,/, $values_in; #/ my $num_vals = scalar(@values); my $values_ok = 0; foreach my $value (@values) { if (_value_ok($value, @valid_values)) { ++$values_ok; } } ($num_vals == $values_ok) ? return 1 : return 0; } #----------------------------------------------------------------------------------------------- sub _value_ok { # Check that the input value is contained in list of # valid values (@valid_values). Return 1 (true) if input value is valid, # Otherwise return 0 (false). my ($value, @valid_values) = @_; # If the valid value list is null, all values are valid. unless (@valid_values) { return 1; } $value =~ s/^\s+//; $value =~ s/\s+$//; foreach my $expect (@valid_values) { if ($value =~ /^$expect$/i) { return 1; } } return 0; } #----------------------------------------------------------------------------------------------- #----------------------------------------------------------------------------------------------- 1; # to make use or require happy