File:  [LON-CAPA] / loncom / ConfigFileEdit.pm
Revision 1.4: download - view: text, annotated - select for diffs
Wed Aug 22 19:53:22 2007 UTC (15 years, 1 month 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

#
#
# $Id: ConfigFileEdit.pm,v 1.4 2007/08/22 19:53:22 albertel Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#

package ConfigFileEdit;

use IO::File;

#
#   Module to read/edit configuration files.
#   See the POD at the bottom of the file for more information.

#------------------------------ internal utility functions ----------

# 
# Comment 
#   Returns true if the line is completely a comment.
# Paramter:
#    line  
#        Contents of a configuration file line.
#
sub Comment {
    my $line = shift;

    # Leading whitespace followed by a #..

    if ($line =~ /^[' ',\t]*\#/) {
	return 1;
    }
    # Solely whitespace or empty  line.

    $line =~ s/[' ',\t]//g;
    return ($line eq "");

}

#
#  Field
#    Return the value of a field in the line.  Leading whitespace is trimmed
#    from the first key (key 0).
#  Parameters:
#     line 
#        Line from which to extract the field.
#
#     idx
#        Index of the field to extract.
#
sub Field {
    my $line = shift;
    my $idx  = shift;

    $line =~ s/(^ *)|(^\t*)//;

    my @fields = split(/:/, $line);

    return $fields[$idx];
}
#
#   Index:
#      Return a reference to a hash that indexes a line array.
#      The hash is keyed on a field in the line array lines
#      Each hash entry is the line number of the line in which 
#      that key value appears.  Note that at present, keys must be
#      unique.
#  Parameters:
#      $array    - Reference to a line array.
#      $idxfield - Field number to index on (0 is the first field).
#  Returns:
#    Reference to the index hash:
sub Index {
    my $array     = shift;
    my $idxfield  = shift;
   
    my %hash;
    for(my $l = 0; $l < scalar(@$array); $l++) {
	chomp $array->[$l];	# Ensure lines have no \n's.
	my $line = $array->[$l];
	if(!Comment($line)) {
	    my $keyvalue = Field($line, $idxfield);
	    $hash{$keyvalue} = $l;
	}
    }


    return \%hash;
}


#------------------------------- public functions --------------------
#
#   new
#     Create a new configuration file editor object.
#     configuration files are : separated fields that 
#     may have comments, blank lines and trailing comments.
#     comments are indicated by #"s.
#   Parameters:
#     filename 
#            Name of file to open.
#     indexfield
#            Select the field to index the file by.
#
# 
sub new {
    my $class      = shift;
    my $filename   = shift;
    my $indexfield = shift;

    # Open the configuration file.  Failure results in the return
    # of an undef.
    # Note we dont' need to hold on to the file handle after the file
    # is read in.

    open(CONFIGFILE, "< $filename") 
	or return undef;


    #   Read the file into a line array:

    my @linearray = <CONFIGFILE>;
    close(CONFIGFILE);
    
    
    #  Build the key to lines hash: this hash
    #  is keyed on item $indexfield of the line
    #  and contains the line number of the actual line.

    my $hashref = Index(\@linearray, $indexfield);


    #   Build the object hash, bless it and return.

    my $self       = { Filename   => $filename,
		       Indexfield => $indexfield,
		       LineArray  => \@linearray,
		       KeyToLines => $hashref};

    bless ($self, $class);

    return $self;
    
}
#
#   Append an element to the configuration file array.
#   The element is placed at the end of the array. If the element is not
#   a comment. The key is added to the index.
#
#   Parameters:
#      $self     - Reference to our member hash.
#      $line     - A line to add to the config file.
sub Append { 
    my $self    = shift;
    my $line    = shift;

    #   Regardless, the line is added to the config file.

    my $linearray = ($self->{LineArray});
    push(@$linearray, $line);	                     # Append the line.
    my $newindex = @$linearray - 1;                  # Index of new line.

    #   If the line is not a comment, pull out the desired field and add
    #   it to the line index hash.

    if(!Comment($line)) {
	my $field = Field($line, $self->{Indexfield});
	$self->{KeyToLines}->{$field} = $newindex;
    }
}
#
#   Find a non comment line by looking it up by key.  
#  Parameters:
#     $self  - Reference to our member hash.
#     $key   - Lookup key.
#  Returns:
#     Contents of the line or undef if there is no match.
#
sub Find {
    my $self    = shift;
    my $key     = shift;

    my $hash    = $self->{KeyToLines};
    if(defined($hash->{$key})) {
	my $lines   = $self->{LineArray};
	return $lines->[$hash->{$key}];
    } else {
	return undef;
    }
}
#
#   Return the number of lines in the current configuration file.
#   Note that this count includes the comment lines.  To
#   Get the non comment lines the best thing is to iterate through the
#   keys of the KeyToLines hash.
#  Parameters:
#    $self     - Reference to member data hash for the object.
#
sub LineCount {
    my $self  = shift;
    my $lines = $self->{LineArray};
    my $count = @$lines;
    return $count;
}
#
#   Delete a line from the configuration file.
#   Note at present, there is no support for deleting comment lines.
#   The line is deleted, from the array.  All lines following are slid back
#   one index and the index hash is rebuilt.
# Parameters:
#   $self     - Reference to the member data hash for the object.
#   $key      - key value of the line to delete.
# NOTE:
#   If a line matching this key does not exist, this is a no-op.
#
sub DeleteLine {
    my $self     = shift;
    my $key      = shift;

    my $lines    = $self->{LineArray};
    my $index    = $self->{KeyToLines};
    my $lastidx  = $self->LineCount() - 1;   # Index of last item.


    my @temp;

    if(! defined($index->{$key})) {           # bail if no match.
	return;
    }
    my $itemno   = $index->{$key}; # Index of item to delete.

    
    if ($itemno != $lastidx) {               # need to slide and reindex.
	@temp = @$lines[0..($itemno-1)];
	@temp[$itemno..($lastidx-1)] = @$lines[($itemno+1)..$lastidx];
	    

    } else {			             # just need to truncate
	@temp = @$lines[0..$lastidx-1];	     # So take the initial slice.
    }

    $self->{KeyToLines} = Index(\@temp, $self->{Indexfield});
    $self->{LineArray} = \@temp;             # Replace the lines index. 


}
#
#   Replace a line in the configuration file:
#   The line is looked up by index.
#   The line is replaced by the one passed in... note if the line
#   is a comment, the index is just deleted!!
#   The index for the line is replaced with the new value of the key field
#  (it's possible the key field changed).
# 
#  Parameters:
#     $self          - Reference to the object's member data hash.
#     $key           - Lookup key.
#     $line          - New line.
# NOTE:
#  If there is no line with the key $key, this reduces to an append.
#
sub ReplaceLine {
    my $self       = shift;
    my $key        = shift;
    my $line       = shift;

    my $hashref  = $self->{KeyToLines};
    if(!defined $hashref->{$key}) {
	$self->Append($line); 
    } else {
	my $l     = $hashref->{$key};
	my $lines = $self->{LineArray};
	$lines->[$l] = $line;	          # Replace old line.
	delete $hashref->{$key};          # get rid of the old index.
	if(!Comment($line)) {	          # Index this line only if not comment!
	    my $newkey = Field($line, $self->{Indexfield});
	    $hashref->{$newkey} = $l;
	}
    }
}
#
#   Write the configuration array to a file:
#   Parameters:
#      $self           - Reference to the object's member data hash.
#      $fh              - Name of file to write.
sub Write {
    my $self     = shift;
    my $fh       = shift;
    
    my $lines    = $self->{LineArray};
    my $length   = @$lines;
    for (my $i = 0; $i < $length; $i++) {
	print $fh $lines->[$i]."\n";
    }   
}

#   Get:
#      return the entire contents of the file as a string.
# Parameters:
#    $self      - (this).
#
sub Get {
    my $self    = shift;
    
    my $contents = "";
    my $lines    = $self->{LineArray};
    my $length   = @$lines;

    for (my $i = 0; $i < $length; $i++) {
	$contents .= $lines->[$i]."\n";
    }
    return $contents;
}

1;
#----------------------------- Documentation --------------------------------------
#

=pod

=head1 NAME

ConfigFileEdit - Lookups and edits on a configuration file.

=head1 SYNOPSIS

    use LONCAPA::ConfigFileEdit;
    use IO::File;

    my $editor = ConfigFileEdit->new("file.cfg", 0);
    $editor->Append("new:line:with:config:info");      
    my $line   = $editor->Find("key");
    my $size   = $editor->LineCount();
    $editor->DeleteLine("george");
    $editor->ReplaceLine("new","new:line:with:different:info");  
    my $fh = new IO::File("> modified.cfg", 0);
    $editor->Write($fh);

=head1 DESCRIPTION

Configuration files in LonCAPA contain lines of colon separated fields.
Configuration files can also contain comments initiated by the hash (#)
character that continue to the end of line.  Finally, configuration files
can be made more readable by the inclusion of either empty lines or 
lines entirely made up of whitespace.

ConfigFileEdit allows manipulation of configuration files in a way that
preserves comments and order.  This differs from LonCAPA's 'normal' mechanism
handling configuration files by throwing them up into a hash by key.

ConfigFileEdit maintains the original configuration file in an array and
creates the index hash as a hash to line numbers in the array.  This allows
O(k) time lookups, while preserving file order and comments (comments are
lines in the array that have no indices in the associated hash.

In addition to line lookup, ConfigFileEdit supports simple editing
functions such as delete, append, and replace.  At present, Insertions
at arbitrary points in the file are not supported.  The modified
file can also be written out.

=head1 METHODS

=head2 new ( filename, keyfield )

Creates  a new ConfigFileEdit object from an existing file.   Where:

=over 4

=item * filename - The name of the configuration file to open.

=item * keyfield - The number of the field for which the index hash is generated.
    Fields are enumerated from zero.

=item * RETURNS: - undef if the file could not be open, otherwise a reference
    to a hash that contains the object member data.

=back

=head2 Append ( newline )

Appends a new line to the configuration file in memory.  The file that was
used to create the object is not modified by this operation.

=over 4

=item * newline - A new line to append to the end of the configurationfile.

=back

=head2 LineCount 

Returns the number of lines in the file.  This count includes the nubmer of
comments.

=head2 DeleteLine ( key )

Deletes the line that matches key.  Note that if there is no matching line,
this function is a no-op.

=over 4

=item * key   - The key to match, the line that is indexed by this key is deleted.

=back

=head2 ReplaceLine ( key, newcontents  )

Replaces the selected line in its entirety.  Note that the config file is re-indexed
so it is legal to modify the line's key field.

=over 4

=item * key    - The key that selects which line is replaced.

=item * newcontents - The new contents of the line.

=back

=head2 Write ( fh )

Writes the contents of the configuration file's line array to file.

=over 4

=item * fh   - A file handle that is open for write on the destination file.

=back

=head2 Get ()

Return the entire contents of the configuration file as a single string.

=head2 Comment ( line )

Static member that returns true if the line passed in is a comment or blank line.

=head2 Field ( line, index )

Static member that returns the value of a particular field on a config file line.

=over 4

=item * line   - The line that's parsed.

=item * index  - The index requested (0 is the first field on the line).

=back



=head2 Index ( linearray, fieldno )

Returns a reference to a hash that indexes a line array by a particular field number.
this can be used to produce secondary indices if required by the application (see
MEMBER DATA below).

=over 4

=item * linearray - A reference to an array containing text in configuration file format.
=item * fieldno - Number of the field to index (0 is the first field).

=item * RETURNS - A reference to a hash from field value to line array element number.

=back

=head1 MEMBER DATA

The following member data can be considered exported.

=head2 LineArray

The reference to the configuration files line array.

=cut


__END__

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