1#!/usr/bin/perl
2# $Id: $
3# Written by Adrian Mariano, additional features by Eric Backus and
4# Jeff Conrad.
5
6# Script to translate a texinfo file into an nroff/troff manual page.
7# last revision: 20 January 2014 Jeff Conrad
8
9$version="1.01s";
10
11$html=0;
12$example=0;
13$ignore=0;
14$tex=0;
15$doman=0;
16$title=0;
17$diditem=0;
18$justdidlp=1;
19$noman=0;
20$manprefix="";
21$args=($#ARGV < 0) ? "stdin" : "@ARGV";
22
23printf(".\\\"Do not edit this file.  It was created from %s\n", $args);
24printf(".\\\"using texi2man version %s on %s", $version, `date`);
25
26while(<>)
27{
28    # use font CW in tables
29    if (/\@c man\s+l\s/)
30    {
31	s/\@c man //;
32	s/l/lfCWp-1/;
33	print;
34	next;
35    }
36    if (s/\@c man //)
37    {
38	print;
39	if (/\.TH/) { add_extensions(); }
40	next;
41    }
42    if (/\@c noman/) { $noman=1; next; }
43    if (/\@c end noman/) { $noman=0; next; }
44    if ($noman) { next; }
45
46    if (/\@c ifman\s*(.*)/) { $doman=1; $manprefix = $1; next; }
47    if (/\@c end ifman/) { $doman=0; $manprefix = ""; next; }
48
49    if (/^\\input/) { next; }
50    if (/^\*/) { next; }
51    if (/^START-INFO-DIR-ENTRY/) { next; }
52    if (/^END-INFO-DIR-ENTRY/) { next; }
53
54    if (/\@titlepage/) { $title=1; next; }
55    if (/\@end titlepage/) { $title=0; next; }
56    if (/\@tex/) { $tex=1; next; }
57    if (/\@end tex/) { $tex=0; next; }
58    if (/\@ignore/) { $ignore=1; next; }
59    if (/\@end ignore/) { $ignore=0; next; }
60    if (/\@ifhtml/) { $html=1; next; }
61    if (/\@end ifhtml/) { $html=0; next; }
62    if (!$doman && ($ignore || $html || $title || $tex)) { next; }
63    if (/\@codequoteundirected/) { next; }
64
65    s/\@\*$/\n\.br/g;
66    s/^\@\*/.br/g;
67    s/\@\*$/\n.br/g;
68    s/\@ / /g;
69    s/\@dmn\{([^}]*)}/\\|$1/g;
70    s/\@tie\{}/\@no_break_space\{}/g;
71    s/\@w\{}/\@no_break_space\{}/g;
72    s/\@backslashchar\{}/\\e/g;
73
74    # ellipsis, defined in extensions
75    s/\@dots\{}/\\*(El/g;
76
77    s/\@cite\{([^}]*)}/\@in_sgl_quotes\{$1}/g;
78    s/\@url\{([^}]*)}/\@in_sgl_quotes\{$1}/g;
79    s/\@email\{([^}]*)}/\@in_sgl_quotes\{$1}/g;
80
81    s/\@dfn\{([^}]*)}/\@in_italics\{$1}/g;
82
83    s/\@emph\{([^}]*)}/\@in_italics\{$1}/g;
84    s/\@i\{([^}]*)}/\@in_italics\{$1}/g;
85    s/\@r\{([^}]*)}/\@in_roman\{$1}/g;
86    s/\@var\{([^}]*)}/\@in_italics\{$1}/g;
87
88    s/\@b\{([^}]*)}/\@in_bold\{$1}/g;
89    s/\@strong\{([^}]*)}/\@in_bold\{$1}/g;
90
91    # remove trailing comma from xref because man won't include the page number
92    s/\@xref\{([^}]*)},/\@xref\{$1}/g;
93    s/\@xref\{([^}]*)}/See \@in_italics\{$1}/g;
94    s/\@ref\{([^}]*)}/\@ref\{$1}/g;
95    s/\@ref\{([^}]*)}/\@in_italics\{$1}/g;
96    s/\@pxref\{([^}]*)}/see \@in_italics\{$1}/g;
97    s/\@uref\{([^}]*)}/\@in_roman\{$1}/g;
98
99    if (/\@chapter.*\@command/)
100    {
101	s/\@command\{([^}]*)}/\@in_italics\{$1}/g;
102    }
103
104    # show in constant-width font
105    s/\@code\{([^}]*)}/\@constwid\{$1}/g;
106    s/\@command\{([^}]*)}/\@constwid\{$1}/g;
107    s/\@env\{([^}]*)}/\@constwid\{$1}/g;
108
109    # show in constant-width oblique font
110    s/\@kbd\{([^}]*)}/\@constwidI\{$1}/g;
111
112    # show in constant-width font with single quotes
113    s/\@file\{([^}]*)}/\@constwidQ\{$1}/g;
114    s/\@option\{([^}]*)}/\@constwidQ\{$1}/g;
115    # Pass ASCII double quotes to .CQ encoded as two double quotes
116    # disallow single quotes here because groff converts them to
117    # typographical closing quotes.
118    # This substitution works only in very limited circumstances, and
119    # needs extension to handle the general case of ASCII quotes in
120    # sample text
121    s/\@samp\{([^}]*)["']{2,2}}/\@samp\{$1""""}/g;
122    s/\@samp\{(.*\@(tie|no_break_space)\{})["']{2,2}}/\@samp\{$1""""}/g;
123    s/\@samp\{([^}]*)}/\@constwidQ\{$1}/g;
124
125    s/\@sc\{([^}]*)}/\@to_upper\{$1}/g;
126
127    s/\@key\{([^}]*)}/\@in_italics\{$1}/g;
128    s/\@footnote\{([^}]*)}/\@in_square_br\{$1}/g;
129
130    s/\@math\{([^}]*)}/\@no_decoration\{$1}/g;
131
132    if (/\@w\{([^}]*)}/) {
133	s/\@w\{([^}]*)}/\@no_break_word\{$1}/g;
134    }
135
136    s/\@minus\{}/\\-/g;
137    s/\@copyright\{}/\\(co/g;
138    s/\@noindent//;
139    s/\@\{/{/g;
140    s/\@}/}/g;
141    s/\@\@/@/g;
142    s/---/\\(em/g;
143
144    s/\@in_sgl_quotes\{([^}]+)}/`$1'/g;
145    s/\@in_dbl_quotes\{([^}]+)}/\"$1\"/g;
146    s/\@in_italics\{([^}]+)}/\\fI$1\\fP/g;
147    s/\@in_roman\{([^}]+)}/\\fR$1\\fP/g;
148    s/\@in_bold\{([^}]+)}/\\fB$1\\fP/g;
149    s/\@to_upper\{([^}]*)}/\U$1\E/g;
150    s/\@no_decoration\{([^}]*)}/$1/g;
151    if (/\@no_break_word\{([^}]+)}(\S*)/) {
152	$_ = no_break_word("$_", '@no_break_word');
153    }
154    s/\@no_break_space\{}/\\ /g;
155    s/\@[ ]/ /g;
156    s/\@in_angle_br\{([^}]*)}/<$1>/g;
157    s/\@in_square_br\{([^}]*)}/[$1]/g;
158
159    # set up to use CW, CI, and CQ macros
160    # put every instance on a new line
161    # ensure that prepended and appended macros go on separate lines
162    # separate concatenated commands with spaces
163    s/([}])(\@constwid[IQ]*)/$1 $2/g;
164    s/(\@constwid[IQ]*\{[^}]+})(\@)/$1 $2/g;
165    # space before -> newline
166    s/\s+(\S*\@constwid[IQ]*\{[^}]+}\S*)/\n$1/g;
167    # space after -> newline
168    s/(\S*\@constwid[IQ]*\{[^}]+}\S*)\s+/$1\n/g;
169
170    if (/(\S*)\@constwidI\{([^}]+)}(\S*)/) {
171	$_ = CW_macro("$_", '@constwidI', ".CI");
172    }
173    if (/(\S*)\@constwidQ\{([^}]+)}(\S*)/) {
174	$_ = CW_macro("$_", '@constwidQ', ".CQ");
175    }
176    if (/(\S*)\@constwid\{([^}]+)}(\S*)/) {
177	$_ = CW_macro("$_", '@constwid', ".CW");
178    }
179
180    # handle backslash character in sample
181    s/(\.C[IQW]\s+\S+\s+)"\\"/$1"\\e"/g;
182    s/(\.C[IQW]\s+)"\\"/$1"\\e"/g;
183    # handle backslash character in Windows pathname
184    # starts with a drive specifier ...
185    if (/(\.C[IQW]\s+"[[:alpha:]]:)/) {
186	# don't change font switches or escaped spaces
187	s/(\S)\\(?!(\s|f[RIBP]|f\([A-Z]{2}))/$1\\e/g;
188    }
189    # some versions of n/troff don't have \(en, so use \-
190    # don't replace double hyphens in C[IQW] macros; assume true en
191    # dashes will be closed up to previous word
192    s/([^" ]+)--/$1\\-/g;
193
194    s/\@value\{([^\s]+)}/$value{$1}/eg;
195    if (/\@set\s+([^\s]+)\s+(.*)$/) { $value{$1} = $2; next; }
196    if (/\@clear\s+([^\s]+)\s+(.*)$/) { delete $value{$1}; next; }
197
198    # works only for @item and @itemx as used in units(1)
199    if (/\@itemx (.*)/)
200    {
201	$samp = $1;
202	# add hair space to visually separate the hyphens in roman type
203	$samp =~ s/--/-\\^-/;
204	$samp =~ s/-([[:alnum:]])/-\\^$1/;
205	if (!$diditem)
206	    { printf(".TP\n.BR \"$samp\""); }
207	else
208	    { printf(" \", \" \"$samp\""); }
209	$diditem=1; next;
210    }
211    elsif ($diditem) { printf("\n"); $diditem=0; }
212    if (/\@item (.*)/)
213    {
214	$samp = $1;
215	# add hair space to visually separate the hyphens in roman type
216	$samp =~ s/--/-\\^-/;
217	$samp =~ s/-([[:alnum:]])/-\\^$1/;
218	printf("%s.TP\n%s.BR \"$samp\"", $manprefix, $manprefix);
219	$diditem=1;
220	next;
221    }
222
223    if (s/\@chapter (.*)/.SH \U$1\E/)
224    {
225	# restore proper case on font switches
226	s/\\FR/\\fR/g;
227	s/\\FI/\\f(BI/g;	# chapter headings (SH in man) are bold
228	s/\\FP/\\fP/g;
229	printf("%s%s", $manprefix, $_);
230	$justdidlp=1;
231	next;
232    }
233
234    if (s/\@section (.*)/$1/)
235    {
236	printf("%s.SS %s", $manprefix, $_);
237	next;
238    }
239
240    # FIXME? why do we need $manprefix for these?
241    # input/output example macros
242    if (/\@example/) { printf("%s.ES\n", $manprefix); $example=1; next; }
243    if (/\@end example/) { printf("%s.EE\n", $manprefix); $example=0; $justdidlp=0; next; }
244
245    if (/\@smallexample/) { printf("%s.ES S\n", $manprefix); $example=1; next; }
246    if (/\@end smallexample/) { printf("%s.EE\n", $manprefix); $example=0; $justdidlp=0; next; }
247
248    # no CW font
249    if (/\@display/) { printf("%s.DS\n", $manprefix, $manprefix); $example=1; next; }
250    if (/\@end display/) { printf("%s.DE\n", $manprefix, $manprefix); $example=0; next; }
251
252    # no CW font, no indent
253    if (/\@format/) { printf("%s.nf\n", $manprefix); $example=1; next; }
254    if (/\@end format/) { printf("%s.fi\n", $manprefix); $example=0; next; }
255
256
257    if ($example) { s/\\\s*$/\\e\n/ };
258
259    if (!$example && /^\s*$/ && !$doman)
260    {
261	if ($justdidlp) { next; }
262	printf(".PP\n");
263	$justdidlp=1;
264	next;
265    }
266
267    if (/^\@/) { next; }
268
269    printf("%s%s", $manprefix, $_);
270
271    if (!$doman) { $justdidlp=0; }
272}
273
274# Extensions to legacy man macro package. groff loads the man macro file
275# after the call of TH, so these definitions must likewise follow that
276# call of TH if they are overwrite any groff extensions with the same
277# names that might be added in the future.
278
279sub add_extensions
280{
281    # ensure that ASCII circumflex U+005E (^) is not remapped with groff
282    printf(".\\\"\n");
283    printf(".\\\" ensure that ASCII circumflex U+005E (^) is not remapped with groff\n");
284    printf(".if \\n(.g .tr ^\\(ha\n");
285
286    # ellipsis: space periods with troff but not with nroff
287    printf(".\\\" ellipsis: space periods with troff but not with nroff\n");
288    printf(".if n .ds El \\&...\n");
289    printf(".if t .ds El \\&.\\ .\\ .\n");
290
291    # constant-width font
292    printf(".\\\"\n");
293    printf(".\\\" Extensions to man macros\n");
294    printf(".\\\"\n");
295    printf(".\\\" Constant-width font\n");
296    printf(".de CW\n");
297    printf(".hy 0\n");
298    # just single quotes with nroff
299    printf(".if n \\{\\\n");
300    printf(".ie \\\\n(.\$>2 \\&\\\\\$1'\\\\\$2'\\\\\$3\n");
301    printf(".el \\&'\\\\\$1'\\\\\$2\n");
302    printf(".\\}\n");
303    # constant-width font with troff
304    printf(".if t \\{\\\n");
305    printf(".ie \\\\n(.\$>2 \\&\\\\\$1\\f(CW\\\\\$2\\fR\\\\\$3\n");
306    printf(".el \\&\\f(CW\\\\\$1\\fR\\\\\$2\n");
307    printf(".\\}\n");
308    printf(".hy 14\n");
309    printf("..\n");
310
311    # constant-width oblique font
312    printf(".\\\" Constant-width oblique font\n");
313    printf(".de CI\n");
314    printf(".hy 0\n");
315    # single quotes with nroff
316    printf(".if n \\{\\\n");
317    printf(".ie \\\\n(.\$>2 \\&\\\\\$1'\\fI\\\\\$2\\fR'\\\\\$3\n");
318    printf(".el \\&'\\fI\\\\\$1\\fR'\\\\\$2\n");
319    printf(".\\}\n");
320    # constant-width oblique font with troff
321    printf(".if t \\{\\\n");
322    printf(".ie \\\\n(.\$>2 \\&\\\\\$1\\f(CI\\\\\$2\\fR\\\\\$3\n");
323    printf(".el \\&\\f(CI\\\\\$1\\fR\\\\\$2\n");
324    printf(".\\}\n");
325    printf(".hy 14\n");
326    printf("..\n");
327
328    # constant-width font with quotes with troff
329    printf(".\\\" Constant-width font with quotes\n");
330    printf(".de CQ\n");
331    printf(".hy 0\n");
332    # just single quotes with nroff
333    printf(".if n \\{\\\n");
334    printf(".ie \\\\n(.\$>2 \\&\\\\\$1'\\\\\$2'\\\\\$3\n");
335    printf(".el \\&'\\\\\$1'\\\\\$2\n");
336    printf(".\\}\n");
337    # constant-width font with troff
338    printf(".if t \\{\\\n");
339    # quotes passed as literal text encoded as \(fm
340    # make it a double quote because groff converts ` and ' to opening and
341    # closing quotes
342    printf(".ie \\\\n(.\$>2 \\&\\\\\$1`\\f(CW\\\\\$2\\fR'\\\\\$3\n");
343    printf(".el \\&`\\f(CW\\\\\$1\\fR'\\\\\$2\n");
344    printf(".\\}\n");
345    printf(".hy 14\n");
346    printf("..\n");
347
348    # Display Start--indent, no fill
349    printf(".\\\" Display start\n");
350    printf(".de DS\n");
351    printf(".hy 0\n");
352    printf(".if t .in +4n\n");
353    printf(".if n .in +3n\n");
354    printf(".nf\n");
355    printf("..\n");
356
357    # Display End
358    printf(".\\\" Display end\n");
359    printf(".de DE\n");
360    printf(".fi\n");
361    printf(".in\n");
362    printf(".hy 14\n");
363    printf("..\n");
364
365    # Example Start--like display, but with font CW
366    printf(".\\\" Example start\n");
367    printf(".de ES\n");
368    # call before size or font change to get consistent indent
369    printf(".DS\n");
370    # CW font with troff; optionally reduce size
371    printf(".if t \\{\\\n");
372    printf(".if '\\\\\$1'S' \\{\\\n");
373    printf(".nr Ex 1\n");
374    printf(".ps -1\n");
375    printf(".\\}\n");
376    printf(".el .nr Ex 0\n");
377    printf(".nr mE \\\\n(.f\n");
378    printf(".ft CW\n");
379    printf(".\\}\n");
380    printf("..\n");
381
382    # Example End
383    printf(".\\\" Example end\n");
384    printf(".de EE\n");
385    # restore font and size with troff
386    printf(".if t \\{\\\n");
387    printf(".ft \\\\n(mE\n");
388    printf(".if \\\\n(Ex=1 .ps\n");
389    printf(".\\}\n");
390    printf(".DE\n");
391    printf("..\n");
392}
393
394# convert texinfo commands to .C[IQW] macros
395sub CW_macro
396{
397    my $line = shift;
398    my $from = shift;
399    my $to = shift;
400
401    # prepended and appended punctuation
402    $line =~ s/(\S+)$from\{([^}]+)}(\S+)/$to $1 "$2" $3/g;
403    # prepended punctuation
404    $line =~ s/(\S+)$from\{([^}]+)}/$to $1 "$2" ""/g;
405    # appended punctuation
406    $line =~ s/$from\{([^}]+)}(\S+)/$to "$1" $2/g;
407    # just the argument
408    $line =~ s/$from\{([^}]+)}/$to "$1"/g;
409
410    return $line;
411}
412
413# convert all spaces within @w{...} to unbreakable
414sub no_break_word
415{
416    my $line = shift;
417    my $pattern = (shift) . "\{";
418    my $len = length($pattern);
419    my $ndx = -1;
420    my $bracelevel = 0;
421    my $char;
422
423    while (($ndx = index($line, $pattern, $ndx)) > -1) {
424	# get rid of the @ command and opening brace
425	substr($line, $ndx, $len, '');
426	$bracelevel = 1;
427	while ($bracelevel > 0) {
428	    $char = substr($line, $ndx, 1);
429	    # end of line and braces not closed
430	    if ($char eq "") {
431		last;
432	    }
433	    elsif ($char eq '{') {
434		$bracelevel++;
435	    }
436	    elsif ($char eq '}') {
437		$bracelevel--;
438	    }
439	    # make spaces nonbreaking
440	    if ($char eq ' ') {
441		substr($line, $ndx++, 1, '\ ');
442		$ndx++;
443		# assume multiple spaces are not wanted
444		while (substr($line, $ndx, 1) eq ' ') {
445		    substr($line, $ndx, 1, '');
446		}
447	    }
448	    $ndx++;
449	}
450	# get rid of the closing brace for the @ command. This should
451	# always be true unless there�s an internal brace mismatch ...
452	if (substr($line, $ndx - 1, 1) eq '}' ) {
453	    substr($line, $ndx - 1, 1, '');
454	}
455	else {
456	    die "Missing closing '}'";
457	}
458    }
459
460    return $line;
461}
462
463
464