Annotation of loncom/ConfigFileEdit.pm, revision 1.2

1.1       foxr        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: 
1.2     ! foxr       29: use IO::File;
        !            30: 
1.1       foxr       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: 
1.2     ! foxr      244:     my @temp;
1.1       foxr      245: 
                    246:     if(! defined($index->{$key})) {           # bail if no match.
                    247: 	return;
                    248:     }
                    249:     my $itemno   = $index->{$key}; # Index of item to delete.
                    250: 
1.2     ! foxr      251:     
1.1       foxr      252:     if ($itemno != $lastidx) {               # need to slide and reindex.
1.2     ! foxr      253: 	@temp = @$lines[0..($itemno-1)];
        !           254: 	@temp[$itemno..($lastidx-1)] = @$lines[($itemno+1)..$lastidx];
        !           255: 	    
        !           256: 
1.1       foxr      257:     } else {			             # just need to truncate
1.2     ! foxr      258: 	@temp = @$lines[0..$lastidx-1];	     # So take the initial slice.
1.1       foxr      259:     }
1.2     ! foxr      260: 
        !           261:     $self->{KeyToLines} = Index(\@temp, $self->{Indexfield});
        !           262:     $self->{LineArray} = \@temp;             # Replace the lines index. 
1.1       foxr      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: }
1.2     ! foxr      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: 
1.1       foxr      316: 1;
1.2     ! foxr      317: #----------------------------- Documentation --------------------------------------
        !           318: #
        !           319: 
        !           320: =pod
        !           321: 
        !           322: =head1 NAME
        !           323: 
        !           324: ConfigFileEdit - Lookups and edits on a configuration file.
        !           325: 
        !           326: =head1 SYNOPSIS
        !           327: 
        !           328:     use LONCAPA::ConfigFileEdit;
        !           329:     use IO::File;
        !           330: 
        !           331:     my $editor = ConfigFileEdit->new("file.cfg", 0);
        !           332:     $editor->Append("new:line:with:config:info");      
        !           333:     my $line   = $editor->Find("key");
        !           334:     my $size   = $editor->LineCount();
        !           335:     $editor->DeleteLine("george");
        !           336:     $editor->ReplaceLine("new","new:line:with:different:info");  
        !           337:     my $fh = new IO::File("> modified.cfg", 0);
        !           338:     $editor->Write($fh);
        !           339: 
        !           340: =head1 DESCRIPTION
        !           341: 
        !           342: Configuration files in LonCAPA contain lines of colon separated fields.
        !           343: Configuration files can also contain comments initiated by the hash (#)
        !           344: character that continue to the end of line.  Finally, configuration files
        !           345: can be made more readable by the inclusion of either empty lines or 
        !           346: lines entirely made up of whitespace.
        !           347: 
        !           348: ConfigFileEdit allows manipulation of configuration files in a way that
        !           349: preserves comments and order.  This differs from LonCAPA's 'normal' mechanism
        !           350: handling configuration files by throwing them up into a hash by key.
        !           351: 
        !           352: ConfigFileEdit maintains the original configuration file in an array and
        !           353: creates the index hash as a hash to line numbers in the array.  This allows
        !           354: O(k) time lookups, while preserving file order and comments (comments are
        !           355: lines in the array that have no indices in the associated hash.
        !           356: 
        !           357: In addition to line lookup, ConfigFileEdit supports simple editing
        !           358: functions such as delete, append, and replace.  At present, Insertions
        !           359: at arbitrary points in the file are not supported.  The modified
        !           360: file can also be written out.
        !           361: 
        !           362: =head1 METHODS
        !           363: 
        !           364: =head2 new ( filename, keyfield )
        !           365: 
        !           366: Creates  a new ConfigFileEdit object from an existing file.   Where:
        !           367: 
        !           368: =over 4
        !           369: 
        !           370: =item * filename - The name of the configuration file to open.
        !           371: 
        !           372: =item * keyfield - The number of the field for which the index hash is generated.
        !           373:     Fields are enumerated from zero.
        !           374: 
        !           375: =item * RETURNS: - undef if the file could not be open, otherwise a reference
        !           376:     to a hash that contains the object member data.
        !           377: 
        !           378: =back
        !           379: 
        !           380: =head2 Append ( newline )
        !           381: 
        !           382: Appends a new line to the configuration file in memory.  The file that was
        !           383: used to create the object is not modified by this operation.
        !           384: 
        !           385: =over 4
        !           386: 
        !           387: =item * newline - A new line to append to the end of the configurationfile.
        !           388: 
        !           389: =back
        !           390: 
        !           391: =head2 LineCount 
        !           392: 
        !           393: Returns the number of lines in the file.  This count includes the nubmer of
        !           394: comments.
        !           395: 
        !           396: =head2 DeleteLine ( key )
        !           397: 
        !           398: Deletes the line that matches key.  Note that if there is no matching line,
        !           399: this function is a no-op.
        !           400: 
        !           401: =over 4
        !           402: 
        !           403: =item * key   - The key to match, the line that is indexed by this key is deleted.
        !           404: 
        !           405: =back
        !           406: 
        !           407: =head2 ReplaceLine ( key, newcontents  )
        !           408: 
        !           409: Replaces the selected line in its entirety.  Note that the config file is re-indexed
        !           410: so it is legal to modify the line's key field.
        !           411: 
        !           412: =over 4
        !           413: 
        !           414: =item * key    - The key that selects which line is replaced.
        !           415: 
        !           416: =item * newcontents - The new contents of the line.
        !           417: 
        !           418: =back
        !           419: 
        !           420: =head2 Write ( fh )
        !           421: 
        !           422: Writes the contents of the configuration file's line array to file.
        !           423: 
        !           424: =over 4
        !           425: 
        !           426: =item * fh   - A file handle that is open for write on the destination file.
        !           427: 
        !           428: =back
        !           429: 
        !           430: =head2 Comment ( line )
        !           431: 
        !           432: Static member that returns true if the line passed in is a comment or blank line.
        !           433: 
        !           434: =head2 Field ( line, index )
        !           435: 
        !           436: Static member that returns the value of a particular field on a config file line.
        !           437: 
        !           438: =over 4
        !           439: 
        !           440: =item * line   - The line that's parsed.
        !           441: 
        !           442: =item * index  - The index requested (0 is the first field on the line).
        !           443: 
        !           444: =back
        !           445: 
        !           446: =head2 Index ( linearray, fieldno )
        !           447: 
        !           448: Returns a reference to a hash that indexes a line array by a particular field number.
        !           449: this can be used to produce secondary indices if required by the application (see
        !           450: MEMBER DATA below).
        !           451: 
        !           452: =over 4
        !           453: 
        !           454: =item * linearray - A reference to an array containing text in configuration file format.
        !           455: =item * fieldno - Number of the field to index (0 is the first field).
        !           456: 
        !           457: =item * RETURNS - A reference to a hash from field value to line array element number.
        !           458: 
        !           459: =back
        !           460: 
        !           461: =head1 MEMBER DATA
        !           462: 
        !           463: The following member data can be considered exported.
        !           464: 
        !           465: =head2 LineArray
        !           466: 
        !           467: The reference to the configuration files line array.
        !           468: 
        !           469: =cut
        !           470: 
1.1       foxr      471: 
1.2     ! foxr      472: __END__

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