88e939d57f0f8dbea3dabd0760d580e3d04d124f
[darcs-mirror-metainit.git] / lib / MetaInit / Parse.pm
1 package MetaInit::Parse;
2 use v5.008;
3 use strict;
4 use warnings;
5 use Carp qw/croak/;
6 use File::Basename;
7 use Scalar::Util qw/openhandle/;
8
9 =head1 NAME
10
11 MetaInit::Parse - Parses a MetaInit definition fild
12
13 =head1 SYNOPSIS
14
15     $data = MetaInit::Parse::parse($filename)
16
17 Opens and parses the specified file. The parsed data is handed back as a
18 hashref, containing an entry for each definition.
19
20 =head2 FILE FORMAT
21
22 The file to be parsed should be in a RFC822-like format (field names and values
23 are separated by a colon (C<:>). Lines starting with a blank are just appended
24 to their previous line's value. Lines that contain only a period (C<.>) in them
25 will become an empty line in the output.
26
27 Empty lines are ignored. 
28
29 Comments are allowed - All characters after a # sign until the end of the 
30 line are ignored. If you need to include the # sign, prepend it with a 
31 backslash (\#).
32
33 =head2 FIELDS
34
35 A MetaInit file has several defined fields, out of which only C<Exec> is mandatory:
36
37 =over 4
38
39 =item Short-Description
40
41 A short (~60 character) description of the daemon. Merely informative.
42
43 =item Description
44
45 A longer description of the daemon, usually a couple of lines long. Merely
46 informative.
47
48 =item Exec
49
50 The command to execute to get the daemon to room. Please keep in mind this 
51 should start the daemon in the I<foreground>, not send it to the background.
52
53 =item Required-Start
54
55 The facilities that should be started before this one. This can include the
56 LSB facility names. It defaults to B<$remote_fs>.
57
58 =item Required-Stop
59
60 B<CURRENTLY IGNORED - SHOULD CHECK LATER>
61
62 =item Should-Start
63
64 B<CURRENTLY IGNORED - SHOULD CHECK LATER>
65
66 =item Should-Stop
67
68 B<CURRENTLY IGNORED - SHOULD CHECK LATER>
69
70 =item Prestart-Hook
71
72 A shell snippet that should be included in the generated init script I<before>
73 the daemon is run
74
75 =item Poststop-hook
76
77 A shell snippet that should be included in the generated init script I<after>
78 the daemon finishes running
79
80 =item No-Auto
81
82 If this is set to anything, the generated init scripts will be installed in a
83 way that the deamon is not started by default on system start up. It can still
84 be started manually (e.g. using /etc/init.d/<name> start) and be configured to
85 start automatically (e.g. by adjusting the symlinks).
86
87 =back
88
89 Some fields (Required-Start, Should-Start, Required-Stop and Should-Stop) will
90 not return strings but arrayrefs - Elements will be separated by whitespace.
91
92 =head1 NOTES
93
94 Note that this module is written specifically for MetaInit, it will quite 
95 probably not be useful outside it.
96
97 =head1 SEE ALSO
98
99 The (as for now inexistent ;-) ) MetaInit documentation
100
101 LSB facility names, 
102 http://refspecs.freestandards.org/LSB_2.1.0/LSB-generic/LSB-generic/facilname.html
103
104 =head1 AUTHOR
105
106 =cut
107
108 my @splits = qw(Required-Start Should-Start Required-Stop Should-Stop);
109 my @mandatory = qw(Exec);
110
111 sub slurp_from_fh {
112     my ($fh) = @_;
113
114     return do { local $/; <$fh> };
115 }
116
117 sub process_data {
118     my ($data, $parsed) = @_;
119
120     my $lastkey;
121     for (split /\n/, $data) {
122         chomp;
123         # Ignore comments; unescape escaped #s
124         s/[^\\]\#.*//;
125         s/\\\#/\#/g;
126         # Ignore empty lines; convert single dots in a line into empty lines
127         next if /^\s*$/;
128         s/^\s*\.\s*$//;
129
130         if (my ($key, $value) = m/^(\S.*)\s*:\s*(.*)/) {
131             $parsed->{$key} = $value;
132             $lastkey = $key;
133         }
134         elsif ($lastkey) {
135             s/^\s+//;
136             s/^\.$//;
137             $parsed->{$lastkey} .= "\n$_";
138         } else {
139             die "Cannot parse line: ``$_''";
140         }
141     }
142 }
143
144 sub fixup_results {
145     my (%parsed) = @_;
146
147     my $error_msg = "";
148     for my $field (@mandatory) {
149             $error_msg .= "Mandatory field `$field' not provided\n" unless exists $parsed{$field};
150     }
151
152     croak($error_msg) if $error_msg;
153
154     if (not exists $parsed{Description}) {
155         $parsed{Description} = $parsed{"Short-Description"}
156     }
157
158     ($parsed{Path}, $parsed{Args}) = split(/\s+/,$parsed{Exec});
159     $parsed{Basename} = basename $parsed{Path};
160
161     for (@splits){
162         $parsed{$_} = [ split m/\s+/, $parsed{$_}||'' ];
163     }
164
165     if ($parsed{"No-Auto"}) {
166         $parsed{"Start-Levels"} = [];
167         $parsed{"Stop-Levels"}  = [1,2,3,4,5];
168     } else {
169         $parsed{"Start-Levels"} = [2,3,4,5];
170         $parsed{"Stop-Levels"}  = [1];
171     }
172
173     if ($parsed{"Post-Stop"}) {
174         push @{$parsed{"Stop-Levels"}}, 0, 6;
175     }
176
177     for (qw/Start-Levels Stop-Levels/) {
178         @{$parsed{$_}} = sort @{$parsed{$_}};
179     }
180
181     return %parsed;
182 }
183
184 sub parse {
185     my ($args) = @_;
186
187     if (ref $args ne 'HASH') {
188         croak('hash reference expected');
189     }
190
191     my $data;
192     my %parsed;
193
194     if (defined $args->{input}) {
195         $data = $args->{input};
196     }
197     elsif (my $fh = openhandle($args->{handle})) {
198         $data = slurp_from_fh($fh);
199     }
200     elsif (defined (my $file = $args->{filename})) {
201         open(my $handle, '<', $file)
202             or die "Failed to open input file `$file': $!";
203
204         $data = slurp_from_fh($handle);
205
206         $parsed{File} = $file;
207         $parsed{Name} = basename($file,'.metainit');
208     }
209     else {
210         croak("no input given; you need to pass a `filename', "
211             . "an opened `handle' or an `input' string");
212     }
213
214     if (!defined $parsed{File}) {
215         if (ref $args->{fields} ne 'HASH'
216          || !exists $args->{fields}->{File}
217          || !exists $args->{fields}->{Name}) {
218             croak("parsing from handles or strings requires the `fields' option to "
219                 . "be set to a hash reference that defines both the `Name' and the "
220                 . "`File' field.");
221         }
222     }
223
224     @parsed{keys %{ $args->{fields} }} = values %{ $args->{fields} };
225
226     # Defaults:
227
228         $parsed{"Short-Description"} = $parsed{Name};
229         $parsed{"No-Auto"} = 0;
230     $parsed{"Required-Start"} = '$remote_fs';
231     $parsed{"Required-Stop"}  = '$remote_fs';
232
233     process_data($data, \%parsed);
234     %parsed = fixup_results(%parsed);
235     
236     return \%parsed;
237 }
238
239 # Return a true value
240 1;
241
242 # vim:sw=4:ts=4:expandtab