File:  [LON-CAPA] / loncom / ConfigFileEdit.pm
Revision 1.3: download - view: text, annotated - select for diffs
Tue Dec 30 11:28:51 2003 UTC (20 years, 3 months ago) by foxr
Branches: MAIN
CVS tags: version_2_5_X, version_2_5_2, version_2_5_1, version_2_5_0, version_2_4_X, version_2_4_99_0, version_2_4_2, version_2_4_1, version_2_4_0, version_2_3_X, version_2_3_99_0, version_2_3_2, version_2_3_1, version_2_3_0, version_2_2_X, version_2_2_99_1, version_2_2_99_0, version_2_2_2, version_2_2_1, version_2_2_0, version_2_1_X, version_2_1_99_3, version_2_1_99_2, version_2_1_99_1, version_2_1_99_0, version_2_1_3, version_2_1_2, version_2_1_1, version_2_1_0, version_2_0_X, version_2_0_99_1, version_2_0_2, version_2_0_1, version_2_0_0, version_1_99_3, version_1_99_2, version_1_99_1_tmcc, version_1_99_1, version_1_99_0_tmcc, version_1_99_0, version_1_3_X, version_1_3_3, version_1_3_2, version_1_3_1, version_1_3_0, version_1_2_X, version_1_2_99_1, version_1_2_99_0, version_1_2_1, version_1_2_0, version_1_1_99_5, version_1_1_99_4, version_1_1_99_3, version_1_1_99_2, version_1_1_99_1, version_1_1_99_0, HEAD
Add support to get the entire contents of an edited config file as a string.

    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: use IO::File;
   30: 
   31: #
   32: #   Module to read/edit configuration files.
   33: #   See the POD at the bottom of the file for more information.
   34: 
   35: #------------------------------ internal utility functions ----------
   36: 
   37: # 
   38: # Comment 
   39: #   Returns true if the line is completely a comment.
   40: # Paramter:
   41: #    line  
   42: #        Contents of a configuration file line.
   43: #
   44: sub Comment {
   45:     my $line = shift;
   46: 
   47:     # Leading whitespace followed by a #..
   48: 
   49:     if ($line =~ /^[' ',\t]*\#/) {
   50: 	return 1;
   51:     }
   52:     # Solely whitespace or empty  line.
   53: 
   54:     $line =~ s/[' ',\t]//g;
   55:     return ($line eq "");
   56: 
   57: }
   58: 
   59: #
   60: #  Field
   61: #    Return the value of a field in the line.  Leading whitespace is trimmed
   62: #    from the first key (key 0).
   63: #  Parameters:
   64: #     line 
   65: #        Line from which to extract the field.
   66: #
   67: #     idx
   68: #        Index of the field to extract.
   69: #
   70: sub Field {
   71:     my $line = shift;
   72:     my $idx  = shift;
   73: 
   74:     $line =~ s/(^ *)|(^\t*)//;
   75: 
   76:     my @fields = split(/:/, $line);
   77: 
   78:     return $fields[$idx];
   79: }
   80: #
   81: #   Index:
   82: #      Return a reference to a hash that indexes a line array.
   83: #      The hash is keyed on a field in the line array lines
   84: #      Each hash entry is the line number of the line in which 
   85: #      that key value appears.  Note that at present, keys must be
   86: #      unique.
   87: #  Parameters:
   88: #      $array    - Reference to a line array.
   89: #      $idxfield - Field number to index on (0 is the first field).
   90: #  Returns:
   91: #    Reference to the index hash:
   92: sub Index {
   93:     my $array     = shift;
   94:     my $idxfield  = shift;
   95:    
   96:     my %hash;
   97:     for(my $l = 0; $l < scalar(@$array); $l++) {
   98: 	chomp $array->[$l];	# Ensure lines have no \n's.
   99: 	my $line = $array->[$l];
  100: 	if(!Comment($line)) {
  101: 	    my $keyvalue = Field($line, $idxfield);
  102: 	    $hash{$keyvalue} = $l;
  103: 	}
  104:     }
  105: 
  106: 
  107:     return \%hash;
  108: }
  109: 
  110: 
  111: #------------------------------- public functions --------------------
  112: #
  113: #   new
  114: #     Create a new configuration file editor object.
  115: #     configuration files are : separated fields that 
  116: #     may have comments, blank lines and trailing comments.
  117: #     comments are indicated by #"s.
  118: #   Parameters:
  119: #     filename 
  120: #            Name of file to open.
  121: #     indexfield
  122: #            Select the field to index the file by.
  123: #
  124: # 
  125: sub new {
  126:     my $class      = shift;
  127:     my $filename   = shift;
  128:     my $indexfield = shift;
  129: 
  130:     # Open the configuration file.  Failure results in the return
  131:     # of an undef.
  132:     # Note we dont' need to hold on to the file handle after the file
  133:     # is read in.
  134: 
  135:     open(CONFIGFILE, "< $filename") 
  136: 	or return undef;
  137: 
  138: 
  139:     #   Read the file into a line array:
  140: 
  141:     my @linearray = <CONFIGFILE>;
  142:     close(CONFIGFILE);
  143:     
  144:     
  145:     #  Build the key to lines hash: this hash
  146:     #  is keyed on item $indexfield of the line
  147:     #  and contains the line number of the actual line.
  148: 
  149:     my $hashref = Index(\@linearray, $indexfield);
  150: 
  151: 
  152:     #   Build the object hash, bless it and return.
  153: 
  154:     my $self       = { Filename   => $filename,
  155: 		       Indexfield => $indexfield,
  156: 		       LineArray  => \@linearray,
  157: 		       KeyToLines => $hashref};
  158: 
  159:     bless ($self, $class);
  160: 
  161:     return $self;
  162:     
  163: }
  164: #
  165: #   Append an element to the configuration file array.
  166: #   The element is placed at the end of the array. If the element is not
  167: #   a comment. The key is added to the index.
  168: #
  169: #   Parameters:
  170: #      $self     - Reference to our member hash.
  171: #      $line     - A line to add to the config file.
  172: sub Append { 
  173:     my $self    = shift;
  174:     my $line    = shift;
  175: 
  176:     #   Regardless, the line is added to the config file.
  177: 
  178:     my $linearray = ($self->{LineArray});
  179:     push(@$linearray, $line);	                     # Append the line.
  180:     my $newindex = @$linearray - 1;                  # Index of new line.
  181: 
  182:     #   If the line is not a comment, pull out the desired field and add
  183:     #   it to the line index hash.
  184: 
  185:     if(!Comment($line)) {
  186: 	my $field = Field($line, $self->{Indexfield});
  187: 	$self->{KeyToLines}->{$field} = $newindex;
  188:     }
  189: }
  190: #
  191: #   Find a non comment line by looking it up by key.  
  192: #  Parameters:
  193: #     $self  - Reference to our member hash.
  194: #     $key   - Lookup key.
  195: #  Returns:
  196: #     Contents of the line or undef if there is no match.
  197: #
  198: sub Find {
  199:     my $self    = shift;
  200:     my $key     = shift;
  201: 
  202:     my $hash    = $self->{KeyToLines};
  203:     if(defined($hash->{$key})) {
  204: 	my $lines   = $self->{LineArray};
  205: 	return $lines->[$hash->{$key}];
  206:     } else {
  207: 	return undef;
  208:     }
  209: }
  210: #
  211: #   Return the number of lines in the current configuration file.
  212: #   Note that this count includes the comment lines.  To
  213: #   Get the non comment lines the best thing is to iterate through the
  214: #   keys of the KeyToLines hash.
  215: #  Parameters:
  216: #    $self     - Reference to member data hash for the object.
  217: #
  218: sub LineCount {
  219:     my $self  = shift;
  220:     my $lines = $self->{LineArray};
  221:     my $count = @$lines;
  222:     return $count;
  223: }
  224: #
  225: #   Delete a line from the configuration file.
  226: #   Note at present, there is no support for deleting comment lines.
  227: #   The line is deleted, from the array.  All lines following are slid back
  228: #   one index and the index hash is rebuilt.
  229: # Parameters:
  230: #   $self     - Reference to the member data hash for the object.
  231: #   $key      - key value of the line to delete.
  232: # NOTE:
  233: #   If a line matching this key does not exist, this is a no-op.
  234: #
  235: sub DeleteLine {
  236:     my $self     = shift;
  237:     my $key      = shift;
  238: 
  239:     my $lines    = $self->{LineArray};
  240:     my $index    = $self->{KeyToLines};
  241:     my $lastidx  = $self->LineCount() - 1;   # Index of last item.
  242: 
  243: 
  244:     my @temp;
  245: 
  246:     if(! defined($index->{$key})) {           # bail if no match.
  247: 	return;
  248:     }
  249:     my $itemno   = $index->{$key}; # Index of item to delete.
  250: 
  251:     
  252:     if ($itemno != $lastidx) {               # need to slide and reindex.
  253: 	@temp = @$lines[0..($itemno-1)];
  254: 	@temp[$itemno..($lastidx-1)] = @$lines[($itemno+1)..$lastidx];
  255: 	    
  256: 
  257:     } else {			             # just need to truncate
  258: 	@temp = @$lines[0..$lastidx-1];	     # So take the initial slice.
  259:     }
  260: 
  261:     $self->{KeyToLines} = Index(\@temp, $self->{Indexfield});
  262:     $self->{LineArray} = \@temp;             # Replace the lines index. 
  263: 
  264: 
  265: }
  266: #
  267: #   Replace a line in the configuration file:
  268: #   The line is looked up by index.
  269: #   The line is replaced by the one passed in... note if the line
  270: #   is a comment, the index is just deleted!!
  271: #   The index for the line is replaced with the new value of the key field
  272: #  (it's possible the key field changed).
  273: # 
  274: #  Parameters:
  275: #     $self          - Reference to the object's member data hash.
  276: #     $key           - Lookup key.
  277: #     $line          - New line.
  278: # NOTE:
  279: #  If there is no line with the key $key, this reduces to an append.
  280: #
  281: sub ReplaceLine {
  282:     my $self       = shift;
  283:     my $key        = shift;
  284:     my $line       = shift;
  285: 
  286:     my $hashref  = $self->{KeyToLines};
  287:     if(!defined $hashref->{$key}) {
  288: 	$self->Append($line); 
  289:     } else {
  290: 	my $l     = $hashref->{$key};
  291: 	my $lines = $self->{LineArray};
  292: 	$lines->[$l] = $line;	          # Replace old line.
  293: 	delete $hashref->{$key};          # get rid of the old index.
  294: 	if(!Comment($line)) {	          # Index this line only if not comment!
  295: 	    my $newkey = Field($line, $self->{Indexfield});
  296: 	    $hashref->{$newkey} = $l;
  297: 	}
  298:     }
  299: }
  300: #
  301: #   Write the configuration array to a file:
  302: #   Parameters:
  303: #      $self           - Reference to the object's member data hash.
  304: #      $fh              - Name of file to write.
  305: sub Write {
  306:     my $self     = shift;
  307:     my $fh       = shift;
  308:     
  309:     my $lines    = $self->{LineArray};
  310:     my $length   = @$lines;
  311:     for (my $i = 0; $i < $length; $i++) {
  312: 	print $fh $lines->[$i]."\n";
  313:     }   
  314: }
  315: 
  316: #   Get:
  317: #      return the entire contents of the file as a string.
  318: # Parameters:
  319: #    $self      - (this).
  320: #
  321: sub Get {
  322:     my $self    = shift;
  323:     
  324:     my $contents = "";
  325:     my $lines    = $self->{LineArray};
  326:     my $length   = @$lines;
  327: 
  328:     for (my $i = 0; $i < $length; $i++) {
  329: 	$contents .= $lines->[$i]."\n";
  330:     }
  331:     return $contents;
  332: }
  333: 
  334: 1;
  335: #----------------------------- Documentation --------------------------------------
  336: #
  337: 
  338: =pod
  339: 
  340: =head1 NAME
  341: 
  342: ConfigFileEdit - Lookups and edits on a configuration file.
  343: 
  344: =head1 SYNOPSIS
  345: 
  346:     use LONCAPA::ConfigFileEdit;
  347:     use IO::File;
  348: 
  349:     my $editor = ConfigFileEdit->new("file.cfg", 0);
  350:     $editor->Append("new:line:with:config:info");      
  351:     my $line   = $editor->Find("key");
  352:     my $size   = $editor->LineCount();
  353:     $editor->DeleteLine("george");
  354:     $editor->ReplaceLine("new","new:line:with:different:info");  
  355:     my $fh = new IO::File("> modified.cfg", 0);
  356:     $editor->Write($fh);
  357: 
  358: =head1 DESCRIPTION
  359: 
  360: Configuration files in LonCAPA contain lines of colon separated fields.
  361: Configuration files can also contain comments initiated by the hash (#)
  362: character that continue to the end of line.  Finally, configuration files
  363: can be made more readable by the inclusion of either empty lines or 
  364: lines entirely made up of whitespace.
  365: 
  366: ConfigFileEdit allows manipulation of configuration files in a way that
  367: preserves comments and order.  This differs from LonCAPA's 'normal' mechanism
  368: handling configuration files by throwing them up into a hash by key.
  369: 
  370: ConfigFileEdit maintains the original configuration file in an array and
  371: creates the index hash as a hash to line numbers in the array.  This allows
  372: O(k) time lookups, while preserving file order and comments (comments are
  373: lines in the array that have no indices in the associated hash.
  374: 
  375: In addition to line lookup, ConfigFileEdit supports simple editing
  376: functions such as delete, append, and replace.  At present, Insertions
  377: at arbitrary points in the file are not supported.  The modified
  378: file can also be written out.
  379: 
  380: =head1 METHODS
  381: 
  382: =head2 new ( filename, keyfield )
  383: 
  384: Creates  a new ConfigFileEdit object from an existing file.   Where:
  385: 
  386: =over 4
  387: 
  388: =item * filename - The name of the configuration file to open.
  389: 
  390: =item * keyfield - The number of the field for which the index hash is generated.
  391:     Fields are enumerated from zero.
  392: 
  393: =item * RETURNS: - undef if the file could not be open, otherwise a reference
  394:     to a hash that contains the object member data.
  395: 
  396: =back
  397: 
  398: =head2 Append ( newline )
  399: 
  400: Appends a new line to the configuration file in memory.  The file that was
  401: used to create the object is not modified by this operation.
  402: 
  403: =over 4
  404: 
  405: =item * newline - A new line to append to the end of the configurationfile.
  406: 
  407: =back
  408: 
  409: =head2 LineCount 
  410: 
  411: Returns the number of lines in the file.  This count includes the nubmer of
  412: comments.
  413: 
  414: =head2 DeleteLine ( key )
  415: 
  416: Deletes the line that matches key.  Note that if there is no matching line,
  417: this function is a no-op.
  418: 
  419: =over 4
  420: 
  421: =item * key   - The key to match, the line that is indexed by this key is deleted.
  422: 
  423: =back
  424: 
  425: =head2 ReplaceLine ( key, newcontents  )
  426: 
  427: Replaces the selected line in its entirety.  Note that the config file is re-indexed
  428: so it is legal to modify the line's key field.
  429: 
  430: =over 4
  431: 
  432: =item * key    - The key that selects which line is replaced.
  433: 
  434: =item * newcontents - The new contents of the line.
  435: 
  436: =back
  437: 
  438: =head2 Write ( fh )
  439: 
  440: Writes the contents of the configuration file's line array to file.
  441: 
  442: =over 4
  443: 
  444: =item * fh   - A file handle that is open for write on the destination file.
  445: 
  446: =back
  447: 
  448: =head2 Get ()
  449: 
  450: Return the entire contents of the configuration file as a single string.
  451: 
  452: =head2 Comment ( line )
  453: 
  454: Static member that returns true if the line passed in is a comment or blank line.
  455: 
  456: =head2 Field ( line, index )
  457: 
  458: Static member that returns the value of a particular field on a config file line.
  459: 
  460: =over 4
  461: 
  462: =item * line   - The line that's parsed.
  463: 
  464: =item * index  - The index requested (0 is the first field on the line).
  465: 
  466: =back
  467: 
  468: 
  469: 
  470: =head2 Index ( linearray, fieldno )
  471: 
  472: Returns a reference to a hash that indexes a line array by a particular field number.
  473: this can be used to produce secondary indices if required by the application (see
  474: MEMBER DATA below).
  475: 
  476: =over 4
  477: 
  478: =item * linearray - A reference to an array containing text in configuration file format.
  479: =item * fieldno - Number of the field to index (0 is the first field).
  480: 
  481: =item * RETURNS - A reference to a hash from field value to line array element number.
  482: 
  483: =back
  484: 
  485: =head1 MEMBER DATA
  486: 
  487: The following member data can be considered exported.
  488: 
  489: =head2 LineArray
  490: 
  491: The reference to the configuration files line array.
  492: 
  493: =cut
  494: 
  495: 
  496: __END__

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