github.com/AbhinandanKurakure/podman/v3@v3.4.10/hack/xref-helpmsgs-manpages (about) 1 #!/usr/bin/perl 2 # 3 # xref-helpmsgs-manpages - cross-reference --help options against man pages 4 # 5 package LibPod::CI::XrefHelpmsgsManpages; 6 7 use v5.14; 8 use utf8; 9 10 use strict; 11 use warnings; 12 13 (our $ME = $0) =~ s|.*/||; 14 our $VERSION = '0.1'; 15 16 # For debugging, show data structures using DumpTree($var) 17 #use Data::TreeDumper; $Data::TreeDumper::Displayaddress = 0; 18 19 # unbuffer output 20 $| = 1; 21 22 ############################################################################### 23 # BEGIN user-customizable section 24 25 # Path to podman executable 26 my $Default_Podman = './bin/podman'; 27 my $PODMAN = $ENV{PODMAN} || $Default_Podman; 28 29 # Path to all doc files, including .rst and (down one level) markdown 30 my $Docs_Path = 'docs/source'; 31 32 # Path to podman markdown source files (of the form podman-*.1.md) 33 my $Markdown_Path = "$Docs_Path/markdown"; 34 35 # Global error count 36 my $Errs = 0; 37 38 # END user-customizable section 39 ############################################################################### 40 41 use FindBin; 42 43 ############################################################################### 44 # BEGIN boilerplate args checking, usage messages 45 46 sub usage { 47 print <<"END_USAGE"; 48 Usage: $ME [OPTIONS] 49 50 $ME recursively runs 'podman --help' against 51 all subcommands; and recursively reads podman-*.1.md files 52 in $Markdown_Path, then cross-references that each --help 53 option is listed in the appropriate man page and vice-versa. 54 55 $ME invokes '\$PODMAN' (default: $Default_Podman). 56 57 Exit status is zero if no inconsistencies found, one otherwise 58 59 OPTIONS: 60 61 -v, --verbose show verbose progress indicators 62 -n, --dry-run make no actual changes 63 64 --help display this message 65 --version display program name and version 66 END_USAGE 67 68 exit; 69 } 70 71 # Command-line options. Note that this operates directly on @ARGV ! 72 our $debug = 0; 73 our $verbose = 0; 74 sub handle_opts { 75 use Getopt::Long; 76 GetOptions( 77 'debug!' => \$debug, 78 'verbose|v' => \$verbose, 79 80 help => \&usage, 81 version => sub { print "$ME version $VERSION\n"; exit 0 }, 82 ) or die "Try `$ME --help' for help\n"; 83 } 84 85 # END boilerplate args checking, usage messages 86 ############################################################################### 87 88 ############################## CODE BEGINS HERE ############################### 89 90 # The term is "modulino". 91 __PACKAGE__->main() unless caller(); 92 93 # Main code. 94 sub main { 95 # Note that we operate directly on @ARGV, not on function parameters. 96 # This is deliberate: it's because Getopt::Long only operates on @ARGV 97 # and there's no clean way to make it use @_. 98 handle_opts(); # will set package globals 99 100 # Fetch command-line arguments. Barf if too many. 101 die "$ME: Too many arguments; try $ME --help\n" if @ARGV; 102 103 my $help = podman_help(); 104 my $man = podman_man('podman'); 105 my $rst = podman_rst(); 106 107 xref_by_help($help, $man); 108 xref_by_man($help, $man); 109 110 xref_rst($help, $rst); 111 112 exit !!$Errs; 113 } 114 115 ############################################################################### 116 # BEGIN cross-referencing 117 118 ################## 119 # xref_by_help # Find keys in '--help' but not in man 120 ################## 121 sub xref_by_help { 122 my ($help, $man, @subcommand) = @_; 123 124 for my $k (sort keys %$help) { 125 if (exists $man->{$k}) { 126 if (ref $help->{$k}) { 127 xref_by_help($help->{$k}, $man->{$k}, @subcommand, $k); 128 } 129 # Otherwise, non-ref is leaf node such as a --option 130 } 131 else { 132 my $man = $man->{_path} || 'man'; 133 warn "$ME: podman @subcommand --help lists $k, but $k not in $man\n"; 134 ++$Errs; 135 } 136 } 137 } 138 139 ################# 140 # xref_by_man # Find keys in man pages but not in --help 141 ################# 142 # 143 # In an ideal world we could share the functionality in one function; but 144 # there are just too many special cases in man pages. 145 # 146 sub xref_by_man { 147 my ($help, $man, @subcommand) = @_; 148 149 # FIXME: this generates way too much output 150 for my $k (grep { $_ ne '_path' } sort keys %$man) { 151 if (exists $help->{$k}) { 152 if (ref $man->{$k}) { 153 xref_by_man($help->{$k}, $man->{$k}, @subcommand, $k); 154 } 155 } 156 elsif ($k ne '--help' && $k ne '-h') { 157 my $man = $man->{_path} || 'man'; 158 159 # Special case: podman-inspect serves dual purpose (image, ctr) 160 my %ignore = map { $_ => 1 } qw(-l -s -t --latest --size --type); 161 next if $man =~ /-inspect/ && $ignore{$k}; 162 163 # Special case: podman-diff serves dual purpose (image, ctr) 164 my %diffignore = map { $_ => 1 } qw(-l --latest ); 165 next if $man =~ /-diff/ && $diffignore{$k}; 166 167 # Special case: the 'trust' man page is a mess 168 next if $man =~ /-trust/; 169 170 # Special case: '--net' is an undocumented shortcut 171 next if $k eq '--net' && $help->{'--network'}; 172 173 # Special case: these are actually global options 174 next if $k =~ /^--(cni-config-dir|runtime)$/ && $man =~ /-build/; 175 176 # Special case: weirdness with Cobra and global/local options 177 next if $k eq '--namespace' && $man =~ /-ps/; 178 179 next if "@subcommand" eq 'system' && $k eq 'service'; 180 181 # Special case: podman completion is a hidden command 182 next if $k eq 'completion'; 183 184 warn "$ME: podman @subcommand: $k in $man, but not --help\n"; 185 ++$Errs; 186 } 187 } 188 } 189 190 ############## 191 # xref_rst # Cross-check *.rst files against help 192 ############## 193 sub xref_rst { 194 my ($help, $rst, @subcommand) = @_; 195 196 # Cross-check against rst (but only subcommands, not options). 197 # We key on $help because that is Absolute Truth: anything in podman --help 198 # must be referenced in an rst (the converse is not true). 199 for my $k (sort grep { $_ !~ /^-/ } keys %$help) { 200 # Check for subcommands, if any (eg podman system -> connection -> add) 201 if (ref $help->{$k}) { 202 xref_rst($help->{$k}, $rst->{$k}, @subcommand, $k); 203 } 204 205 # Check that command is mentioned in at least one .rst file 206 if (! exists $rst->{$k}{_desc}) { 207 my @podman = ("podman", @subcommand, $k); 208 warn "$ME: no link in *.rst for @podman\n"; 209 ++$Errs; 210 } 211 } 212 } 213 214 # END cross-referencing 215 ############################################################################### 216 # BEGIN data gathering 217 218 ################# 219 # podman_help # Parse output of 'podman [subcommand] --help' 220 ################# 221 sub podman_help { 222 my %help; 223 open my $fh, '-|', $PODMAN, @_, '--help' 224 or die "$ME: Cannot fork: $!\n"; 225 my $section = ''; 226 while (my $line = <$fh>) { 227 # Cobra is blessedly consistent in its output: 228 # Usage: ... 229 # Available Commands: 230 # .... 231 # Options: 232 # .... 233 # 234 # Start by identifying the section we're in... 235 if ($line =~ /^Available\s+(Commands):/) { 236 $section = lc $1; 237 } 238 elsif ($line =~ /^(Options):/) { 239 $section = lc $1; 240 } 241 242 # ...then track commands and options. For subcommands, recurse. 243 elsif ($section eq 'commands') { 244 if ($line =~ /^\s{1,4}(\S+)\s/) { 245 my $subcommand = $1; 246 print "> podman @_ $subcommand\n" if $debug; 247 248 # check that the same subcommand is not listed twice (#12356) 249 if (exists $help{$subcommand}) { 250 warn "$ME: 'podman @_ help' lists '$subcommand' twice\n"; 251 ++$Errs; 252 } 253 254 $help{$subcommand} = podman_help(@_, $subcommand) 255 unless $subcommand eq 'help'; # 'help' not in man 256 } 257 } 258 elsif ($section eq 'options') { 259 # Handle '--foo' or '-f, --foo' 260 if ($line =~ /^\s{1,10}(--\S+)\s/) { 261 print "> podman @_ $1\n" if $debug; 262 $help{$1} = 1; 263 } 264 elsif ($line =~ /^\s{1,10}(-\S),\s+(--\S+)\s/) { 265 print "> podman @_ $1, $2\n" if $debug; 266 $help{$1} = $help{$2} = 1; 267 } 268 } 269 } 270 close $fh 271 or die "$ME: Error running 'podman @_ --help'\n"; 272 273 return \%help; 274 } 275 276 277 ################ 278 # podman_man # Parse contents of podman-*.1.md 279 ################ 280 sub podman_man { 281 my $command = shift; 282 my $subpath = "$Markdown_Path/$command.1.md"; 283 my $manpath = "$FindBin::Bin/../$subpath"; 284 print "** $subpath \n" if $debug; 285 286 my %man = (_path => $subpath); 287 open my $fh, '<', $manpath 288 or die "$ME: Cannot read $manpath: $!\n"; 289 my $section = ''; 290 my @most_recent_flags; 291 my $previous_subcmd = ''; 292 while (my $line = <$fh>) { 293 chomp $line; 294 next unless $line; # skip empty lines 295 296 # .md files designate sections with leading double hash 297 if ($line =~ /^##\s*(GLOBAL\s+)?OPTIONS/) { 298 $section = 'flags'; 299 } 300 elsif ($line =~ /^\#\#\s+(SUB)?COMMANDS/) { 301 $section = 'commands'; 302 } 303 elsif ($line =~ /^\#\#[^#]/) { 304 $section = ''; 305 } 306 307 # This will be a table containing subcommand names, links to man pages. 308 # The format is slightly different between podman.1.md and subcommands. 309 elsif ($section eq 'commands') { 310 # In podman.1.md 311 if ($line =~ /^\|\s*\[podman-(\S+?)\(\d\)\]/) { 312 # $1 will be changed by recursion _*BEFORE*_ left-hand assignment 313 my $subcmd = $1; 314 $man{$subcmd} = podman_man("podman-$1"); 315 } 316 317 # In podman-<subcommand>.1.md 318 elsif ($line =~ /^\|\s+(\S+)\s+\|\s+\[\S+\]\((\S+)\.1\.md\)/) { 319 # $1 will be changed by recursion _*BEFORE*_ left-hand assignment 320 my $subcmd = $1; 321 if ($previous_subcmd gt $subcmd) { 322 warn "$ME: $subpath: '$previous_subcmd' and '$subcmd' are out of order\n"; 323 ++$Errs; 324 } 325 $previous_subcmd = $subcmd; 326 $man{$subcmd} = podman_man($2); 327 } 328 } 329 330 # Options should always be of the form '**-f**' or '**\-\-flag**', 331 # possibly separated by comma-space. 332 elsif ($section eq 'flags') { 333 # e.g. 'podman run --ip6', documented in man page, but nonexistent 334 if ($line =~ /^not\s+implemented/i) { 335 delete $man{$_} for @most_recent_flags; 336 } 337 338 @most_recent_flags = (); 339 # As of PR #8292, all options are <h4> and anchored 340 if ($line =~ s/^\#{4}\s+//) { 341 # If option has long and short form, long must come first. 342 # This is a while-loop because there may be multiple long 343 # option names, e.g. --net/--network 344 while ($line =~ s/^\*\*(--[a-z0-9-]+)\*\*(=\*[a-zA-Z0-9-]+\*)?(,\s+)?//g) { 345 $man{$1} = 1; 346 push @most_recent_flags, $1; 347 } 348 # Short form 349 if ($line =~ s/^\*\*(-[a-zA-Z0-9])\*\*(=\*[a-zA-Z0-9-]+\*)?//g) { 350 $man{$1} = 1; 351 352 # Keep track of them, in case we see 'Not implemented' below 353 push @most_recent_flags, $1; 354 } 355 } 356 } 357 } 358 close $fh; 359 360 # Special case: the 'image trust' man page tries hard to cover both set 361 # and show, which means it ends up not being machine-readable. 362 if ($command eq 'podman-image-trust') { 363 my %set = %man; 364 my %show = %man; 365 $show{$_} = 1 for qw(--raw -j --json); 366 return +{ set => \%set, show => \%show } 367 } 368 369 return \%man; 370 } 371 372 373 ################ 374 # podman_rst # Parse contents of docs/source/*.rst 375 ################ 376 sub podman_rst { 377 my %rst; 378 379 # Read all .rst files, looking for ":doc:`subcmd <target>` description" 380 for my $rst (glob "$Docs_Path/*.rst") { 381 open my $fh, '<', $rst 382 or die "$ME: Cannot read $rst: $!\n"; 383 384 # The basename of foo.rst is usually, but not always, the name of 385 # a podman subcommand. There are a few special cases: 386 (my $command = $rst) =~ s!^.*/(.*)\.rst!$1!; 387 388 my $subcommand_href = \%rst; 389 if ($command eq 'Commands') { 390 ; 391 } 392 elsif ($command eq 'managecontainers') { 393 $subcommand_href = $rst{container} //= { }; 394 } 395 elsif ($command eq 'connection') { 396 $subcommand_href = $rst{system}{connection} //= { }; 397 } 398 else { 399 $subcommand_href = $rst{$command} //= { }; 400 } 401 402 my $previous_subcommand = ''; 403 while (my $line = <$fh>) { 404 if ($line =~ /^:doc:`(\S+)\s+<(.*?)>`\s+(.*)/) { 405 my ($subcommand, $target, $desc) = ($1, $2, $3); 406 407 # Check that entries are in alphabetical order 408 if ($subcommand lt $previous_subcommand) { 409 warn "$ME: $rst:$.: '$previous_subcommand' and '$subcommand' are out of order\n"; 410 ++$Errs; 411 } 412 $previous_subcommand = $subcommand; 413 414 # Mark this subcommand as documented. 415 $subcommand_href->{$subcommand}{_desc} = $desc; 416 417 # Check for invalid links. These will be one of two forms: 418 # <markdown/foo.1> -> markdown/foo.1.md 419 # <foo> -> foo.rst 420 if ($target =~ m!^markdown/!) { 421 if (! -e "$Docs_Path/$target.md") { 422 warn "$ME: $rst:$.: '$subcommand' links to nonexistent $target\n"; 423 ++$Errs; 424 } 425 } 426 else { 427 if (! -e "$Docs_Path/$target.rst") { 428 warn "$ME: $rst:$.: '$subcommand' links to nonexistent $target.rst\n"; 429 } 430 } 431 } 432 } 433 close $fh; 434 } 435 436 # Special case: 'image trust set/show' are documented in image-trust.1 437 $rst{image}{trust}{$_} = { _desc => 'ok' } for (qw(set show)); 438 439 return \%rst; 440 } 441 442 # END data gathering 443 ############################################################################### 444 445 1;