File:  [LON-CAPA] / loncom / ConfigFileEdit.pm
Revision 1.1: download - view: text, annotated - select for diffs
Tue Nov 25 12:09:57 2003 UTC (20 years, 5 months ago) by foxr
Branches: MAIN
CVS tags: version_1_0_99, HEAD
Configuration file editor:  This module maintains a file consisting of colon
separated fields as an array of lines that is indexed by a single selected field
The indexing is done by maintaining a hash of index value -> line array index.
This allows keyed lookups in constant time as well as editing that preserves
lines with comments and so on.  Still to do:
- Write modified file.
See the cfgedittests directory for the test suite associated with this module.
Please add to those tests if you add functionality.

    1: #
    2: #
    3: #
    4: # Copyright Michigan State University Board of Trustees
    5: #
    6: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    7: #
    8: # LON-CAPA is free software; you can redistribute it and/or modify
    9: # it under the terms of the GNU General Public License as published by
   10: # the Free Software Foundation; either version 2 of the License, or
   11: # (at your option) any later version.
   12: #
   13: # LON-CAPA is distributed in the hope that it will be useful,
   14: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   15: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   16: # GNU General Public License for more details.
   17: #
   18: # You should have received a copy of the GNU General Public License
   19: # along with LON-CAPA; if not, write to the Free Software
   20: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   21: #
   22: # /home/httpd/html/adm/gpl.txt
   23: #
   24: # http://www.lon-capa.org/
   25: #
   26: 
   27: package ConfigFileEdit;
   28: 
   29: #
   30: #   Module to read/edit configuration files.
   31: #   See the POD at the bottom of the file for more information.
   32: 
   33: #------------------------------ internal utility functions ----------
   34: 
   35: # 
   36: # Comment 
   37: #   Returns true if the line is completely a comment.
   38: # Paramter:
   39: #    line  
   40: #        Contents of a configuration file line.
   41: #
   42: sub Comment {
   43:     my $line = shift;
   44: 
   45:     # Leading whitespace followed by a #..
   46: 
   47:     if ($line =~ /^[' ',\t]*\#/) {
   48: 	return 1;
   49:     }
   50:     # Solely whitespace or empty  line.
   51: 
   52:     $line =~ s/[' ',\t]//g;
   53:     return ($line eq "");
   54: 
   55: }
   56: 
   57: #
   58: #  Field
   59: #    Return the value of a field in the line.  Leading whitespace is trimmed
   60: #    from the first key (key 0).
   61: #  Parameters:
   62: #     line 
   63: #        Line from which to extract the field.
   64: #
   65: #     idx
   66: #        Index of the field to extract.
   67: #
   68: sub Field {
   69:     my $line = shift;
   70:     my $idx  = shift;
   71: 
   72:     $line =~ s/(^ *)|(^\t*)//;
   73: 
   74:     my @fields = split(/:/, $line);
   75: 
   76:     return $fields[$idx];
   77: }
   78: #
   79: #   Index:
   80: #      Return a reference to a hash that indexes a line array.
   81: #      The hash is keyed on a field in the line array lines
   82: #      Each hash entry is the line number of the line in which 
   83: #      that key value appears.  Note that at present, keys must be
   84: #      unique.
   85: #  Parameters:
   86: #      $array    - Reference to a line array.
   87: #      $idxfield - Field number to index on (0 is the first field).
   88: #  Returns:
   89: #    Reference to the index hash:
   90: sub Index {
   91:     my $array     = shift;
   92:     my $idxfield  = shift;
   93:    
   94:     my %hash;
   95:     for(my $l = 0; $l < scalar(@$array); $l++) {
   96: 	chomp $array->[$l];	# Ensure lines have no \n's.
   97: 	my $line = $array->[$l];
   98: 	if(!Comment($line)) {
   99: 	    my $keyvalue = Field($line, $idxfield);
  100: 	    $hash{$keyvalue} = $l;
  101: 	}
  102:     }
  103: 
  104: 
  105:     return \%hash;
  106: }
  107: 
  108: 
  109: #------------------------------- public functions --------------------
  110: #
  111: #   new
  112: #     Create a new configuration file editor object.
  113: #     configuration files are : separated fields that 
  114: #     may have comments, blank lines and trailing comments.
  115: #     comments are indicated by #"s.
  116: #   Parameters:
  117: #     filename 
  118: #            Name of file to open.
  119: #     indexfield
  120: #            Select the field to index the file by.
  121: #
  122: # 
  123: sub new {
  124:     my $class      = shift;
  125:     my $filename   = shift;
  126:     my $indexfield = shift;
  127: 
  128:     # Open the configuration file.  Failure results in the return
  129:     # of an undef.
  130:     # Note we dont' need to hold on to the file handle after the file
  131:     # is read in.
  132: 
  133:     open(CONFIGFILE, "< $filename") 
  134: 	or return undef;
  135: 
  136: 
  137:     #   Read the file into a line array:
  138: 
  139:     my @linearray = <CONFIGFILE>;
  140:     close(CONFIGFILE);
  141:     
  142:     
  143:     #  Build the key to lines hash: this hash
  144:     #  is keyed on item $indexfield of the line
  145:     #  and contains the line number of the actual line.
  146: 
  147:     my $hashref = Index(\@linearray, $indexfield);
  148: 
  149: 
  150:     #   Build the object hash, bless it and return.
  151: 
  152:     my $self       = { Filename   => $filename,
  153: 		       Indexfield => $indexfield,
  154: 		       LineArray  => \@linearray,
  155: 		       KeyToLines => $hashref};
  156: 
  157:     bless ($self, $class);
  158: 
  159:     return $self;
  160:     
  161: }
  162: #
  163: #   Append an element to the configuration file array.
  164: #   The element is placed at the end of the array. If the element is not
  165: #   a comment. The key is added to the index.
  166: #
  167: #   Parameters:
  168: #      $self     - Reference to our member hash.
  169: #      $line     - A line to add to the config file.
  170: sub Append { 
  171:     my $self    = shift;
  172:     my $line    = shift;
  173: 
  174:     #   Regardless, the line is added to the config file.
  175: 
  176:     my $linearray = ($self->{LineArray});
  177:     push(@$linearray, $line);	                     # Append the line.
  178:     my $newindex = @$linearray - 1;                  # Index of new line.
  179: 
  180:     #   If the line is not a comment, pull out the desired field and add
  181:     #   it to the line index hash.
  182: 
  183:     if(!Comment($line)) {
  184: 	my $field = Field($line, $self->{Indexfield});
  185: 	$self->{KeyToLines}->{$field} = $newindex;
  186:     }
  187: }
  188: #
  189: #   Find a non comment line by looking it up by key.  
  190: #  Parameters:
  191: #     $self  - Reference to our member hash.
  192: #     $key   - Lookup key.
  193: #  Returns:
  194: #     Contents of the line or undef if there is no match.
  195: #
  196: sub Find {
  197:     my $self    = shift;
  198:     my $key     = shift;
  199: 
  200:     my $hash    = $self->{KeyToLines};
  201:     if(defined($hash->{$key})) {
  202: 	my $lines   = $self->{LineArray};
  203: 	return $lines->[$hash->{$key}];
  204:     } else {
  205: 	return undef;
  206:     }
  207: }
  208: #
  209: #   Return the number of lines in the current configuration file.
  210: #   Note that this count includes the comment lines.  To
  211: #   Get the non comment lines the best thing is to iterate through the
  212: #   keys of the KeyToLines hash.
  213: #  Parameters:
  214: #    $self     - Reference to member data hash for the object.
  215: #
  216: sub LineCount {
  217:     my $self  = shift;
  218:     my $lines = $self->{LineArray};
  219:     my $count = @$lines;
  220:     return $count;
  221: }
  222: #
  223: #   Delete a line from the configuration file.
  224: #   Note at present, there is no support for deleting comment lines.
  225: #   The line is deleted, from the array.  All lines following are slid back
  226: #   one index and the index hash is rebuilt.
  227: # Parameters:
  228: #   $self     - Reference to the member data hash for the object.
  229: #   $key      - key value of the line to delete.
  230: # NOTE:
  231: #   If a line matching this key does not exist, this is a no-op.
  232: #
  233: sub DeleteLine {
  234:     my $self     = shift;
  235:     my $key      = shift;
  236: 
  237:     my $lines    = $self->{LineArray};
  238:     my $index    = $self->{KeyToLines};
  239:     my $lastidx  = $self->LineCount() - 1;   # Index of last item.
  240: 
  241: 
  242:     my @temp = @$lines;
  243: 
  244: 
  245:     if(! defined($index->{$key})) {           # bail if no match.
  246: 	return;
  247:     }
  248:     my $itemno   = $index->{$key}; # Index of item to delete.
  249: 
  250:     if ($itemno != $lastidx) {               # need to slide and reindex.
  251: 	@$temp[$itemno..($lastidx-1)] =
  252: 	    @$temp[$itemno+1..$lastidx];
  253: 	$#temp = $lastidx - 1;
  254: 	$self->{KeyToLines} = Index(\@temp, $self->{Indexfield});
  255:     } else {			             # just need to truncate
  256: 	$#temp = $lastidx-1;		             # the line array...
  257: 	delete($index->{$key});	             # and remove from index hash.
  258:     }
  259:     $self->{LineArray} = \@temp;
  260: 
  261: 
  262: }
  263: #
  264: #   Replace a line in the configuration file:
  265: #   The line is looked up by index.
  266: #   The line is replaced by the one passed in... note if the line
  267: #   is a comment, the index is just deleted!!
  268: #   The index for the line is replaced with the new value of the key field
  269: #  (it's possible the key field changed).
  270: # 
  271: #  Parameters:
  272: #     $self          - Reference to the object's member data hash.
  273: #     $key           - Lookup key.
  274: #     $line          - New line.
  275: # NOTE:
  276: #  If there is no line with the key $key, this reduces to an append.
  277: #
  278: sub ReplaceLine {
  279:     my $self       = shift;
  280:     my $key        = shift;
  281:     my $line       = shift;
  282: 
  283:     my $hashref  = $self->{KeyToLines};
  284:     if(!defined $hashref->{$key}) {
  285: 	$self->Append($line); 
  286:     } else {
  287: 	my $l     = $hashref->{$key};
  288: 	my $lines = $self->{LineArray};
  289: 	$lines->[$l] = $line;	          # Replace old line.
  290: 	delete $hashref->{$key};          # get rid of the old index.
  291: 	if(!Comment($line)) {	          # Index this line only if not comment!
  292: 	    my $newkey = Field($line, $self->{Indexfield});
  293: 	    $hashref->{$newkey} = $l;
  294: 	}
  295:     }
  296: }
  297: 1;
  298: 

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>