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;