manpage-syntax.pl 6.4 KB

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