github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/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 ############################################################################### 20 # BEGIN user-customizable section 21 22 # Path to podman executable 23 my $Default_Podman = './bin/podman'; 24 my $PODMAN = $ENV{PODMAN} || $Default_Podman; 25 26 # Path to podman markdown source files (of the form podman-*.1.md) 27 my $Markdown_Path = 'docs/source/markdown'; 28 29 # END user-customizable section 30 ############################################################################### 31 32 use FindBin; 33 34 ############################################################################### 35 # BEGIN boilerplate args checking, usage messages 36 37 sub usage { 38 print <<"END_USAGE"; 39 Usage: $ME [OPTIONS] 40 41 $ME recursively runs 'podman --help' against 42 all subcommands; and recursively reads podman-*.1.md files 43 in $Markdown_Path, then cross-references that each --help 44 option is listed in the appropriate man page and vice-versa. 45 46 $ME invokes '\$PODMAN' (default: $Default_Podman). 47 48 Exit status is zero if no inconsistencies found, one otherwise 49 50 OPTIONS: 51 52 -v, --verbose show verbose progress indicators 53 -n, --dry-run make no actual changes 54 55 --help display this message 56 --version display program name and version 57 END_USAGE 58 59 exit; 60 } 61 62 # Command-line options. Note that this operates directly on @ARGV ! 63 our $debug = 0; 64 our $verbose = 0; 65 sub handle_opts { 66 use Getopt::Long; 67 GetOptions( 68 'debug!' => \$debug, 69 'verbose|v' => \$verbose, 70 71 help => \&usage, 72 version => sub { print "$ME version $VERSION\n"; exit 0 }, 73 ) or die "Try `$ME --help' for help\n"; 74 } 75 76 # END boilerplate args checking, usage messages 77 ############################################################################### 78 79 ############################## CODE BEGINS HERE ############################### 80 81 # The term is "modulino". 82 __PACKAGE__->main() unless caller(); 83 84 # Main code. 85 sub main { 86 # Note that we operate directly on @ARGV, not on function parameters. 87 # This is deliberate: it's because Getopt::Long only operates on @ARGV 88 # and there's no clean way to make it use @_. 89 handle_opts(); # will set package globals 90 91 # Fetch command-line arguments. Barf if too many. 92 die "$ME: Too many arguments; try $ME --help\n" if @ARGV; 93 94 my $help = podman_help(); 95 my $man = podman_man('podman'); 96 97 my $retval = xref_by_help($help, $man) 98 + xref_by_man($help, $man); 99 100 exit !!$retval; 101 } 102 103 ################## 104 # xref_by_help # Find keys in '--help' but not in man 105 ################## 106 sub xref_by_help { 107 my ($help, $man, @subcommand) = @_; 108 my $errs = 0; 109 110 for my $k (sort keys %$help) { 111 if (exists $man->{$k}) { 112 if (ref $help->{$k}) { 113 $errs += xref_by_help($help->{$k}, $man->{$k}, @subcommand, $k); 114 } 115 # Otherwise, non-ref is leaf node such as a --option 116 } 117 else { 118 my $man = $man->{_path} || 'man'; 119 warn "$ME: podman @subcommand --help lists $k, but $k not in $man\n"; 120 ++$errs; 121 } 122 } 123 124 return $errs; 125 } 126 127 ################# 128 # xref_by_man # Find keys in man pages but not in --help 129 ################# 130 # 131 # In an ideal world we could share the functionality in one function; but 132 # there are just too many special cases in man pages. 133 # 134 sub xref_by_man { 135 my ($help, $man, @subcommand) = @_; 136 137 my $errs = 0; 138 139 # FIXME: this generates way too much output 140 for my $k (grep { $_ ne '_path' } sort keys %$man) { 141 if (exists $help->{$k}) { 142 if (ref $man->{$k}) { 143 $errs += xref_by_man($help->{$k}, $man->{$k}, @subcommand, $k); 144 } 145 } 146 elsif ($k ne '--help' && $k ne '-h') { 147 my $man = $man->{_path} || 'man'; 148 149 # Special case: podman-inspect serves dual purpose (image, ctr) 150 my %ignore = map { $_ => 1 } qw(-l -s -t --latest --size --type); 151 next if $man =~ /-inspect/ && $ignore{$k}; 152 153 # Special case: the 'trust' man page is a mess 154 next if $man =~ /-trust/; 155 156 # Special case: '--net' is an undocumented shortcut 157 next if $k eq '--net' && $help->{'--network'}; 158 159 # Special case: these are actually global options 160 next if $k =~ /^--(cni-config-dir|runtime)$/ && $man =~ /-build/; 161 162 # Special case: weirdness with Cobra and global/local options 163 next if $k eq '--namespace' && $man =~ /-ps/; 164 165 # Special case: these require compiling with 'varlink' tag, 166 # which doesn't happen in CI gating task. 167 next if $k eq 'varlink'; 168 next if "@subcommand" eq 'system' && $k eq 'service'; 169 170 warn "$ME: podman @subcommand: $k in $man, but not --help\n"; 171 ++$errs; 172 } 173 } 174 175 return $errs; 176 } 177 178 179 ################# 180 # podman_help # Parse output of 'podman [subcommand] --help' 181 ################# 182 sub podman_help { 183 my %help; 184 open my $fh, '-|', $PODMAN, @_, '--help' 185 or die "$ME: Cannot fork: $!\n"; 186 my $section = ''; 187 while (my $line = <$fh>) { 188 # Cobra is blessedly consistent in its output: 189 # Usage: ... 190 # Available Commands: 191 # .... 192 # Flags: 193 # .... 194 # 195 # Start by identifying the section we're in... 196 if ($line =~ /^Available\s+(Commands):/) { 197 $section = lc $1; 198 } 199 elsif ($line =~ /^(Flags):/) { 200 $section = lc $1; 201 } 202 203 # ...then track commands and options. For subcommands, recurse. 204 elsif ($section eq 'commands') { 205 if ($line =~ /^\s{1,4}(\S+)\s/) { 206 my $subcommand = $1; 207 print "> podman @_ $subcommand\n" if $debug; 208 $help{$subcommand} = podman_help(@_, $subcommand) 209 unless $subcommand eq 'help'; # 'help' not in man 210 } 211 } 212 elsif ($section eq 'flags') { 213 # Handle '--foo' or '-f, --foo' 214 if ($line =~ /^\s{1,10}(--\S+)\s/) { 215 print "> podman @_ $1\n" if $debug; 216 $help{$1} = 1; 217 } 218 elsif ($line =~ /^\s{1,10}(-\S),\s+(--\S+)\s/) { 219 print "> podman @_ $1, $2\n" if $debug; 220 $help{$1} = $help{$2} = 1; 221 } 222 } 223 } 224 close $fh 225 or die "$ME: Error running 'podman @_ --help'\n"; 226 227 return \%help; 228 } 229 230 231 ################ 232 # podman_man # Parse contents of podman-*.1.md 233 ################ 234 sub podman_man { 235 my $command = shift; 236 my $subpath = "$Markdown_Path/$command.1.md"; 237 my $manpath = "$FindBin::Bin/../$subpath"; 238 print "** $subpath \n" if $debug; 239 240 my %man = (_path => $subpath); 241 open my $fh, '<', $manpath 242 or die "$ME: Cannot read $manpath: $!\n"; 243 my $section = ''; 244 my @most_recent_flags; 245 while (my $line = <$fh>) { 246 chomp $line; 247 next unless $line; # skip empty lines 248 249 # .md files designate sections with leading double hash 250 if ($line =~ /^##\s*(GLOBAL\s+)?OPTIONS/) { 251 $section = 'flags'; 252 } 253 elsif ($line =~ /^\#\#\s+(SUB)?COMMANDS/) { 254 $section = 'commands'; 255 } 256 elsif ($line =~ /^\#\#/) { 257 $section = ''; 258 } 259 260 # This will be a table containing subcommand names, links to man pages. 261 # The format is slightly different between podman.1.md and subcommands. 262 elsif ($section eq 'commands') { 263 # In podman.1.md 264 if ($line =~ /^\|\s*\[podman-(\S+?)\(\d\)\]/) { 265 $man{$1} = podman_man("podman-$1"); 266 } 267 268 # In podman-<subcommand>.1.md 269 elsif ($line =~ /^\|\s+(\S+)\s+\|\s+\[\S+\]\((\S+)\.1\.md\)/) { 270 $man{$1} = podman_man($2); 271 } 272 } 273 274 # Flags should always be of the form '**-f**' or '**--flag**', 275 # possibly separated by comma-space. 276 elsif ($section eq 'flags') { 277 # e.g. 'podman run --ip6', documented in man page, but nonexistent 278 if ($line =~ /^not\s+implemented/i) { 279 delete $man{$_} for @most_recent_flags; 280 } 281 282 @most_recent_flags = (); 283 # Handle any variation of '**--foo**, **-f**' 284 while ($line =~ s/^\*\*((--[a-z0-9-]+)|(-.))\*\*(,\s+)?//g) { 285 $man{$1} = 1; 286 287 # Keep track of them, in case we see 'Not implemented' below 288 push @most_recent_flags, $1; 289 } 290 } 291 } 292 close $fh; 293 294 # Special case: the 'image trust' man page tries hard to cover both set 295 # and show, which means it ends up not being machine-readable. 296 if ($command eq 'podman-image-trust') { 297 my %set = %man; 298 my %show = %man; 299 $show{$_} = 1 for qw(--raw -j --json); 300 return +{ set => \%set, show => \%show } 301 } 302 303 return \%man; 304 } 305 306 307 1;