File Coverage

blib/lib/StanzaFile.pm
Criterion Covered Total %
statement 12 132 9.0
branch 0 64 0.0
condition 0 9 0.0
subroutine 4 23 17.3
pod 0 18 0.0
total 16 246 6.5


line stmt bran cond sub pod time code
1             #
2             # Revision History:
3             #
4             # 27-Nov-2002 Dick Munroe (munroe@csworks.com)
5             # Break structure initialization out of the new function to make
6             # inheritance easier.
7             # Allow Stanzas to have keywords without arguments when converting
8             # back to strings.
9             #
10             # 20-Dec-2002 Dick Munroe (munroe@csworks.com)
11             # Add use strict, restrict the version of perl to 5.8.0 or higher,
12             # and necessary changes imposed by use strict.
13             #
14             #
15             # 17-May-2003 Dick Munroe (munroe@csworks.com)
16             # Fix things so that package variables can't leak.
17             #
18             # 20-May-2003 Dick Munroe (munroe@csworks.com)
19             # Make the test harness happy.
20             # Forgot the tests in the MANIFEST.
21             # Backport to 5.6.1
22             #
23              
24             package StanzaFile ;
25              
26 1     1   604 use strict ;
  1         1  
  1         48  
27 1     1   6 use vars qw($VERSION) ;
  1         2  
  1         39  
28              
29 1     1   964 use FileHandle ;
  1         13524  
  1         7  
30 1     1   400 use Stanza ;
  1         2  
  1         2784  
