File:  [LON-CAPA] / loncom / ConfigFileEdit.pm
Revision 1.4: download - view: text, annotated - select for diffs
Wed Aug 22 19:53:22 2007 UTC (16 years, 7 months ago) by albertel
Branches: MAIN
CVS tags: version_2_9_X, version_2_9_99_0, version_2_9_1, version_2_9_0, version_2_8_X, version_2_8_99_1, version_2_8_99_0, version_2_8_2, version_2_8_1, version_2_8_0, version_2_7_X, version_2_7_99_1, version_2_7_99_0, version_2_7_1, version_2_7_0, version_2_6_X, version_2_6_99_1, version_2_6_99_0, version_2_6_3, version_2_6_2, version_2_6_1, version_2_6_0, version_2_5_99_1, version_2_5_99_0, version_2_12_X, version_2_11_X, version_2_11_4_uiuc, version_2_11_4_msu, version_2_11_4, version_2_11_3_uiuc, version_2_11_3_msu, version_2_11_3, version_2_11_2_uiuc, version_2_11_2_msu, version_2_11_2_educog, version_2_11_2, version_2_11_1, version_2_11_0_RC3, version_2_11_0_RC2, version_2_11_0_RC1, version_2_11_0, version_2_10_X, version_2_10_1, version_2_10_0_RC2, version_2_10_0_RC1, version_2_10_0, loncapaMITrelate_1, language_hyphenation_merge, language_hyphenation, bz6209-base, bz6209, bz5969, bz2851, PRINT_INCOMPLETE_base, PRINT_INCOMPLETE, HEAD, GCI_3, GCI_2, GCI_1, BZ5971-printing-apage, BZ5434-fox, BZ4492-merge, BZ4492-feature_horizontal_radioresponse, BZ4492-feature_Support_horizontal_radioresponse, BZ4492-Support_horizontal_radioresponse
- add in revision info

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

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