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