/contrib/cvs/doc/mkman.pl

https://bitbucket.org/freebsd/freebsd-head/ · Perl · 372 lines · 235 code · 40 blank · 97 comment · 27 complexity · 3424cb01d43035a8fd70bb27420c62d5 MD5 · raw file

  1. #! @PERL@
  2. #
  3. # Generate a man page from sections of a Texinfo manual.
  4. #
  5. # Copyright 2004, 2006
  6. # The Free Software Foundation,
  7. # Derek R. Price,
  8. # & Ximbiot <http://ximbiot.com>
  9. #
  10. # This program is free software; you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation; either version 2, or (at your option)
  13. # any later version.
  14. #
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with this program; if not, write to the Free Software Foundation,
  22. # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  23. # Need Perl 5.005 or greater for re 'eval'.
  24. require 5.005;
  25. # The usual.
  26. use strict;
  27. use IO::File;
  28. ###
  29. ### GLOBALS
  30. ###
  31. my $texi_num = 0; # Keep track of how many texinfo files have been encountered.
  32. my @parent; # This needs to be global to be used inside of a regex later.
  33. my $nk; # Ditto.
  34. my $ret; # The RE match Type, used in debug prints.
  35. my $debug = 0; # Debug mode?
  36. ###
  37. ### FUNCTIONS
  38. ###
  39. sub debug_print
  40. {
  41. print @_ if $debug;
  42. }
  43. sub keyword_mode
  44. {
  45. my ($keyword, $file) = @_;
  46. return "\\fR"
  47. if $keyword =~ /^(|r|t)$/;
  48. return "\\fB"
  49. if $keyword =~ /^(strong|sc|code|file|samp)$/;
  50. return "\\fI"
  51. if $keyword =~ /^(emph|var|dfn)$/;
  52. die "no handler for keyword \`$keyword', found at line $. of file \`$file'\n";
  53. }
  54. # Return replacement for \@$keyword{$content}.
  55. sub do_keyword
  56. {
  57. my ($file, $parent, $keyword, $content) = @_;
  58. return "`$content\\(aq in the CVS manual"
  59. if $keyword eq "ref";
  60. return "see node `$content\\(aq in the CVS manual"
  61. if $keyword =~ /^p?xref$/;
  62. return "\\fP\\fP$content"
  63. if $keyword =~ /^splitrcskeyword$/;
  64. my $endmode = keyword_mode $parent;
  65. my $startmode = keyword_mode $keyword, $file;
  66. return "$startmode$content$endmode";
  67. }
  68. ###
  69. ### MAIN
  70. ###
  71. for my $file (@ARGV)
  72. {
  73. my $fh = new IO::File "< $file"
  74. or die "Failed to open file \`$file': $!";
  75. if ($file !~ /\.(texinfo|texi|txi)$/)
  76. {
  77. print stderr "Passing \`$file' through unprocessed.\n";
  78. # Just cat any file that doesn't look like a Texinfo source.
  79. while (my $line = $fh->getline)
  80. {
  81. print $line;
  82. }
  83. next;
  84. }
  85. print stderr "Processing \`$file'.\n";
  86. $texi_num++;
  87. my $gotone = 0;
  88. my $inblank = 0;
  89. my $indent = 0;
  90. my $inexample = 0;
  91. my $inmenu = 0;
  92. my $intable = 0;
  93. my $last_header = "";
  94. my @table_headers;
  95. my @table_footers;
  96. my $table_header = "";
  97. my $table_footer = "";
  98. my $last;
  99. while ($_ = $fh->getline)
  100. {
  101. if (!$gotone && /^\@c ----- START MAN $texi_num -----$/)
  102. {
  103. $gotone = 1;
  104. next;
  105. }
  106. # Skip ahead until our man section.
  107. next unless $gotone;
  108. # If we find the end tag we are done.
  109. last if /^\@c ----- END MAN $texi_num -----$/;
  110. # Need to do this everywhere. i.e., before we print example
  111. # lines, since literal back slashes can appear there too.
  112. s/\\/\\\\/g;
  113. s/^\./\\&./;
  114. s/([\s])\./$1\\&./;
  115. s/'/\\(aq/g;
  116. s/`/\\`/g;
  117. s/(?<!-)---(?!-)/\\(em/g;
  118. s/\@bullet({}|\b)/\\(bu/g;
  119. s/\@dots({}|\b)/\\&.../g;
  120. # Examples should be indented and otherwise untouched
  121. if (/^\@example$/)
  122. {
  123. $indent += 2;
  124. print qq{.SP\n.PD 0\n};
  125. $inexample = 1;
  126. next;
  127. }
  128. if ($inexample)
  129. {
  130. if (/^\@end example$/)
  131. {
  132. $indent -= 2;
  133. print qq{\n.PD\n.IP "" $indent\n};
  134. $inexample = 0;
  135. next;
  136. }
  137. if (/^[ ]*$/)
  138. {
  139. print ".SP\n";
  140. next;
  141. }
  142. # Preserve the newline.
  143. $_ = qq{.IP "" $indent\n} . $_;
  144. }
  145. # Compress blank lines into a single line. This and its
  146. # corresponding skip purposely bracket the @menu and comment
  147. # removal so that blanks on either side of a menu are
  148. # compressed after the menu is removed.
  149. if (/^[ ]*$/)
  150. {
  151. $inblank = 1;
  152. next;
  153. }
  154. # Not used
  155. if (/^\@(ignore|menu)$/)
  156. {
  157. $inmenu++;
  158. next;
  159. }
  160. # Delete menu contents.
  161. if ($inmenu)
  162. {
  163. next unless /^\@end (ignore|menu)$/;
  164. $inmenu--;
  165. next;
  166. }
  167. # Remove comments
  168. next if /^\@c(omment)?\b/;
  169. # Ignore includes.
  170. next if /^\@include\b/;
  171. # It's okay to ignore this keyword - we're not using any
  172. # first-line indent commands at all.
  173. next if s/^\@noindent\s*$//;
  174. # @need is only significant in printed manuals.
  175. next if s/^\@need\s+.*$//;
  176. # If we didn't hit the previous check and $inblank is set, then
  177. # we just finished with some number of blanks. Print the man
  178. # page blank symbol before continuing processing of this line.
  179. if ($inblank)
  180. {
  181. print ".SP\n";
  182. $inblank = 0;
  183. }
  184. # Chapter headers.
  185. $last_header = $1 if s/^\@node\s+(.*)$/.SH "$1"/;
  186. if (/^\@appendix\w*\s+(.*)$/)
  187. {
  188. my $content = $1;
  189. $content =~ s/^$last_header(\\\(em|\s+)?//;
  190. next if $content =~ /^\s*$/;
  191. s/^\@appendix\w*\s+.*$/.SS "$content"/;
  192. }
  193. # Tables are similar to examples, except we need to handle the
  194. # keywords.
  195. if (/^\@(itemize|table)(\s+(.*))?$/)
  196. {
  197. $indent += 2;
  198. push @table_headers, $table_header;
  199. push @table_footers, $table_footer;
  200. my $content = $3;
  201. if (/^\@itemize/)
  202. {
  203. my $bullet = $content;
  204. $table_header = qq{.IP "$bullet" $indent\n};
  205. $table_footer = "";
  206. }
  207. else
  208. {
  209. my $hi = $indent - 2;
  210. $table_header = qq{.IP "" $hi\n};
  211. $table_footer = qq{\n.IP "" $indent};
  212. if ($content)
  213. {
  214. $table_header .= "$content\{";
  215. $table_footer = "\}$table_footer";
  216. }
  217. }
  218. $intable++;
  219. next;
  220. }
  221. if ($intable)
  222. {
  223. if (/^\@end (itemize|table)$/)
  224. {
  225. $table_header = pop @table_headers;
  226. $table_footer = pop @table_footers;
  227. $indent -= 2;
  228. $intable--;
  229. next;
  230. }
  231. s/^\@itemx?(\s+(.*))?$/$table_header$2$table_footer/;
  232. # Fall through so the rest of the table lines are
  233. # processed normally.
  234. }
  235. # Index entries.
  236. s/^\@cindex\s+(.*)$/.IX "$1"/;
  237. $_ = "$last$_" if $last;
  238. undef $last;
  239. # Trap keywords
  240. $nk = qr/
  241. \@(\w+)\{
  242. (?{ debug_print "$ret MATCHED $&\nPUSHING $1\n";
  243. push @parent, $1; }) # Keep track of the last keyword
  244. # keyword we encountered.
  245. ((?>
  246. [^{}]|(?<=\@)[{}] # Non-braces...
  247. | # ...or...
  248. (??{ $nk }) # ...nested keywords...
  249. )*) # ...without backtracking.
  250. \}
  251. (?{ debug_print "$ret MATCHED $&\nPOPPING ",
  252. pop (@parent), "\n"; }) # Lose track of the current keyword.
  253. /x;
  254. $ret = "m//";
  255. if (/\@\w+\{(?:[^{}]|(?<=\@)[{}]|(??{ $nk }))*$/)
  256. {
  257. # If there is an opening keyword on this line without a
  258. # close bracket, we need to find the close bracket
  259. # before processing the line. Set $last to append the
  260. # next line in the next pass.
  261. $last = $_;
  262. next;
  263. }
  264. # Okay, the following works somewhat counter-intuitively. $nk
  265. # processes the whole line, so @parent gets loaded properly,
  266. # then, since no closing brackets have been found for the
  267. # outermost matches, the innermost matches match and get
  268. # replaced first.
  269. #
  270. # For example:
  271. #
  272. # Processing the line:
  273. #
  274. # yadda yadda @code{yadda @var{foo} yadda @var{bar} yadda}
  275. #
  276. # Happens something like this:
  277. #
  278. # 1. Ignores "yadda yadda "
  279. # 2. Sees "@code{" and pushes "code" onto @parent.
  280. # 3. Ignores "yadda " (backtracks and ignores "yadda yadda
  281. # @code{yadda "?)
  282. # 4. Sees "@var{" and pushes "var" onto @parent.
  283. # 5. Sees "foo}", pops "var", and realizes that "@var{foo}"
  284. # matches the overall pattern ($nk).
  285. # 6. Replaces "@var{foo}" with the result of:
  286. #
  287. # do_keyword $file, $parent[$#parent], $1, $2;
  288. #
  289. # which would be "\Ifoo\B", in this case, because "var"
  290. # signals a request for italics, or "\I", and "code" is
  291. # still on the stack, which means the previous style was
  292. # bold, or "\B".
  293. #
  294. # Then the while loop restarts and a similar series of events
  295. # replaces "@var{bar}" with "\Ibar\B".
  296. #
  297. # Then the while loop restarts and a similar series of events
  298. # replaces "@code{yadda \Ifoo\B yadda \Ibar\B yadda}" with
  299. # "\Byadda \Ifoo\B yadda \Ibar\B yadda\R".
  300. #
  301. $ret = "s///";
  302. @parent = ("");
  303. while (s/$nk/do_keyword $file, $parent[$#parent], $1, $2/e)
  304. {
  305. # Do nothing except reset our last-replacement
  306. # tracker - the replacement regex above is handling
  307. # everything else.
  308. debug_print "FINAL MATCH $&\n";
  309. @parent = ("");
  310. }
  311. # Finally, unprotect texinfo special characters.
  312. s/\@://g;
  313. s/\@([{}])/$1/g;
  314. # Verify we haven't left commands unprocessed.
  315. die "Unprocessed command at line $. of file \`$file': "
  316. . ($1 ? "$1\n" : "<EOL>\n")
  317. if /^(?>(?:[^\@]|\@\@)*)\@(\w+|.|$)/;
  318. # Unprotect @@.
  319. s/\@\@/\@/g;
  320. # And print whatever's left.
  321. print $_;
  322. }
  323. }