31              
32             our $VERSION = "1.05" ;
33              
34             sub _new
35             {
36 0     0     my $thePackage = shift ;
37              
38 0   0       $thePackage = ref($thePackage) || $thePackage ;
39              
40 0           my $theStanzaFile =
41             {
42             'stanzaFlag' => undef,
43             'header' => new Stanza('header'),
44             'stanza' => {},
45             'order' => []
46             } ;
47              
48 0           return bless $theStanzaFile, $thePackage ;
49             } ;
50              
51             #
52             # Create a new stanza file object.
53             #
54             # Arguments:
55             #
56             # file_name => string
57             # file_handle => FileHandle object reference.
58             # file_string => string
59             #
60              
61             sub new
62             {
63 0     0 0   my $thePackage = shift ;
64              
65 0 0         die "Odd number of arguments passed to StanzaFile::new" if ((scalar(@_) % 2) != 0) ;
66              
67 0           my %ARGS = @_ ;
68              
69 0           my $theStanzaFile = $thePackage->_new() ;
70              
71 0 0         $theStanzaFile->read(%ARGS) if (%ARGS) ;
72              
73 0           return $theStanzaFile ;
74             } ;
75              
76             #
77             # Interface to be able to add "header" information which is simply
78             # information that appears in the file prior to the occurance of
79             # a stanza. By default, this is an invalid syntax.
80             #
81              
82             sub addHeader
83             {
84 0     0 0   my ($theObject, $theHeader) = @_ ;
85              
86 0           die "Information not in a Stanza: $theHeader" ;
87             } ;
88              
89             #
90             # Add a new stanza. Keep track of the order of addition.
91             #
92              
93             sub add
94             {
95 0     0 0   my ($theObject, $theStanza) = @_ ;
96              
97 0 0         die "Can't add anything but Stanza's" if (ref($theStanza) ne "Stanza") ;
98              
99 0           $theObject->{'stanza'}->{$theStanza->name}=$theStanza ;
100 0           push @{$theObject->{'order'}},$theStanza->name ;
  0            
101              
102 0           return $theObject ;
103             } ;
104              
105             #
106             # Does the specified stanza exist?
107             #
108              
109             sub exists
110             {
111 0 0   0 0   die "Too few arguments passed to StanzaFile::exists" if (scalar(@_) < 2) ;
112              
113 0           my ($theObject, $theStanzaName) = @_ ;
114              
115 0 0 0       if (ref($theStanzaName) && ($theStanzaName->isa('Stanza')))
116             {
117 0           return $theObject->{'stanza'}->{$theStanzaName->name()} ;
118             }
119             else
120             {
121 0           return $theObject->{'stanza'}->{$theStanzaName} ;
122             } ;
123             } ;
124              
125             #
126             # True if this is the beginning of a stanza, otherwise false. The
127             # true value is the contents marking the beginning of a stanza.
128             #
129              
130             sub isBeginning
131             {
132 0 0   0 0   die "Too few arguments passed to StanzaFile::isBeginning" if (scalar(@_) < 2) ;
133              
134 0           my ($theObject, $theLine) = @_ ;
135              
136 0 0         if ($theLine =~ m/\s*\[([^\]]+)\]/)
137             {
138 0           my $theName = $1 ;
139              
140 0           $theName =~ s/^\s+(.*)/$1/ ;
141 0           $theName =~ s/(.*?)\s+$/$1/ ;
142 0           $theName =~ s/\s{2,}/ /g ;
143              
144 0           return $theName ;
145             } ;
146              
147 0           return undef ;
148             } ;
149              
150             #
151             # Return true if this is a comment or a blank line.
152             #
153              
154             sub isComment
155             {
156 0 0   0 0   die "Too few arguments passed to StanzaFile::isComment" if (scalar(@_) < 2) ;
157              
158 0           my ($theObject, $theLine) = @_ ;
159              
160 0           return $theLine =~ m/^\s*(\#|$)/ ;
161             } ;
162              
163             #
164             # Return the name/value pair if this is, indeed, a name/value pair
165             # otherwise return false.
166             #
167              
168             sub isValue
169             {
170 0     0 0   my ($theObject, $theLine) = @_ ;
171              
172 0 0         if ($theLine =~ m/\s*([\w ]+?)\s*=\s*(.*)/)
173             {
174 0           return ($1, $2) ;
175             } ;
176              
177 0           return undef ;
178             } ;
179              
180             #
181             # Create a new stanza of the "appropriate" type.
182             #
183              
184             sub newStanza
185             {
186 0     0 0   my ($theObject, $theName) = @_ ;
187              
188 0           return new Stanza($theName) ;
189             } ;
190              
191             #
192             # Parse the stanza file into stanzas. The contents of the
193             # file is passed as array arguments.
194             #
195              
196             sub parse
197             {
198 0     0 0   my $theObject = shift ;
199              
200 0 0         die "Too few arguments to StanzaFile::parse" if (scalar(@_) < 2) ;
201              
202 0           my $theStanza ;
203              
204 0           foreach (@_)
205             {
206             #
207             # Don't process comment characters or blank lines
208             #
209              
210 0 0         next if $theObject->isComment($_) ;
211              
212 0           my $theStanzaName ;
213              
214 0 0         if ($theStanzaName = $theObject->isBeginning($_))
215             {
216             #
217             # Found the beginning of a stanza, create a new stanza object,
218             # add it to the contents of the current object and continue.
219             #
220              
221 0           $theStanza = $theObject->newStanza($theStanzaName) ;
222            
223 0           $theObject->add($theStanza) ;
224              
225 0           $theObject->{'stanzaFlag'} = 1 ;
226             }
227             else
228             {
229             #
230             # If it isn't a comment, a stanza introduction, or a blank line,
231             # then it must be a line to be added to the current stanza (if
232             # there is a stanza in progress) or to the header (if there isn't
233             # a stanza in progress).
234             #
235              
236 0 0         if ($theObject->{'stanzaFlag'})
237             {
238             eval
239 0           {
240 0           $theStanza->add($theObject->isValue($_)) ;
241             } ;
242             }
243             else
244             {
245             eval
246 0           {
247 0           $theObject->addHeader($theObject->isValue($_)) ;
248             } ;
249             } ;
250              
251 0 0         die "Invalid format in Stanza $theStanza->name(): $_" if ($@) ;
252             } ;
253             } ;
254            
255 0           return $theObject ;
256             } ;
257              
258             #
259             # Process a file into a set of stanzas.
260             #
261             # Arguments (one of the following):
262             #
263             # file_handle=>FileHandle object reference to opened file.
264             # file_name =>Path to file.
265             # file_string=>Contents of file.
266             #
267              
268             sub read
269             {
270 0 0   0 0   die "Too few arguments to StanzaFile::Read" if (scalar(@_) < 2) ;
271            
272 0           my $theObject = shift ;
273              
274 0 0         die "Wrong number of arguments passed to StanzaFile::read" if ((scalar(@_) % 2) ne 0) ;
275              
276 0           my %ARGS = @_ ;
277              
278 0           my @theFile ;
279              
280 0 0         if (defined($ARGS{'file_string'}))
    0          
    0          
281             {
282 0           @theFile = split /\n/,$ARGS{'file_string'} ;
283             }
284             elsif (defined($ARGS{'file_name'}))
285             {
286 0           my $theFileHandle = new FileHandle "< " . $ARGS{'file_name'} ;
287            
288 0 0         die "Can't open " . $ARGS{'file_name'} . " for input" if (!defined($theFileHandle)) ;
289              
290 0           @theFile = $theFileHandle->getlines() ;
291              
292 0           undef $theFileHandle ;
293             }
294             elsif (defined($ARGS{'file_handle'}))
295             {
296 0 0         die "Must be a FileHandle class in StanzaFile::read('file_handle=>...)" if (!$ARGS{'file_handle'}-isa("FileHandle")) ;
297            
298 0           @theFile = $ARGS{'file_handle'}->getlines
299             }
300             else
301             {
302 0           die "Missing argument for StanzaFile::read" ;
303             } ;
304              
305 0           return $theObject->parse(@theFile) ;
306             } ;
307              
308             #
309             # Produce a string representation of a stanza file. The default
310             # "windows.ini" form which the base class parses is of the form:
311             #
312             # [name]
313             # name=value
314             # ...
315             #
316             # [name]
317             # ...
318             #
319             # Note that all comments are lost during the input/output process
320             # using StanzaFile.
321             #
322             # FIX ME It seems more natural for the stanza's do be doing the
323             # stringifying but the parseing is being done in the stanza file
324             # class and all stanzas are are containers for data, so ...
325             #
326              
327             sub headerAsString
328             {
329 0     0 0   return "" ;
330             } ;
331              
332             sub stanzaAsString
333             {
334 0     0 0   my ($theObject, $theStanza) = @_ ;
335              
336 0           my $theString = "[" . $theStanza->name() . "]\n" ;
337              
338 0           foreach ($theStanza->order())
339             {
340 0 0         if (defined($theStanza->item($_)))
341             {
342 0           $theString = $theString . $_ . "=" . $theStanza->item($_) . "\n" ;
343             }
344             else
345             {
346 0           $theString = $theString . $_ . "\n" ;
347             } ;
348             } ;
349              
350 0           return $theString ;
351             } ;
352              
353             sub asString
354             {
355 0     0 0   my $theObject = shift ;
356              
357 0           my $theString = $theObject->headerAsString() . "\n" ;
358              
359 0           foreach ($theObject->order())
360             {
361 0           $theString = $theString . $theObject->stanzaAsString($theObject->stanza($_)) . "\n" ;
362             } ;
363              
364 0           $theString =~ s/^\n+// ;
365 0           chomp($theString) ;
366              
367 0           return $theString ;
368             } ;
369              
370             #
371             # Produce a file from a set of stanzas.
372             #
373             # Arguments (one of the following):
374             #
375             # file_handle=>FileHandle object reference to opened file.
376             # file_name =>Path to file.
377             # file_string=>Reference to string to contain file.
378             #
379              
380             sub write
381             {
382 0 0   0 0   die "Too few arguments to StanzaFile::write" if (scalar(@_) < 2) ;
383            
384 0           my $theObject = shift ;
385              
386 0 0         die "Wrong number of arguments passed to StanzaFile::read" if ((scalar(@_) % 2) ne 0) ;
387              
388 0           my %ARGS = @_ ;
389              
390 0           my $theFile = $theObject->asString() ;
391              
392 0 0         if (defined($ARGS{'file_string'}))
    0          
    0          
393             {
394 0           ${$ARGS{'file_string'}} = $theFile ;
  0            
395             }
396             elsif (defined($ARGS{'file_name'}))
397             {
398 0           my $theFileHandle = new FileHandle "> " . $ARGS{'file_name'} ;
399            
400 0 0         die "Can't open " . $ARGS{'file_name'} . " for output" if (!defined($theFileHandle)) ;
401              
402 0           $theFileHandle->print($theFile) ;
403              
404 0           undef $theFileHandle ;
405             }
406             elsif (defined($ARGS{'file_handle'}))
407             {
408 0 0         die "Must be a FileHandle class in StanzaFile::write('file_handle=>...)" if (!$ARGS{'file_handle'}-isa("FileHandle")) ;
409            
410 0           $ARGS{'file_handle'}->print($theFile) ;
411             }
412             else
413             {
414 0           die "Missing argument for StanzaFile::write" ;
415             } ;
416              
417 0           return $theFile ;
418             } ;
419              
420             #
421             # Replace an [possibly] existing Stanza.
422             #
423              
424             sub replace
425             {
426 0     0 0   my ($theObject, $theStanza) = @_ ;
427              
428 0 0         die "Can't replace anything but Stanza's" if (ref($theStanza) ne "Stanza") ;
429              
430 0 0         if (!$theObject->exists($theStanza))
431             {
432 0           push @{$theObject->{'order'}},$theStanza->name ;
  0            
433             } ;
434              
435 0           $theObject->{'stanza'}->{$theStanza->name}=$theStanza ;
436              
437 0           return $theObject ;
438             } ;
439              
440             #
441             # Accessor Functions.
442             #
443              
444             sub header
445             {
446 0     0 0   my $theObject = shift ;
447              
448 0           return $theObject->{'header'} ;
449             } ;
450              
451             sub order
452             {
453 0     0 0   my $theObject = shift ;
454              
455 0           return @{$theObject->{'order'}} ;
  0            
456             } ;
457              
458             sub stanza
459             {
460 0     0 0   my ($theObject, $theName) = @_ ;
461              
462 0 0 0       if (ref($theName) && ($theName->isa('Stanza')))
463             {
464 0           return $theObject->{'stanza'}->{$theName->name()} ;
465             }
466             else
467             {
468 0           return $theObject->{'stanza'}->{$theName} ;
469             } ;
470             } ;
471              
472             1 ;
473              
474             =pod
475              
476             =head1 NAME
477              
478             StanzaFile - read, parse, and write files containing "stanzas".
479              
480             =head1 SYNOPSIS
481            
482             # Parse a .ini format file into stanzas.
483             #
484              
485             use StanzaFile ;
486             my $a = new StanzaFile(file_name=>"/etc/wvdial.conf") ;
487              
488             # Add a new stanza to a StanzaFile.
489             #
490             $a->add(new Stanza('Stanza Name')) ;
491              
492             # Check for a stanza's existance.
493             #
494             if ($a->exists('New Stanza'))
495             {
496             ...
497             } ;
498              
499             # Parse a .ini format file into stanzas.
500             #
501              
502             use StanzaFile ;
503             my $a = new StanzaFile ;
504             $a->read(file_name=>"/etc/wvdial.conf") ;
505              
506             # Produce a string version of the StanzaFile
507             # (Comments and other formatting are lost)
508             #
509              
510             my $theString = $a->asString() ;
511              
512             # Write the StanzaFile.
513             #
514             $a->write("/etc/newFile.conf") ;
515              
516             # Add a new stanza to the file, replacing the stanza if it
517             # already exists in the file.
518             #
519             $theNewStanza = new Stanza('New Stanza') ;
520             $a->replace($theNewStanza) ;
521              
522             # Access the "header" stanza.
523             #
524             my $theHeaderStanza = $a->header() ;
525              
526             # Order in which the stanzas were added to the stanza file.
527             #
528             my @theAdditionOrder = $a->order() ;
529              
530             # Get a stanza object from the stanza file.
531             #
532             my $theStanzaObject = $a->stanza('The Stanza') ;
533              
534             =head1 DESCRIPTION
535              
536             A number of Linux configuration files are stored in a Windows format
537             know as "stanzas" or WINDOWS.INI format. These files are of the form
538              
539             [name]
540             variable=value
541             variable1=value1
542             ...
543              
544             [name1]
545             variableA=valueA
546             variable1A=value1A
547              
548             and so on. This class is designed to provide parsing and processing
549             capabilities for the WINDOWS.INI format and provide a general enough
550             framework so that other formats of stanzas can be easily supported
551             (see StanzaFile::Grub for an example).
552              
553             With the StanzaFile and it's companion class Stanza it is reasonably
554             easy to read, parse, process, and write virtually any type of stanza
555             formatted information.
556              
557             =head1 EXAMPLES
558              
559             The following is a somewhat contrived example, but it shows the
560             merging of two StanzaFiles.
561              
562             my $a = new StanzaFile("/etc/wvfile.conf") ;
563             my $b = new StanzaFile("newWvfile.conf") ;
564              
565             foreach ($b->order())
566             {
567             if ($a->exists($_))
568             {
569             $a->stanza($_)->merge($b->stanza($_)) ;
570             }
571             else
572             {
573             $a->add($b->stanza($_)) ;
574             } ;
575             } ;
576              
577             $a->write(file_name=>"/etc/mergedWvdial.conf") ;
578              
579             =head1 BUGS
580              
581             None known.
582              
583             =head1 WARNINGS
584              
585             =head1 AUTHOR
586              
587             Dick Munroe (munroe@csworks.com).
588              
589             I'm looking for work (contract or permanent). I
590             do a lot more than just hack Perl. Take a look at my:
591              
592             Resume: http://www.csworks.com/resume
593             Skills: http://www.csworks.com/skills
594             CV: http://www.csworks.com/cv
595              
596             for the gory details. If you see a match, drop me a note and we'll see what we
597             can work out.
598              
599             =head1 SEE ALSO
600              
601             =cut
602