Annotation of loncom/ConfigFileEdit.pm, revision 1.3

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.3     ! foxr      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: 
1.1       foxr      334: 1;
1.2       foxr      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: 
1.3     ! foxr      448: =head2 Get ()
        !           449: 
        !           450: Return the entire contents of the configuration file as a single string.
        !           451: 
1.2       foxr      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
1.3     ! foxr      467: 
        !           468: 
1.2       foxr      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: 
1.1       foxr      495: 
1.2       foxr      496: __END__

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