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>