| #!/usr/bin/perl | |
| use strict; | |
| use warnings; | |
| my %SECTIONS; | |
| { | |
| my $order = 0; | |
| %SECTIONS = ( | |
| 'NAME' => { | |
| required => 1, | |
| order => $order++, | |
| }, | |
| 'SYNOPSIS' => { | |
| required => 1, | |
| optional_in_man_sections => { | |
| '7' => 1, | |
| }, | |
| order => $order++, | |
| }, | |
| 'DESCRIPTION' => { | |
| required => 1, | |
| order => $order++, | |
| }, | |
| 'OPTIONS' => { | |
| order => $order++, | |
| required => 0, | |
| }, | |
| 'CONFIGURATION' => { | |
| order => $order++, | |
| }, | |
| 'BUGS' => { | |
| order => $order++, | |
| }, | |
| 'SEE ALSO' => { | |
| order => $order++, | |
| }, | |
| 'FILE FORMAT' => { | |
| order => $order++, | |
| }, | |
| 'GIT' => { | |
| required => 1, | |
| order => $order++, | |
| }, | |
| ); | |
| } | |
| my $SECTION_RX = do { | |
| my ($names) = join "|", keys %SECTIONS; | |
| qr/^($names)$/s; | |
| }; | |
| my $exit_code = 0; | |
| sub report { | |
| my ($msg) = @_; | |
| print STDERR "$ARGV:$.: $msg\n"; | |
| $exit_code = 1; | |
| } | |
| my $man_section_number; | |
| my $last_was_section; | |
| my @actual_order; | |
| while (my $line = <>) { | |
| chomp $line; | |
| if ($. == 1) { | |
| $line =~ m/\((\d)\)/; | |
| $man_section_number = $1; | |
| if (!$man_section_number) { | |
| report("first line must be formatted like 'gitfaq(7)'"); | |
| # exit to avoid dealing with $man_section_number being undefined later | |
| last; | |
| } | |
| } | |
| if ($line =~ $SECTION_RX) { | |
| push @actual_order => $line; | |
| $last_was_section = 1; | |
| # Have no "last" section yet, processing NAME | |
| next if @actual_order == 1; | |
| my @expected_order = sort { | |
| $SECTIONS{$a}->{order} <=> $SECTIONS{$b}->{order} | |
| } @actual_order; | |
| my $expected_last = $expected_order[-2]; | |
| my $actual_last = $actual_order[-2]; | |
| if ($actual_last ne $expected_last) { | |
| report("section '$line' incorrectly ordered, comes after '$actual_last'"); | |
| } | |
| next; | |
| } | |
| if ($last_was_section) { | |
| my $last_section = $actual_order[-1]; | |
| if (length $last_section ne length $line) { | |
| report("dashes under '$last_section' should match its length!"); | |
| } | |
| if ($line !~ /^-+$/) { | |
| report("dashes under '$last_section' should be '-' dashes!"); | |
| } | |
| $last_was_section = 0; | |
| } | |
| if (eof) { | |
| # We have both a hash and an array to consider, for | |
| # convenience | |
| my %actual_sections; | |
| @actual_sections{@actual_order} = (); | |
| for my $section (sort keys %SECTIONS) { | |
| next if !$SECTIONS{$section}->{required} or | |
| $SECTIONS{$section}->{optional_in_man_sections}->{$man_section_number} or | |
| exists $actual_sections{$section}; | |
| report("has no required '$section' section!"); | |
| } | |
| # Reset per-file state | |
| { | |
| @actual_order = (); | |
| # this resets our $. for each file | |
| close ARGV; | |
| } | |
| } | |
| } | |
| exit $exit_code; |