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>