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>