cd2nroff 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. #!/usr/bin/env perl
  2. #***************************************************************************
  3. # _ _ ____ _
  4. # Project ___| | | | _ \| |
  5. # / __| | | | |_) | |
  6. # | (__| |_| | _ <| |___
  7. # \___|\___/|_| \_\_____|
  8. #
  9. # Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
  10. #
  11. # This software is licensed as described in the file COPYING, which
  12. # you should have received as part of this distribution. The terms
  13. # are also available at https://curl.se/docs/copyright.html.
  14. #
  15. # You may opt to use, copy, modify, merge, publish, distribute and/or sell
  16. # copies of the Software, and permit persons to whom the Software is
  17. # furnished to do so, under the terms of the COPYING file.
  18. #
  19. # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
  20. # KIND, either express or implied.
  21. #
  22. # SPDX-License-Identifier: curl
  23. #
  24. ###########################################################################
  25. =begin comment
  26. Converts a curldown file to nroff (man page).
  27. =end comment
  28. =cut
  29. my $cd2nroff = "0.1"; # to keep check
  30. my $dir;
  31. my $extension;
  32. while(1) {
  33. if($ARGV[0] eq "-d") {
  34. shift @ARGV;
  35. $dir = shift @ARGV;
  36. }
  37. elsif($ARGV[0] eq "-e") {
  38. shift @ARGV;
  39. $extension = shift @ARGV;
  40. }
  41. elsif($ARGV[0] eq "-h") {
  42. print <<HELP
  43. Usage: cd2nroff [options] [file.md]
  44. -d <dir> Write the output to the file name from the meta-data in the
  45. specified directory, instead of writing to stdout
  46. -e <ext> If -d is used, this option can provide an added "extension", arbitrary
  47. text really, to append to the file name.
  48. -h This help text,
  49. -v Show version then exit
  50. HELP
  51. ;
  52. exit 0;
  53. }
  54. elsif($ARGV[0] eq "-v") {
  55. print "cd2nroff version $cd2nroff\n";
  56. exit 0;
  57. }
  58. else {
  59. last;
  60. }
  61. }
  62. use POSIX qw(strftime);
  63. my @ts;
  64. if (defined($ENV{SOURCE_DATE_EPOCH})) {
  65. @ts = localtime($ENV{SOURCE_DATE_EPOCH});
  66. } else {
  67. @ts = localtime;
  68. }
  69. my $date = strftime "%B %d %Y", @ts;
  70. sub outseealso {
  71. my (@sa) = @_;
  72. my $comma = 0;
  73. my @o;
  74. push @o, ".SH SEE ALSO\n";
  75. for my $s (sort @sa) {
  76. push @o, sprintf "%s.BR $s", $comma ? ",\n": "";
  77. $comma = 1;
  78. }
  79. push @o, "\n";
  80. return @o;
  81. }
  82. sub single {
  83. my @seealso;
  84. my ($f)=@_;
  85. my $title;
  86. my $section;
  87. my $source;
  88. my $start = 0;
  89. my $errors;
  90. my $fh;
  91. if($f) {
  92. open($fh, "<:crlf", "$f") || return 1;
  93. }
  94. else {
  95. $fh = STDIN;
  96. }
  97. while(<$fh>) {
  98. $line++;
  99. if(!$start) {
  100. if(/^---/) {
  101. # header starts here
  102. $start = 1;
  103. }
  104. next;
  105. }
  106. if(/^Title: *(.*)/i) {
  107. $title=$1;
  108. }
  109. elsif(/^Section: *(.*)/i) {
  110. $section=$1;
  111. }
  112. elsif(/^Source: *(.*)/i) {
  113. $source=$1;
  114. }
  115. elsif(/^See-also: +(.*)/i) {
  116. $salist = 0;
  117. push @seealso, $1;
  118. }
  119. elsif(/^See-also: */i) {
  120. if($seealso[0]) {
  121. print STDERR "$f:$line:1:ERROR: bad See-Also, needs list\n";
  122. return 2;
  123. }
  124. $salist = 1;
  125. }
  126. elsif(/^ +- (.*)/i) {
  127. # the only list we support is the see-also
  128. if($salist) {
  129. push @seealso, $1;
  130. }
  131. }
  132. # REUSE-IgnoreStart
  133. elsif(/^C: (.*)/i) {
  134. $copyright=$1;
  135. }
  136. elsif(/^SPDX-License-Identifier: (.*)/i) {
  137. $spdx=$1;
  138. }
  139. # REUSE-IgnoreEnd
  140. elsif(/^---/) {
  141. # end of the header section
  142. if(!$title) {
  143. print STDERR "ERROR: no 'Title:' in $f\n";
  144. return 1;
  145. }
  146. if(!$section) {
  147. print STDERR "ERROR: no 'Section:' in $f\n";
  148. return 2;
  149. }
  150. if(!$seealso[0]) {
  151. print STDERR "$f:$line:1:ERROR: no 'See-also:' present\n";
  152. return 2;
  153. }
  154. if(!$copyright) {
  155. print STDERR "$f:$line:1:ERROR: no 'C:' field present\n";
  156. return 2;
  157. }
  158. if(!$spdx) {
  159. print STDERR "$f:$line:1:ERROR: no 'SPDX-License-Identifier:' field present\n";
  160. return 2;
  161. }
  162. last;
  163. }
  164. else {
  165. chomp;
  166. print STDERR "WARN: unrecognized line in $f, ignoring:\n:'$_';"
  167. }
  168. }
  169. if(!$start) {
  170. print STDERR "$f:$line:1:ERROR: no header present\n";
  171. return 2;
  172. }
  173. my @desc;
  174. my $quote = 0;
  175. my $blankline = 0;
  176. my $header = 0;
  177. # cut off the leading path from the file name, if any
  178. $f =~ s/^(.*[\\\/])//;
  179. push @desc, ".\\\" generated by cd2nroff $cd2nroff from $f\n";
  180. push @desc, ".TH $title $section \"$date\" $source\n";
  181. while(<$fh>) {
  182. $line++;
  183. $d = $_;
  184. if($quote) {
  185. if($quote == 4) {
  186. # remove the indentation
  187. if($d =~ /^ (.*)/) {
  188. push @desc, "$1\n";
  189. next;
  190. }
  191. else {
  192. # end of quote
  193. $quote = 0;
  194. push @desc, ".fi\n";
  195. next;
  196. }
  197. }
  198. if(/^~~~/) {
  199. # end of quote
  200. $quote = 0;
  201. push @desc, ".fi\n";
  202. next;
  203. }
  204. # convert single backslahes to doubles
  205. $d =~ s/\\/\\\\/g;
  206. # lines starting with a period needs it escaped
  207. $d =~ s/^\./\\&./;
  208. push @desc, $d;
  209. next;
  210. }
  211. # **bold**
  212. $d =~ s/\*\*(\S.*?)\*\*/\\fB$1\\fP/g;
  213. # *italics*
  214. $d =~ s/\*(\S.*?)\*/\\fI$1\\fP/g;
  215. # mentions of curl symbols with man pages use italics by default
  216. $d =~ s/((lib|)curl([^ ]*\(3\)))/\\fI$1\\fP/gi;
  217. # backticked becomes italics
  218. $d =~ s/\`(.*?)\`/\\fI$1\\fP/g;
  219. if(/^## (.*)/) {
  220. my $word = $1;
  221. # if there are enclosing quotes, remove them first
  222. $word =~ s/[\"\'](.*)[\"\']\z/$1/;
  223. # enclose in double quotes if there is a space present
  224. if($word =~ / /) {
  225. push @desc, ".IP \"$word\"\n";
  226. }
  227. else {
  228. push @desc, ".IP $word\n";
  229. }
  230. $header = 1;
  231. }
  232. elsif(/^# (.*)/) {
  233. my $word = $1;
  234. # if there are enclosing quotes, remove them first
  235. $word =~ s/[\"\'](.*)[\"\']\z/$1/;
  236. push @desc, ".SH $word\n";
  237. $header = 1;
  238. }
  239. elsif(/^~~~c/) {
  240. # start of a code section, not indented
  241. $quote = 1;
  242. push @desc, "\n" if($blankline && !$header);
  243. $header = 0;
  244. push @desc, ".nf\n";
  245. }
  246. elsif(/^~~~/) {
  247. # start of a quote section; not code, not indented
  248. $quote = 1;
  249. push @desc, "\n" if($blankline && !$header);
  250. $header = 0;
  251. push @desc, ".nf\n";
  252. }
  253. elsif(/^ (.*)/) {
  254. # quoted, indented by 4 space
  255. $quote = 4;
  256. push @desc, "\n" if($blankline && !$header);
  257. $header = 0;
  258. push @desc, ".nf\n$1\n";
  259. }
  260. elsif(/^[ \t]*\n/) {
  261. # count and ignore blank lines
  262. $blankline++;
  263. }
  264. else {
  265. # don't output newlines if this is the first content after a
  266. # header
  267. push @desc, "\n" if($blankline && !$header);
  268. $blankline = 0;
  269. $header = 0;
  270. # remove single line HTML comments
  271. $d =~ s/<!--.*?-->//g;
  272. # quote minuses in the output
  273. $d =~ s/([^\\])-/$1\\-/g;
  274. # replace single quotes
  275. $d =~ s/\'/\\(aq/g;
  276. # handle double quotes first on the line
  277. $d =~ s/^(\s*)\"/$1\\&\"/;
  278. # lines starting with a period needs it escaped
  279. $d =~ s/^\./\\&./;
  280. if($d =~ /^(.*) /) {
  281. printf STDERR "$f:$line:%d:ERROR: 2 spaces detected\n",
  282. length($1);
  283. $errors++;
  284. }
  285. if($d =~ /^[ \t]*\n/) {
  286. # replaced away all contents
  287. $blankline= 1;
  288. }
  289. else {
  290. push @desc, $d;
  291. }
  292. }
  293. }
  294. close($fh);
  295. push @desc, outseealso(@seealso);
  296. if($dir) {
  297. open(O, ">$dir/$title.$section$extension");
  298. print O @desc;
  299. close(O);
  300. }
  301. else {
  302. print @desc;
  303. }
  304. return $errors;
  305. }
  306. exit single($ARGV[0]);