Slight improvements to URL and email address parsing.
[sw-tools] / perl / SWMan.pm
CommitLineData
961ce1c2 1# -*-perl-*-
2#
44b3c589 3# $Id: SWMan.pm,v 1.2 1999/08/18 17:10:07 mdw Exp $
961ce1c2 4#
5# Display and other fiddling of manual pages
6#
7# (c) 1999 EBI
8#
9
10#----- Licensing notice -----------------------------------------------------
11#
12# This file is part of sw-tools.
13#
14# sw-tools is free software; you can redistribute it and/or modify
15# it under the terms of the GNU General Public License as published by
16# the Free Software Foundation; either version 2 of the License, or
17# (at your option) any later version.
18#
19# sw-tools is distributed in the hope that it will be useful,
20# but WITHOUT ANY WARRANTY; without even the implied warranty of
21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22# GNU General Public License for more details.
23#
24# You should have received a copy of the GNU General Public License
25# along with sw-tools; if not, write to the Free Software Foundation,
26# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27
28#----- Revision history -----------------------------------------------------
29#
30# $Log: SWMan.pm,v $
44b3c589 31# Revision 1.2 1999/08/18 17:10:07 mdw
32# Slight improvements to URL and email address parsing.
33#
961ce1c2 34# Revision 1.1 1999/07/30 18:46:37 mdw
35# New CGI script for browsing installed software and documentation.
36#
37
38#----- Package preamble -----------------------------------------------------
39
40package SWMan;
41
42use IO;
43use POSIX;
44use DirHandle;
45use Exporter;
46
47use SWConfig;
48use SWCGI qw(:DEFAULT :layout);
49
50@ISA = qw(Exporter);
51@EXPORT_OK = qw(subst check);
52
53#----- Useful functions -----------------------------------------------------
54
55%mandb = ();
56
57# --- @mans(SECTION)@ ---
58#
59# Returns a reference to a list of manual pages in the given section.
60
61sub mans($) {
62 my ($sec) = @_;
63 $mandb{$sec} and return $mandb{sec};
64
65 my $d = DirHandle->new("$C{prefix}/man/man$sec") or return undef;
66 my @f;
67 while (my $f = $d->read()) {
68 push(@f, $f);
69 }
70 $mandb{$sec} = \@f;
71 return \@f;
72}
73
74# --- @check(NAME, SECTION)@ ---
75#
76# See whether there's a manpage called NAME with section SECTION.
77
78sub check($$) {
79 my $pre = "$C{prefix}/man/man";
80 my ($man, $sec) = @_;
81 my $f;
82
83 # --- Quick check for obvious things ---
84
85 my ($base) = ($sec =~ /^(\d+)/);
86 $f = "$pre$base/$man.$sec";
87 -r $f and return $f; $f .= ".gz"; -r $f and return $f;
88
89 # --- Snarf the appropriate filename list ---
90
91 my $fs = mans($base) or return undef;
92 foreach my $f (@$fs) {
93 $f =~ /^$man\.$sec\w+(\.gz)?$/ and return "$C{prefix}/man/man$base/$f";
94 }
95 return undef;
96}
97
98# --- @subst(STRING, NAME, SECTION)@ ---
99#
100# If NAME(SECTION) is a manual page, return the STRING appropriately wrapped
101# in an anchor element; otherwise return it unmolested.
102
103sub subst($$$) {
104 my ($s, $n, $sec) = @_;
105 check($n, $sec) and
106 return "<a href=\"$ref?act=man&man=$n&sec=$sec\">$s</a>";
107 return "$s";
108}
109
110# --- @sections()@ ---
111#
112# Return a list of manual sections.
113
114@sectionlist = ();
115
116sub sections() {
117 return @sectionlist if @sectionlist;
118 my @s = ();
119 my $d = DirHandle->new("$C{prefix}/man") or
120 barf("couldn't open man directory: $!");
121 while ($f = $d->read()) {
122 next if $f !~ /^man/ || !-d "$C{prefix}/man/$f";
123 push(@s, $');
124 }
125 return (@sectionlist = sort(@s));
126}
127
128#----- Display a quick section index ----------------------------------------
129
130sub quickie {
131 print "Quick section index:\n";
132 foreach $s (sections()) {
133 print "<a href=\"$ref?act=man&sec=$s\">$s</a>\n";
134 }
135}
136
137#----- Display indices for manual sections ----------------------------------
138
139sub dosection($) {
140 my ($sec) = @_;
141 my @m = ();
142
143 barf("illegal section `$sec'") if $sec =~ m:/:;
144
145 # --- Snarf the list of manual pages in this section ---
146
147 {
148 my $d = DirHandle->new("$C{prefix}/man/man$sec") or
149 barf("couldn't read directory `$C{prefix}/man/man$sec': $!");
150 while (my $f = $d->read()) {
151 my ($man, $sec) = split(/\./, $f, 3);
152 push(@m, "$man($sec)") if $sec;
153 }
154 }
155
156 # --- Sort and emit the index ---
157
158 print("<h4>Section $sec</h4>\n<table>");
159
160 {
161 my $col = 0;
162 foreach my $m (sort(@m)) {
163 my ($man, $sec) = $m =~ /^(.*)\((.*)\)$/;
164 $col or print("<tr>\n");
165 print("<td><a href=\"$ref?act=man&man=$man&sec=$sec\">$m</a>\n");
166 $col = ($col + 1) % 5;
167 }
168 }
169
170 print("</table>\n");
171}
172
173sub section {
174 my $sec = $Q{"sec"};
175 header("Index of manual section $sec");
176 quickie(); print "<hr>\n";
177 dosection($sec);
178 print "<hr>\n"; quickie();;
179 footer();
180}
181
182sub index {
183 header("Manual page index");
184 print("<h3>Manual page index</h3>\n");
185 foreach my $s (sections()) { dosection($s); }
186 footer();
187}
188
189#----- Display a manual page ------------------------------------------------
190
191sub man {
192 my ($man, $sec) = ($Q{"man"}, $Q{"sec"});
193
194 $sec or &index(), return;
195 $man or &section(), return;
196
197 my $file = check($man, $sec) or
198 barf("no manual page $man($sec)");
199 barf("illegal filename `$file'") if $file =~ m:\./:;
200
201 # --- Read the manual page ---
202
203 my $p = IO::Pipe->new();
204 my $kid = fork();
205 defined($kid) or barf("fork failed: $!");
206 if ($kid == 0) {
207 $p->writer();
208 dup2($p->fileno(), 1);
209 chdir("$C{prefix}/man");
210 if ($file =~ /\.gz$/) {
211 $pp = IO::Pipe->new;
212 $kkid = fork();
213 defined($kid) or exit(127);
214 if ($kkid == 0) {
215 $pp->writer();
216 dup2($pp->fileno, 1);
217 exec("gzip", "-dc", $file);
218 exit(127);
219 }
220 exec("nroff", "-man");
221 } else {
222 exec("nroff", "-man", $file);
223 }
224 exit(127);
225 }
226 $p->reader();
227
228 # --- Spit out the manual page now ---
229
230 header("Manual page $Q{man}($Q{sec})");
231 quickie(); print "<hr>\n";
232 print "<pre>\n";
233 while (my $line = $p->getline()) {
234 chomp $line;
235
236 # --- Grind through the line turning it into HTML ---
237
238 {
239 my $state = "";
240 my $l = "";
241
242 for (my $i = 0; $i < length($line); $i++) {
243 my $ch = substr($line, $i, 1);
244 my $nstate = "";
245
246 # --- Sort out overstriking ---
247
248 if (substr($line, $i + 1, 1) eq "\b") {
249 my ($italic, $bold) = (0, 0);
250 $ch eq "_" and $italic = 1;
251 $ch eq substr($line, $i + 2, 1) and $bold = 1;
252 $ch = substr($line, $i + 2, 1);
253 while (substr($line, $i + 1, 1) eq "\b") { $i += 2; }
254 if ($italic && $bold) {
255 $nstate = $state ? $state : "b";
256 } elsif ($italic) {
257 $nstate = "i";
258 } elsif ($bold) {
259 $nstate = "b";
260 }
261 }
262 $state ne $nstate and
263 $l .= ($state && "</$state>") . ($nstate && "<$nstate>");
264 $state = $nstate;
265
266 # --- Translate the character if it's magical ---
267
268 $ch eq "&" and $ch = "&amp;";
269 $ch eq "<" and $ch = "&lt;";
270 $ch eq ">" and $ch = "&gt;";
271 $l .= $ch;
272 }
273 $state and $l .= "</$state>";
274
275 # --- Now find manual references in there ---
276 #
277 # I don't use /x regexps very often, but I think this is a good excuse.
278
279 $l =~ s! ((?:\<[bi]\>)*) # Leading highlighting tags
280 ([-_.\w]+) # Various plausible manual name chars
281 ((?:\</[bi]\>)* # Closing highlighting tags
282 (?:\<[bi]\>)* # And opening ones again
283 \( # An open parenthesis
284 (?:\<[bi]\>)*) # More opening highlights
285 (\d+\w*) # The section number
286 ((?:\</[bi]\>)* # Close highlights
287 \) # Close parens
288 (?:\</[bi]\>)*) # Finally more closing tags
289 ! subst($&, $2, $4) !egx;
290
291 # --- And email and hypertext references too ---
292
293 $l =~ s! ((?:\<[bi]\>)*) # Leading highlighting
44b3c589 294 ((?:https?|ftp|file|news) # A protocol name
295 : # The important and obvious bit
961ce1c2 296 [^]&)\s]+ # Most characters are allowed
297 [^]&).,\s\'\"]) # Don't end on punctuation
298 ((?:\</[bi]\>)*) # Closing tags, optional
44b3c589 299 !$1<a href="$2">$&</a>$3!gx;
961ce1c2 300
44b3c589 301 $l =~ s! ((?:\<[bi]\>)*(?:\bmailto:)?)
302 ( [^\s()&;:{}<>,.\`\"] [^\s()&;:{}<>\`\"]* \@
303 [^\s()&;:{}<>\'\"]* [^\s()&;:{}<>.,\'\"])
961ce1c2 304 ((?:\</[bi]\>)*)
44b3c589 305 !$1<a href="mailto:$2">$&</a>$3!gx;
961ce1c2 306
307 # --- Done! ---
308
309 print $l, "\n";
310 }
311 }
312
313 # --- Done all of that ---
314
315 print "</pre>\n";
316 $p->close();
317 waitpid($kid, 0);
318 barf("nroff failed (exit status $?)") if $?;
319 print "<hr>\n"; quickie();;
320 footer();
321}
322
323#----- Register actions -----------------------------------------------------
324
325$main::ACT{"man"} = \&man;
326
327#----- That's all, folks ----------------------------------------------------
328
3291;