github.com/containers/podman/v5@v5.1.0-rc1/hack/xref-quadlet-docs (about)

     1  #!/usr/bin/perl
     2  #
     3  # xref-quadlet-docs - cross-validate quadlet man page vs actual source
     4  #
     5  # $Id: .perl-template,v 1.2 2020/03/03 20:08:31 esm Exp esm $
     6  #
     7  package Podman::CrossrefQuadletDocs;
     8  
     9  use v5.14;
    10  use utf8;
    11  
    12  use strict;
    13  use warnings;
    14  
    15  (our $ME = $0) =~ s|.*/||;
    16  our $VERSION = '0.1';
    17  
    18  ###############################################################################
    19  # BEGIN user-customizable section
    20  
    21  our $Go  = 'pkg/systemd/quadlet/quadlet.go';
    22  our $Doc = 'docs/source/markdown/podman-systemd.unit.5.md';
    23  
    24  # END   user-customizable section
    25  ###############################################################################
    26  
    27  ###############################################################################
    28  # BEGIN boilerplate args checking, usage messages
    29  
    30  sub usage {
    31      print  <<"END_USAGE";
    32  Usage: $ME [OPTIONS]
    33  
    34  $ME cross-checks quadlet documentation between the Go source[Go]
    35  and the man page[MD].
    36  
    37   [Go]: $Go
    38   [MD]: $Doc
    39  
    40  We check that:
    41  
    42    * all keys in [Go] are documented in [MD]
    43    * all keys in [MD] exist in [Go]
    44      * any keys listed in [MD] tables also have a description block
    45        and vice-versa
    46    * all keys everywhere are in sorted order
    47  
    48  OPTIONS:
    49  
    50    --help         display this message
    51    --version      display program name and version
    52  END_USAGE
    53  
    54      exit;
    55  }
    56  
    57  # Command-line options.  Note that this operates directly on @ARGV !
    58  our $debug   = 0;
    59  sub handle_opts {
    60      use Getopt::Long;
    61      GetOptions(
    62          'debug!'     => \$debug,
    63  
    64          help         => \&usage,
    65          man          => \&man,
    66          version      => sub { print "$ME version $VERSION\n"; exit 0 },
    67      ) or die "Try `$ME --help' for help\n";
    68  }
    69  
    70  # END   boilerplate args checking, usage messages
    71  ###############################################################################
    72  
    73  ############################## CODE BEGINS HERE ###############################
    74  
    75  # The term is "modulino".
    76  __PACKAGE__->main()                                     unless caller();
    77  
    78  # Main code.
    79  sub main {
    80      # Note that we operate directly on @ARGV, not on function parameters.
    81      # This is deliberate: it's because Getopt::Long only operates on @ARGV
    82      # and there's no clean way to make it use @_.
    83      handle_opts();                      # will set package globals
    84  
    85      # No command-line args
    86      die "$ME: Too many arguments; try $ME --help\n"                 if @ARGV;
    87  
    88      my $errs = 0;
    89      $SIG{__WARN__} = sub {
    90          print STDERR "@_";
    91          ++$errs;
    92      };
    93  
    94      # Assume that Go source file has Truth
    95      my $true_keys = read_go($Go);
    96  
    97      # Read md file, compare against Truth
    98      crossref_doc($Doc, $true_keys);
    99  
   100      exit $errs;
   101  }
   102  
   103  
   104  #############
   105  #  read_go  #  Returns list of key strings found in quadlet.go
   106  #############
   107  sub read_go {
   108      my $path = shift;
   109      open my $fh, '<', $path
   110          or die "$ME: Cannot read $path: $!\n";;
   111  
   112      my @found;                          # List of key strings
   113      my $last_constname;                 # Most recently seen const name
   114  
   115      while (my $line = <$fh>) {
   116          # Only interested in lines of the form   KeyFoo = "Foo"
   117          if ($line =~ /^\s+Key(\S+)\s+=\s+"(\S+)"/) {
   118              my ($constname, $keystring) = ($1, $2);
   119  
   120              my $deprecated = ($line =~ m!\s//\s+deprecated!i);
   121  
   122              # const name must be the same as the string
   123              $constname eq $keystring
   124                  or warn "$ME: $path:$.: mismatched strings: Key$constname = \"$keystring\"\n";
   125  
   126              # Sorting check.
   127              if ($last_constname) {
   128                  if (lc($constname) lt lc($last_constname)) {
   129                      warn "$ME: $path:$.: out-of-order variable name 'Key$constname' should precede 'Key$last_constname'\n";
   130                  }
   131              }
   132              $last_constname = $constname;
   133  
   134              push @found, $keystring
   135                  unless $deprecated;
   136          }
   137      }
   138      close $fh;
   139  
   140      \@found;
   141  }
   142  
   143  ##################
   144  #  crossref_doc  #  Read the markdown page, cross-check against Truth
   145  ##################
   146  sub crossref_doc {
   147      my $path      = shift;              # in: path to .md file
   148      my $true_keys = shift;              # in: AREF, list of keys from .go
   149  
   150      open my $fh, '<', $path
   151          or die "$ME: Cannot read $path: $!\n";;
   152  
   153      my $unit = '';
   154      my %documented;
   155      my @found_in_table;
   156      my @described;
   157  
   158      # Helper function: when done reading description blocks,
   159      # make sure that there's one block for each key listed
   160      # in the table. Defined as a local function because we
   161      # need to call it from two different places.
   162      my $crossref_against_table = sub {
   163          for my $k (@found_in_table) {
   164              grep { $_ eq $k } @described
   165                  or warn "$ME: key not documented: '$k' listed in table for unit '$unit' but not actually documented\n";
   166          }
   167      };
   168  
   169      # Main loop: read the docs line by line
   170      while (my $line = <$fh>) {
   171          chomp $line;
   172  
   173          # New section, with its own '| table |' and '### Keyword blocks'
   174          if ($line =~ /^##\s+(\S+)\s+units\s+\[(\S+)\]/) {
   175              my $new_unit = $1;
   176              $new_unit eq $2
   177                  or warn "$ME: $path:$.: inconsistent block names in '$line'\n";
   178  
   179              $crossref_against_table->();
   180  
   181              $unit = $new_unit;
   182  
   183              # Reset, because each section has its own table & blocks
   184              @found_in_table = ();
   185              @described = ();
   186              next;
   187          }
   188  
   189          # Table line
   190          if ($line =~ s/^\|\s+//) {
   191              next if $line =~ /^\*\*/;           # title
   192              next if $line =~ /^-----/;          # divider
   193  
   194              if ($line =~ /^([A-Z][A-Za-z6]+)=/) {
   195                  my $key = $1;
   196  
   197                  grep { $_ eq $key } @$true_keys
   198                      or warn "$ME: $path:$.: unknown key '$key' (not present in $Go)\n";
   199  
   200                  # Sorting check
   201                  if (@found_in_table) {
   202                      if (lc($key) lt lc($found_in_table[-1])) {
   203                          warn "$ME: $path:$.: out-of-order key '$key' in table\n";
   204                      }
   205                  }
   206  
   207                  push @found_in_table, $key;
   208                  $documented{$key}++;
   209              }
   210              else {
   211                  warn "$ME: $path:$.: cannot grok table line '$line'\n";
   212              }
   213          }
   214  
   215          # Description block
   216          elsif ($line =~ /^###\s+`(\S+)=`/) {
   217              my $key = $1;
   218  
   219              # Check for dups and for out-of-order
   220              if (@described) {
   221                  if (lc($key) lt lc($described[-1])) {
   222                      warn "$ME: $path:$.: out-of-order key '$key'\n";
   223                  }
   224                  if (grep { lc($_) eq lc($key) } @described) {
   225                      warn "$ME: $path:$.: duplicate key '$key'\n";
   226                  }
   227              }
   228  
   229              grep { $_ eq $key } @found_in_table
   230                  or warn "$ME: $path:$.: key '$key' is not listed in table for unit '$unit'\n";
   231  
   232              push @described, $key;
   233              $documented{$key}++;
   234          }
   235      }
   236  
   237      close $fh;
   238  
   239      # Final cross-check between table and description blocks
   240      $crossref_against_table->();
   241  
   242      # Check that no Go keys are missing
   243  
   244      (my $md_basename = $path) =~ s|^.*/||;
   245      for my $k (@$true_keys) {
   246          $documented{$k}
   247              or warn "$ME: undocumented key: '$k' not found anywhere in $md_basename\n";
   248      }
   249  }
   250  
   251  1;