manpage-syntax.pl 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  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. #
  26. # Scan man page(s) and detect some simple and yet common formatting mistakes.
  27. #
  28. # Output all deviances to stderr.
  29. use strict;
  30. use warnings;
  31. # get the file name first
  32. my $symbolsinversions=shift @ARGV;
  33. # we may get the dir roots pointed out
  34. my @manpages=@ARGV;
  35. my $errors = 0;
  36. my %optblessed;
  37. my %funcblessed;
  38. my @optorder = (
  39. 'NAME',
  40. 'SYNOPSIS',
  41. 'DESCRIPTION',
  42. #'DEFAULT', # CURLINFO_ has no default
  43. 'PROTOCOLS',
  44. 'EXAMPLE',
  45. 'AVAILABILITY',
  46. 'RETURN VALUE',
  47. 'SEE ALSO'
  48. );
  49. my @funcorder = (
  50. 'NAME',
  51. 'SYNOPSIS',
  52. 'DESCRIPTION',
  53. 'EXAMPLE',
  54. 'AVAILABILITY',
  55. 'RETURN VALUE',
  56. 'SEE ALSO'
  57. );
  58. my %shline; # section => line number
  59. my %symbol;
  60. # some CURLINFO_ symbols are not actual options for curl_easy_getinfo,
  61. # mark them as "deprecated" to hide them from link-warnings
  62. my %deprecated = (
  63. CURLINFO_TEXT => 1,
  64. CURLINFO_HEADER_IN => 1,
  65. CURLINFO_HEADER_OUT => 1,
  66. CURLINFO_DATA_IN => 1,
  67. CURLINFO_DATA_OUT => 1,
  68. CURLINFO_SSL_DATA_IN => 1,
  69. CURLINFO_SSL_DATA_OUT => 1,
  70. );
  71. sub allsymbols {
  72. open(F, "<$symbolsinversions") ||
  73. die "$symbolsinversions: $|";
  74. while(<F>) {
  75. if($_ =~ /^([^ ]*) +(.*)/) {
  76. my ($name, $info) = ($1, $2);
  77. $symbol{$name}=$name;
  78. if($info =~ /([0-9.]+) +([0-9.]+)/) {
  79. $deprecated{$name}=$info;
  80. }
  81. }
  82. }
  83. close(F);
  84. }
  85. sub scanmanpage {
  86. my ($file) = @_;
  87. my $reqex = 0;
  88. my $inex = 0;
  89. my $insynop = 0;
  90. my $exsize = 0;
  91. my $synopsize = 0;
  92. my $shc = 0;
  93. my $optpage = 0; # option or function
  94. my @sh;
  95. my $SH="";
  96. open(M, "<$file") || die "no such file: $file";
  97. if($file =~ /[\/\\](CURL|curl_)[^\/\\]*.3/) {
  98. # This is a man page for libcurl. It requires an example!
  99. $reqex = 1;
  100. if($1 eq "CURL") {
  101. $optpage = 1;
  102. }
  103. }
  104. my $line = 1;
  105. while(<M>) {
  106. chomp;
  107. if($_ =~ /^.so /) {
  108. # this man page is just a referral
  109. close(M);
  110. return;
  111. }
  112. if(($_ =~ /^\.SH SYNOPSIS/i) && ($reqex)) {
  113. # this is for libcurl man page SYNOPSIS checks
  114. $insynop = 1;
  115. $inex = 0;
  116. }
  117. elsif($_ =~ /^\.SH EXAMPLE/i) {
  118. $insynop = 0;
  119. $inex = 1;
  120. }
  121. elsif($_ =~ /^\.SH/i) {
  122. $insynop = 0;
  123. $inex = 0;
  124. }
  125. elsif($inex) {
  126. $exsize++;
  127. if($_ =~ /[^\\]\\n/) {
  128. print STDERR "$file:$line '\\n' need to be '\\\\n'!\n";
  129. }
  130. }
  131. elsif($insynop) {
  132. $synopsize++;
  133. if(($synopsize == 1) && ($_ !~ /\.nf/)) {
  134. print STDERR "$file:$line:1:ERROR: be .nf for proper formatting\n";
  135. }
  136. }
  137. if($_ =~ /^\.SH ([^\r\n]*)/i) {
  138. my $n = $1;
  139. # remove enclosing quotes
  140. $n =~ s/\"(.*)\"\z/$1/;
  141. push @sh, $n;
  142. $shline{$n} = $line;
  143. $SH = $n;
  144. }
  145. if($_ =~ /^\'/) {
  146. print STDERR "$file:$line line starts with single quote!\n";
  147. $errors++;
  148. }
  149. if($_ =~ /\\f([BI])(.*)/) {
  150. my ($format, $rest) = ($1, $2);
  151. if($rest !~ /\\fP/) {
  152. print STDERR "$file:$line missing \\f${format} terminator!\n";
  153. $errors++;
  154. }
  155. }
  156. if($_ =~ /(.*)\\f([^BIP])/) {
  157. my ($pre, $format) = ($1, $2);
  158. if($pre !~ /\\\z/) {
  159. # only if there wasn't another backslash before the \f
  160. print STDERR "$file:$line suspicious \\f format!\n";
  161. $errors++;
  162. }
  163. }
  164. if($optpage && $SH && ($SH !~ /^(SYNOPSIS|EXAMPLE|NAME|SEE ALSO)/i) &&
  165. ($_ =~ /(.*)(CURL(OPT_|MOPT_|INFO_)[A-Z0-9_]*)/)) {
  166. # an option with its own man page, check that it is tagged
  167. # for linking
  168. my ($pref, $symbol) = ($1, $2);
  169. if($deprecated{$symbol}) {
  170. # let it be
  171. }
  172. elsif($pref !~ /\\fI\z/) {
  173. print STDERR "$file:$line option $symbol missing \\fI tagging\n";
  174. $errors++;
  175. }
  176. }
  177. if($_ =~ /[ \t]+$/) {
  178. print STDERR "$file:$line trailing whitespace\n";
  179. $errors++;
  180. }
  181. if($_ =~ /\\f([BI])([^\\]*)\\fP/) {
  182. my $r = $2;
  183. if($r =~ /^(CURL.*)\(3\)/) {
  184. my $rr = $1;
  185. if(!$symbol{$rr}) {
  186. print STDERR "$file:$line link to non-libcurl option $rr!\n";
  187. $errors++;
  188. }
  189. }
  190. }
  191. $line++;
  192. }
  193. close(M);
  194. if($reqex) {
  195. # only for libcurl options man-pages
  196. my $shcount = scalar(@sh); # before @sh gets shifted
  197. if($exsize < 2) {
  198. print STDERR "$file:$line missing EXAMPLE section\n";
  199. $errors++;
  200. }
  201. if($shcount < 3) {
  202. print STDERR "$file:$line too few man page sections!\n";
  203. $errors++;
  204. return;
  205. }
  206. my $got = "start";
  207. my $i = 0;
  208. my $shused = 1;
  209. my @shorig = @sh;
  210. my @order = $optpage ? @optorder : @funcorder;
  211. my $blessed = $optpage ? \%optblessed : \%funcblessed;
  212. while($got) {
  213. my $finesh;
  214. $got = shift(@sh);
  215. if($got) {
  216. if($$blessed{$got}) {
  217. $i = $$blessed{$got};
  218. $finesh = $got; # a mandatory one
  219. }
  220. }
  221. if($i && defined($finesh)) {
  222. # mandatory section
  223. if($i != $shused) {
  224. printf STDERR "$file:%u Got %s, when %s was expected\n",
  225. $shline{$finesh},
  226. $finesh,
  227. $order[$shused-1];
  228. $errors++;
  229. return;
  230. }
  231. $shused++;
  232. if($i == scalar(@order)) {
  233. # last mandatory one, exit
  234. last;
  235. }
  236. }
  237. }
  238. if($i != scalar(@order)) {
  239. printf STDERR "$file:$line missing mandatory section: %s\n",
  240. $order[$i];
  241. printf STDERR "$file:$line section found at index %u: '%s'\n",
  242. $i, $shorig[$i];
  243. printf STDERR " Found %u used sections\n", $shcount;
  244. $errors++;
  245. }
  246. }
  247. }
  248. allsymbols();
  249. if(!$symbol{'CURLALTSVC_H1'}) {
  250. print STDERR "didn't get the symbols-in-version!\n";
  251. exit;
  252. }
  253. my $ind = 1;
  254. for my $s (@optorder) {
  255. $optblessed{$s} = $ind++
  256. }
  257. $ind = 1;
  258. for my $s (@funcorder) {
  259. $funcblessed{$s} = $ind++
  260. }
  261. for my $m (@manpages) {
  262. scanmanpage($m);
  263. }
  264. print STDERR "ok\n" if(!$errors);
  265. exit $errors;