f6622a0b81df2a8d97e6697ee77a2de2849d02cf
[ssic.git] / bin / ssic
1 #!/usr/bin/perl
2
3 =head1 NAME
4
5 ssic - Server Side Includes Compiler
6
7 =cut
8
9 use strict;
10 use warnings;
11
12 use Getopt::Long;
13 use CGI::SSI;
14
15 my $VERSION;
16 $VERSION = '1.0.0';
17
18 sub main
19 {
20         my %opts;
21         my $ssi;
22         my $input;
23         my $output;
24
25         $SIG{'__WARN__'} = \&warning;
26
27         Getopt::Long::Configure("no_ignore_case", "bundling", "gnu_compat",
28                 "no_getopt_compat");
29         if (not GetOptions(\%opts,
30                         "o=s",
31                         "D=s%",
32                         "I=s",
33                         "h|help",
34                         "V|version",
35                 )) {
36                 usage(*STDERR);
37                 exit(4);
38         }
39
40         if (exists($opts{'h'})) {
41                 help(*STDOUT);
42                 exit(0);
43         }
44         if (exists($opts{'V'})) {
45                 version(*STDOUT);
46                 exit(0);
47         }
48
49         if ($#ARGV lt 0) {
50                 error(4, "No input files\n");
51         }
52
53         $ssi = init_compiler($opts{'D'}, $opts{'I'});
54
55         if (exists($opts{'o'})) {
56                 if ($#ARGV gt 0) {
57                         error(4, "Cannot specify -o with multiple files\n");
58                 }
59                 compile($ssi, $ARGV[0], $opts{'o'});
60         } else {
61                 for $input (@ARGV) {
62                         $output = $input;
63                         $output =~ s/\.[^.]+$/.html/;
64                         compile($ssi, $input, $output);
65                 }
66         }
67
68         undef $ssi;
69 }
70
71 sub usage
72 {
73         my ($fh) = @_;
74
75         printf($fh "Usage: %s [-o <output>] <input> ...\n", $0);
76 }
77
78 sub help
79 {
80         my ($fh) = @_;
81
82         usage($fh);
83         print("Options:\n");
84         print("  -o <output>        Place the output into <output>\n");
85         print("  -I <directory>     Set the document root to <directory>\n");
86         print("  -D <name>=<value>  Set the variable <name> to <value>\n");
87         print("  -h, --help         Display this information\n");
88         print("  -V, --version      Display compiler version information\n");
89 }
90
91 sub version
92 {
93         my ($fh) = @_;
94
95         printf("ssic %s\n", $VERSION);
96         print("Copyright (C) 2013 Patrick \"P. J.\" McDermott\n");
97         print("License GPLv3+: GNU GPL version 3 or later " .
98                 "<http://gnu.org/licenses/gpl.html>.\n");
99         print("This is free software: you are free to change and " .
100                 "redistribute it.\n");
101         print("There is NO WARRANTY, to the extent permitted by law.\n");
102 }
103
104 sub warning
105 {
106         my ($fmt, @args) = @_;
107
108         printf(STDERR "ssic: Warning: " . $fmt, @args);
109 }
110
111 sub error
112 {
113         my ($status, $fmt, @args) = @_;
114
115         printf(STDERR "ssic: Error: " . $fmt, @args);
116         exit($status);
117 }
118
119 sub init_compiler
120 {
121         my ($vars, $root) = @_;
122         my $ssi;
123         my $var_name;
124         my $var_value;
125
126         %ENV = (
127                 "DOCUMENT_ROOT" => $root,
128         );
129
130         $CGI::SSI::DEBUG = 0;
131         $ssi = CGI::SSI->new();
132
133         $ssi->set("DOCUMENT_ROOT" => $root);
134
135         while (($var_name, $var_value) = each(%{$vars})) {
136                 $ssi->set($var_name => $var_value);
137         }
138
139         return $ssi;
140 }
141
142 sub compile
143 {
144         my ($ssi, $input, $output) = @_;
145         my $input_fh;
146         my $input_abs;
147         my $output_fh;
148
149         if ($input eq $output and $input ne "-") {
150                 warning("Input and output files are equal\n");
151         }
152
153         if ($input eq "-") {
154                 $input_fh = *STDIN;
155                 $input_abs = File::Spec->rel2abs(".");
156         } else {
157                 if (not open($input_fh, "<", $input)) {
158                         error(4, "%s: %s\n", $input, $!);
159                 }
160                 $input_abs = File::Spec->rel2abs($input);
161         }
162         if ($output eq "-") {
163                 $output_fh = *STDOUT;
164         } else {
165                 if (not open($output_fh, ">", $output . "~")) {
166                         error(4, "%s: %s\n", $output . "~", $!);
167                 }
168         }
169
170         # CGI::SSI uses SCRIPT_FILENAME to determine the value of LAST_MODIFIED.
171         $ENV{"DOCUMENT_NAME"} = $input;
172         $ENV{"DOCUMENT_URI"} = $input;
173         $ENV{"SCRIPT_FILENAME"} = $input_abs;
174
175         # Reset config tags to default values.
176         $ssi->config("errmsg",
177                 "[an error occurred while processing this directive]");
178         $ssi->config("sizefmt", "abbrev");
179         $ssi->config("timefmt", undef);
180
181         $ssi->set("DOCUMENT_NAME" => $input);
182         $ssi->set("DOCUMENT_URI" => $input);
183
184         print($output_fh $ssi->process(<$input_fh>));
185
186         if ($input ne "-") {
187                 close($input_fh);
188         }
189         if ($output ne "-") {
190                 close($output_fh);
191                 if (not rename($output . "~", $output)) {
192                         error(4, "%s: %s\n", $output, $!);
193                 }
194         }
195 }
196
197 main();
198
199 __END__
200
201 =head1 SYNOPSIS
202
203 B<ssic>
204 [B<-o> I<output>]
205 [B<-I> I<directory>] 
206 [B<-D> I<name>=I<value> ...]
207 I<input> ...
208
209 =head1 DESCRIPTION
210
211 B<ssic> processes HTML documents with SSI directives formatted as SGML comments.
212 It can be used to process documents without an HTTP server for local browsing or
213 to generate static HTML documents to be efficiently served by an HTTP server.
214 Documents could even be preprocessed, e.g. by a Markdown processor, before being
215 parsed with ssic.
216
217 =head1 OPTIONS
218
219 =over 4
220
221 =item B<-o> I<output>
222
223 Place the output into I<output>.
224
225 =item B<-I> I<directory>
226
227 Set the document root to I<directory>.
228
229 =item B<-D> I<name>=I<value>
230
231 Set the variable I<name> to I<value>.
232
233 =item B<-h>, B<--help>
234
235 Display help information.
236
237 =item B<-V>, B<--version>
238
239 Display compiler version information.
240
241 =back
242
243 =head1 COPYRIGHT
244
245 Copyright (C) 2013  Patrick "P. J." McDermott
246
247 This program is free software: you can redistribute it and/or modify
248 it under the terms of the GNU General Public License as published by
249 the Free Software Foundation, either version 3 of the License, or
250 (at your option) any later version.
251
252 This program is distributed in the hope that it will be useful,
253 but WITHOUT ANY WARRANTY; without even the implied warranty of
254 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
255 GNU General Public License for more details.
256
257 You should have received a copy of the GNU General Public License
258 along with this program.  If not, see <http://www.gnu.org/licenses/>.
259
260 =head1 SEE ALSO
261
262 L<CGI::SSI(3)>
263
264 NCSA HTTPd SSI documentation:
265 L<http://web.archive.org/web/19971210170837/http://hoohoo.ncsa.uiuc.edu/docs/tutorials/includes.html>
266
267 Apache HTTPd mod_include documentation:
268 L<http://httpd.apache.org/docs/current/mod/mod_include.html>
269
270 =cut