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;