|
| 1 | +#!perl |
| 2 | +use v5.36; |
| 3 | +use Pod::Simple::SimpleTree; |
| 4 | + |
| 5 | +# POD NAVIGATION SUBROUTINES |
| 6 | + |
| 7 | +sub header_pos ( $tree, $level, $title, $pos = 0 ) { |
| 8 | + while ( $pos < @$tree ) { |
| 9 | + next |
| 10 | + unless ref( $tree->[$pos] ) eq 'ARRAY' |
| 11 | + && $tree->[$pos][0] eq "head$level"; |
| 12 | + return $pos if $tree->[$pos][2] eq $title; |
| 13 | + } |
| 14 | + continue { $pos++ } |
| 15 | + return; # not found |
| 16 | +} |
| 17 | + |
| 18 | +sub next_header_pos ( $tree, $level, $pos = 0 ) { |
| 19 | + $pos++; |
| 20 | + while ( $pos < @$tree ) { |
| 21 | + next |
| 22 | + unless ref( $tree->[$pos] ) eq 'ARRAY'; |
| 23 | + next unless $tree->[$pos][0] =~ /\Ahead([1-4])\z/; |
| 24 | + next if $1 > $level; |
| 25 | + last if $1 < $level; |
| 26 | + return $pos; |
| 27 | + } |
| 28 | + continue { $pos++ } |
| 29 | + return; # not found |
| 30 | +} |
| 31 | + |
| 32 | +sub find_pos_in ( $master, $delta, $title ) { |
| 33 | + return |
| 34 | + map header_pos( $_, 1, $title ), |
| 35 | + $master, $delta; |
| 36 | +} |
| 37 | + |
| 38 | +# copy the whole section content |
| 39 | +sub copy_section ( $master, $title, $delta ) { |
| 40 | + my ( $master_pos, $delta_pos ) = find_pos_in( $master, $delta, $title ); |
| 41 | + |
| 42 | + # find the end of the section in the delta |
| 43 | + my $end_pos = next_header_pos( $delta, 1, $delta_pos ) - 1; |
| 44 | + |
| 45 | + # inject the whole section from the delta |
| 46 | + splice @$master, $master_pos + 1, |
| 47 | + 0, $delta->@[ $delta_pos + 1 .. $end_pos ]; |
| 48 | +} |
| 49 | + |
| 50 | +# map each section to an action |
| 51 | +my %ACTION_FOR = ( |
| 52 | + 'NAME' => 'skip', |
| 53 | + 'DESCRIPTION' => 'skip', |
| 54 | + 'Notice' => 'copy', |
| 55 | + 'Core Enhancements' => 'copy', |
| 56 | + 'Security' => 'copy', |
| 57 | + 'Incompatible Changes' => 'copy', |
| 58 | + 'Deprecations' => 'copy', |
| 59 | + 'Performance Enhancements' => 'copy', |
| 60 | + 'Modules and Pragmata' => 'skip', |
| 61 | + 'Documentation' => 'copy', |
| 62 | + 'Diagnostics' => 'copy', |
| 63 | + 'Utility Changes' => 'copy', |
| 64 | + 'Configuration and Compilation' => 'copy', |
| 65 | + 'Testing' => 'copy', |
| 66 | + 'Platform Support' => 'copy', |
| 67 | + 'Internal Changes' => 'copy', |
| 68 | + 'Selected Bug Fixes' => 'copy', |
| 69 | + 'Known Problems' => 'copy', |
| 70 | + 'Errata From Previous Releases' => 'copy', |
| 71 | + 'Obituary' => 'copy', |
| 72 | + 'Acknowledgements' => 'skip', |
| 73 | + 'Reporting Bugs' => 'skip', |
| 74 | + 'Give Thanks' => 'skip', |
| 75 | + 'SEE ALSO' => 'skip', |
| 76 | +); |
| 77 | + |
| 78 | +# NOTE: A Pod::Simple::SimpleTree "tree" is really just a list of |
| 79 | +# directives. The only parts that are really tree-like / recursive are |
| 80 | +# the list directives, and pod formatting codes. |
| 81 | + |
| 82 | +sub as_pod ( $tree ) { |
| 83 | + return $tree unless ref $tree; # simple string |
| 84 | + state $handler = { |
| 85 | + Document => sub ( $name, $attr, @nodes ) { |
| 86 | + return map( as_pod($_), @nodes), "=cut\n"; |
| 87 | + }, |
| 88 | + Para => sub ( $name, $attr, @nodes ) { |
| 89 | + return map( as_pod($_), @nodes ), "\n\n"; |
| 90 | + }, |
| 91 | + Verbatim => sub ( $name, $attr, @nodes ) { |
| 92 | + return map( as_pod($_), @nodes ), "\n\n"; |
| 93 | + }, |
| 94 | + X => sub ( $name, $attr, @nodes ) { |
| 95 | + my ( $open, $spacer, $close ) = |
| 96 | + $attr->{'~bracket_count'} |
| 97 | + ? ( |
| 98 | + '<' x $attr->{'~bracket_count'}, |
| 99 | + ' ', |
| 100 | + '>' x $attr->{'~bracket_count'} |
| 101 | + ) |
| 102 | + : ( '<', '', '>' ); |
| 103 | + return "$name$open$spacer", |
| 104 | + map( as_pod($_), @nodes ), |
| 105 | + "$spacer$close"; |
| 106 | + }, |
| 107 | + L => sub ( $name, $attr, @nodes ) { |
| 108 | + return "$name<$attr->{raw}>"; |
| 109 | + }, |
| 110 | + # TODO: =begin / =for |
| 111 | + over => sub ( $name, $attr, @nodes ) { |
| 112 | + return "=over", |
| 113 | + $attr->{'~orig_content'} && " $attr->{'~orig_content'}", "\n\n", |
| 114 | + map( as_pod($_), @nodes ), "=back\n\n"; |
| 115 | + }, |
| 116 | + item => sub ( $name, $attr, @nodes ) { |
| 117 | + return "=item ", |
| 118 | + $attr->{'~orig_content'} ? "$attr->{'~orig_content'}\n\n" : '', |
| 119 | + map( as_pod($_), @nodes ), "\n\n"; |
| 120 | + }, |
| 121 | + '' => sub ( $name, $attr, @nodes ) { |
| 122 | + return "=$name", @nodes && ' ', map( as_pod($_), @nodes ), "\n\n"; |
| 123 | + }, |
| 124 | + }; |
| 125 | + my ( $directive, $attr, @nodes ) = @$tree; |
| 126 | + my $name = |
| 127 | + exists $handler->{$directive} ? $directive |
| 128 | + : $directive =~ /\Aover-/ ? 'over' |
| 129 | + : $directive =~ /\Aitem-/ ? 'item' |
| 130 | + : length($directive) == 1 ? 'X' |
| 131 | + : ''; |
| 132 | + return join '', $handler->{$name}->( $directive, $attr, @nodes ); |
| 133 | +} |
| 134 | + |
| 135 | +# Note: the parser can only be used *once* per file |
| 136 | +sub tree_for ($string) { |
| 137 | + my $parser = Pod::Simple::SimpleTree->new; |
| 138 | + $parser->keep_encoding_directive(1); |
| 139 | + $parser->preserve_whitespace(1); |
| 140 | + $parser->accept_targets('*'); # for & begin/end |
| 141 | + $parser->_output_is_for_JustPod(1); # for ~bracket_count |
| 142 | + $parser->parse_string_document($string)->root; |
| 143 | +} |
| 144 | + |
| 145 | +sub merge_into ( $master, $delta, $file ) { |
| 146 | + |
| 147 | + # loop over the =head1 sections |
| 148 | + for my $title ( |
| 149 | + map $_->[2], # grab the title |
| 150 | + grep ref eq 'ARRAY' && $_->[0] eq 'head1', # of the =head1 |
| 151 | + @$delta # of the delta |
| 152 | + ) |
| 153 | + { |
| 154 | + die "Unexpected section '=head1 $title' in $file\n" |
| 155 | + unless exists $ACTION_FOR{$title}; |
| 156 | + next if $ACTION_FOR{$title} eq 'skip'; |
| 157 | + copy_section( $master, $title, $delta ); |
| 158 | + } |
| 159 | +} |
| 160 | + |
| 161 | +sub slurp { return do { local @ARGV = @_; local $/; <> } } |
| 162 | + |
| 163 | +# MAIN PROGRAM |
| 164 | + |
| 165 | +sub main (@argv) { |
| 166 | + |
| 167 | + # compute the version |
| 168 | + my ($version) = `git describe` =~ /\Av(5\.[0-9]+)/g; |
| 169 | + die "$version does not look like a devel Perl version\n" |
| 170 | + unless $version =~ /\A5\.[0-9]{1,2}[13579]\z/; |
| 171 | + |
| 172 | + # the current, unfinished, delta will be used |
| 173 | + # as the master to produce the final document |
| 174 | + my $final_delta = 'pod/perldelta.pod'; |
| 175 | + my $master = tree_for( slurp($final_delta) ); |
| 176 | + |
| 177 | + # loop over all the development deltas |
| 178 | + my $tag_devel = $version =~ tr/.//dr; |
| 179 | + for my $file_tree ( |
| 180 | + map [ $_->[0], tree_for( slurp( $_->[0] ) ) ], |
| 181 | + sort { $b->[1] <=> $a->[1] } |
| 182 | + map [ $_, m{pod/perl$tag_devel([0-9]+)delta\.pod}g ], |
| 183 | + glob "pod/perl$tag_devel*delta.pod" |
| 184 | + ) |
| 185 | + { |
| 186 | + my ( $file, $delta ) = @$file_tree; |
| 187 | + merge_into( $master, $delta, $file ); |
| 188 | + } |
| 189 | + |
| 190 | + # save the result |
| 191 | + open my $fh, '>', $final_delta |
| 192 | + or die "Can't open $final_delta for writing: $!"; |
| 193 | + print $fh as_pod($master); |
| 194 | + |
| 195 | + return 0; |
| 196 | +} |
| 197 | + |
| 198 | +# make it easier to test |
| 199 | +exit main( @ARGV ) unless caller; |
0 commit comments