6 use Maintainers qw/%Modules/;
7 use lib 'dist/Module-CoreList/lib';
13 # generate the module changes for the Perl you are currently building
14 ./perl -Ilib Porting/corelist-perldelta.pl
16 # generate a diff between the corelist sections of two perldelta* files:
17 perl Porting/corelist-perldelta.pl --mode=check 5.017001 5.017002 <perl5172delta.pod
21 corelist-perldelta.pl is a bit schizophrenic. The part to generate the
22 new Perldelta text does not need Algorithm::Diff, but wants to be
23 run with the freshly built Perl.
25 The part to check the diff wants to be run with a Perl that has an up-to-date
26 L<Module::CoreList>, but needs the outside L<Algorithm::Diff>.
28 Ideally, the program will be split into two separate programs, one
29 to generate the text and one to show the diff between the
30 corelist sections of the last perldelta and the next perldelta.
35 new => qr/New Modules and Pragma(ta)?/,
36 updated => qr/Updated Modules and Pragma(ta)?/,
37 removed => qr/Removed Modules and Pragma(ta)?/,
41 new => 'New Modules and Pragmata',
42 updated => 'Updated Modules and Pragmata',
43 removed => 'Removed Modules and Pragmata',
48 #--------------------------------------------------------------------------#
51 my ($mod, $old_v, $new_v) = @_;
53 say "L<$mod> $new_v has been added to the Perl core.\n";
57 my ($mod, $old_v, $new_v) = @_;
59 say "L<$mod> has been upgraded from version $old_v to $new_v.\n";
60 if ( $deprecated->{$mod} ) {
61 say "NOTE: L<$mod> is deprecated and may be removed from a future version of Perl.\n";
66 my ($mod, $old_v, $new_v) = @_;
68 say "C<$mod> has been removed from the Perl core. Prior version was $old_v.\n";
71 sub generate_section {
72 my ($title, $item_sub, @mods ) = @_;
75 say "=head2 $title\n";
78 for my $tuple ( sort { lc($a->[0]) cmp lc($b->[0]) } @mods ) {
79 my ($mod,$old_v,$new_v) = @$tuple;
80 $old_v //= q('undef');
81 $new_v //= q('undef');
82 $item_sub->($mod, $old_v, $new_v);
88 #--------------------------------------------------------------------------#
91 my %opt = (mode => 'generate');
94 'mode|m:s', # 'generate', 'check'
97 # by default, compare latest two version in CoreList;
98 my @versions = sort keys %Module::CoreList::version;
99 my ($old, $new) = (shift @ARGV, shift @ARGV);
100 $old ||= $versions[-2];
101 $new ||= $versions[-1];
103 if ( $opt{mode} eq 'generate' ) {
104 do_generate($old => $new);
106 elsif ( $opt{mode} eq 'check' ) {
107 do_check(\*ARGV, $old => $new);
110 die "Unrecognized mode '$opt{mode}'\n";
117 my ($old, $new) = @_;
118 my $corelist = \%Module::CoreList::version;
120 $deprecated = $Module::CoreList::deprecated{$new};
122 my (@new,@deprecated,@removed,@pragmas,@modules);
124 # %Modules defines what is currently in core
125 for my $k ( keys %Modules ) {
126 next unless exists $corelist->{$new}{$k};
127 my $old_ver = $corelist->{$old}{$k};
128 my $new_ver = $corelist->{$new}{$k};
129 # in core but not in last corelist
130 if ( ! exists $corelist->{$old}{$k} ) {
131 push @new, [$k, undef, $new_ver];
133 # otherwise just pragmas or modules
135 my $old_ver = $corelist->{$old}{$k};
136 my $new_ver = $corelist->{$new}{$k};
137 next unless defined $old_ver && defined $new_ver && $old_ver ne $new_ver;
138 my $tuple = [ $k, $old_ver, $new_ver ];
140 push @pragmas, $tuple;
143 push @modules, $tuple;
148 # in old corelist, but not this one => removed
149 # N.B. This is exhaustive -- not just what's in %Modules, so modules removed from
150 # distributions will show up here, too. Some person will have to review to see what's
151 # important. That's the best we can do without a historical Maintainers.pl
152 for my $k ( keys %{ $corelist->{$old} } ) {
153 if ( ! exists $corelist->{$new}{$k} ) {
154 push @removed, [$k, $corelist->{$old}{$k}, undef];
158 return (\@new, \@removed, \@pragmas, \@modules);
162 my ($old, $new) = @_;
163 my ($added, $removed, $pragmas, $modules) = corelist_delta($old => $new);
165 generate_section($titles{new}, \&added, @{ $added });
166 generate_section($titles{updated}, \&updated, @{ $pragmas }, @{ $modules });
167 generate_section($titles{removed}, \&removed, @{ $removed });
171 my ($in, $old, $new) = @_;
173 my $delta = DeltaParser->new($in);
174 my ($added, $removed, $pragmas, $modules) = corelist_delta($old => $new);
176 for my $ck (['new', $delta->new_modules, $added],
177 ['removed', $delta->removed_modules, $removed],
178 ['updated', $delta->updated_modules, [@{ $modules }, @{ $pragmas }]]) {
179 my @delta = @{ $ck->[1] };
180 my @corelist = sort { lc $a->[0] cmp lc $b->[0] } @{ $ck->[2] };
182 printf $ck->[0] . ":\n";
184 require Algorithm::Diff;
185 my $diff = Algorithm::Diff->new(map {
186 [map { join q{ } => grep defined, @{ $_ } } @{ $_ }]
187 } \@delta, \@corelist);
189 while ($diff->Next) {
192 if (!$diff->Items(2)) {
193 printf "%d,%dd%d\n", $diff->Get(qw( Min1 Max1 Max2 ));
194 } elsif(!$diff->Items(1)) {
195 printf "%da%d,%d\n", $diff->Get(qw( Max1 Min2 Max2 ));
198 printf "%d,%dc%d,%d\n", $diff->Get(qw( Min1 Max1 Min2 Max2 ));
200 print "< $_\n" for $diff->Items(1);
202 print "> $_\n" for $diff->Items(2);
211 use Pod::Simple::SimpleTree;
214 my ($class, $input) = @_;
216 my $self = bless {} => $class;
218 my $parsed_pod = Pod::Simple::SimpleTree->new->parse_file($input)->root;
219 splice @{ $parsed_pod }, 0, 2; # we don't care about the document structure,
220 # just the nodes within it
222 $self->_parse_delta($parsed_pod);
227 for my $k (keys %sections) {
229 my $m = "${k}_modules";
230 *$m = sub { $_[0]->{$m} };
234 my ($self, $pod) = @_;
237 my ($t, $s) = @{ $_ };
239 # Keep the section title if it has one:
240 if( $s->[0]->[0] eq 'head2' ) {
241 #warn "Keeping section title '$s->[0]->[2]'";
242 $titles{ $t } = $s->[0]->[2]
246 $self->${\"_parse_${t}_section"}($s)
248 my $s = $self->_look_for_section($pod => $sections{$_})
249 or die "failed to parse $_ section";
253 for my $s (keys %sections) {
254 my $m = "${s}_modules";
256 $self->{$m} = [sort {
257 lc $a->[0] cmp lc $b->[0]
264 sub _parse_new_section {
265 my ($self, $section) = @_;
267 $self->{new_modules} = $self->_parse_section($section => sub {
270 my ($first, $second) = @{ $el }[2, 3];
271 my ($ver) = $second =~ /(\d[^\s]+)\s+has\s+been/;
273 return [ $first->[2], undef, $ver ];
279 sub _parse_updated_section {
280 my ($self, $section) = @_;
282 $self->{updated_modules} = $self->_parse_section($section => sub {
285 my ($first, $second) = @{ $el }[2, 3];
286 my $module = $first->[2];
287 my ($old, $new) = $second =~
288 /from\s+(?:version\s+)?(\d[^\s]+)\s+to\s+(\d[^\s]+?)\.?$/;
290 warn "Unable to extract old or new version of $module from perldelta"
291 if !defined $old || !defined $new;
293 return [ $module, $old, $new ];
299 sub _parse_removed_section {
300 my ($self, $section) = @_;
301 $self->{removed_modules} = $self->_parse_section($section => sub {
304 my ($first, $second) = @{ $el }[2, 3];
305 my ($old) = $second =~ /was\s+(\d[^\s]+?)\.?$/;
307 return [ $first->[2], $old, undef ];
314 my ($self, $section, $parser) = @_;
316 my $items = $self->_look_down($section => sub {
318 return unless ref $el && $el->[0] =~ /^item-/
319 && @{ $el } > 2 && ref $el->[2];
320 return unless $el->[2]->[0] eq 'C';
325 return [map { $parser->($_) } @{ $items }];
329 my ($self, $pod, $predicate) = @_;
333 while (my $el = shift @pod) {
334 push @l, $el if $predicate->($el);
338 unshift @pod, @el if @el;
342 return @l ? \@l : undef;
345 sub _look_for_section {
346 my ($self, $pod, $section) = @_;
349 $self->_look_for_range($pod,
352 my ($heading) = $el->[0] =~ /^head(\d)$/;
353 my $f = $heading && $el->[2] =~ /^$section/;
354 $level = $heading if $f && !$level;
359 $el->[0] =~ /^head(\d)$/ && $1 <= $level;
364 sub _look_for_range {
365 my ($self, $pod, $start_predicate, $stop_predicate) = @_;
368 for my $el (@{ $pod }) {
370 return \@l if $stop_predicate->($el);
373 next unless $start_predicate->($el);