Annotation of loncom/ConfigFileEdit.pm, revision 1.4

1.1       foxr        1: #
                      2: #
1.4     ! albertel    3: # $Id: lond,v 1.378 2007/08/08 22:24:36 albertel Exp $
1.1       foxr        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: 
1.2       foxr       30: use IO::File;
                     31: 
1.1       foxr       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: 
1.2       foxr      245:     my @temp;
1.1       foxr      246: 
                    247:     if(! defined($index->{$key})) {           # bail if no match.
                    248: 	return;
                    249:     }
                    250:     my $itemno   = $index->{$key}; # Index of item to delete.
                    251: 
1.2       foxr      252:     
1.1       foxr      253:     if ($itemno != $lastidx) {               # need to slide and reindex.
1.2       foxr      254: 	@temp = @$lines[0..($itemno-1)];
                    255: 	@temp[$itemno..($lastidx-1)] = @$lines[($itemno+1)..$lastidx];
                    256: 	    
                    257: 
1.1       foxr      258:     } else {			             # just need to truncate
1.2       foxr      259: 	@temp = @$lines[0..$lastidx-1];	     # So take the initial slice.
1.1       foxr      260:     }
1.2       foxr      261: 
                    262:     $self->{KeyToLines} = Index(\@temp, $self->{Indexfield});
                    263:     $self->{LineArray} = \@temp;             # Replace the lines index. 
1.1       foxr      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: }
1.2       foxr      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: 
1.3       foxr      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: 
1.1       foxr      335: 1;
1.2       foxr      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: 
1.3       foxr      449: =head2 Get ()
                    450: 
                    451: Return the entire contents of the configuration file as a single string.
                    452: 
1.2       foxr      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
1.3       foxr      468: 
                    469: 
1.2       foxr      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: 
1.1       foxr      496: 
1.2       foxr      497: __END__

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