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 # update the module changes for the Perl you are currently building
17 ./perl -Ilib Porting/corelist-perldelta.pl --mode=update pod/perldelta.pod
19 # generate a diff between the corelist sections of two perldelta* files:
20 perl Porting/corelist-perldelta.pl --mode=check 5.017001 5.017002 <perl5172delta.pod
24 corelist-perldelta.pl is a bit schizophrenic. The part to generate the
25 new Perldelta text does not need Algorithm::Diff, but wants to be
26 run with the freshly built Perl.
28 The part to check the diff wants to be run with a Perl that has an up-to-date
29 L<Module::CoreList>, but needs the outside L<Algorithm::Diff>.
31 Ideally, the program will be split into two separate programs, one
32 to generate the text and one to show the diff between the
33 corelist sections of the last perldelta and the next perldelta.
35 Currently no information about Removed Modules is displayed in any of the
40 new => qr/New Modules and Pragma(ta)?/,
41 updated => qr/Updated Modules and Pragma(ta)?/,
42 removed => qr/Removed Modules and Pragma(ta)?/,
46 new => 'New Modules and Pragmata',
47 updated => 'Updated Modules and Pragmata',
48 removed => 'Removed Modules and Pragmata',
54 my %opt = (mode => 'generate');
57 'mode|m:s', # 'generate', 'check', 'update'
60 # by default, compare latest two version in CoreList;
61 my @versions = sort keys %Module::CoreList::version;
62 my $old = $versions[-2];
63 my $new = $versions[-1];
65 # use the provided versions if present
66 # @ARGV >=2 means [old_version] [new_version] [path/to/file]
68 ($old, $new) = (shift @ARGV, shift @ARGV);
69 die "$old is an invalid version\n" if not exists
70 $Module::CoreList::version{$old};
71 die "$new is an invalid verison\n" if not exists
72 $Module::CoreList::version{$new};
75 if ( $opt{mode} eq 'generate' ) {
76 do_generate($old => $new);
78 elsif ( $opt{mode} eq 'check' ) {
79 do_check(\*ARGV, $old => $new);
81 elsif ( $opt{mode} eq 'update' ) {
82 do_update_existing(shift @ARGV, $old => $new);
85 die "Unrecognized mode '$opt{mode}'\n";
91 # Given two perl versions, it returns a list describing the core distributions that have changed.
92 # The first three elements are hashrefs corresponding to new, updated, and removed modules
93 # and are of the form (mostly, see the special remarks about removed):
94 # 'Distribution Name' => ['Distribution Name', previous version number, current version number]
95 # where the version number is undef if the distribution did not exist.
96 # The fourth element is an arrayref of core distribution names of those distribution for which it
97 # is unknown whether they have changed and therefore need to be manually checked.
99 # In most cases, the distribution name in %Modules corresponds to the module that is representative
100 # of the distribution as listed in Module::CoreList. However, there are a few distribution names
101 # that do not correspond to a module. %distToModules has been created which maps the distribution
102 # name to a representative module. The representative module was chosen by either looking at the
103 # Makefile of the distribution or by seeing which module the distribution has been traditionally
104 # listed under in past perldeltas.
106 # There are a few distributions for which there is no single representative module (e.g. libnet).
107 # These distributions are returned as the last element of the list.
109 # %Modules contains a final key, _PERLLIB, which contains a list of modules that are owned by p5p.
110 # This list contains modules and pragmata that may also be present in Module::CoreList.
111 # A list of modules are in the list @unclaimedModules, which were manually listed based on whether
112 # they were independent modules and whether they have been listed in past perldeltas.
113 # The pragmata were found by doing something like:
114 # say for sort grep { $_ eq lc $_ and !exists $Modules{$_}}
115 # keys %{$Module::CoreList::version{'5.019003'}}
116 # and manually filtering out pragamata that were already covered.
118 # It is currently not possible to differentiate between a removed module and a removed
119 # distribution. Therefore, the removed hashref contains every module that has been removed, even if
120 # the module's corresponding distribution has not been removed.
123 my ($old, $new) = @_;
124 my $corelist = \%Module::CoreList::version;
125 my %changes = Module::CoreList::changes_between( $old, $new );
126 $deprecated = $Module::CoreList::deprecated{$new};
128 my $getModifyType = sub {
130 if ( exists $data->{left} and exists $data->{right} ) {
133 elsif ( !exists $data->{left} and exists $data->{right} ) {
136 elsif ( exists $data->{left} and !exists $data->{right} ) {
142 my @unclaimedModules = qw/AnyDBM_File B B::Concise B::Deparse Benchmark Class::Struct Config::Extensions DB DBM_Filter Devel::Peek DirHandle DynaLoader English Errno ExtUtils::Embed ExtUtils::Miniperl ExtUtils::Typemaps ExtUtils::XSSymSet Fcntl File::Basename File::Compare File::Copy File::DosGlob File::Find File::Glob File::stat FileCache FileHandle FindBin GDBM_File Getopt::Std Hash::Util Hash::Util::FieldHash I18N::Langinfo IPC::Open3 NDBM_File ODBM_File Opcode PerlIO PerlIO::encoding PerlIO::mmap PerlIO::scalar PerlIO::via Pod::Functions Pod::Html POSIX SDBM_File SelectSaver Symbol Sys::Hostname Thread Tie::Array Tie::Handle Tie::Hash Tie::Hash::NamedCapture Tie::Memoize Tie::Scalar Tie::StdHandle Tie::SubstrHash Time::gmtime Time::localtime Time::tm Unicode::UCD UNIVERSAL User::grent User::pwent VMS::DCLsym VMS::Filespec VMS::Stdio XS::Typemap Win32CORE/;
143 my @unclaimedPragmata = qw/_charnames arybase attributes blib bytes charnames deprecate diagnostics encoding feature fields filetest inc::latest integer less locale mro open ops overload overloading re sigtrap sort strict subs utf8 vars vmsish/;
144 my @unclaimed = (@unclaimedModules, @unclaimedPragmata);
146 my %distToModules = (
149 'name' => 'IO-Compress',
150 'modification' => $getModifyType->( $changes{'IO::Compress::Base'} ),
151 'data' => $changes{'IO::Compress::Base'}
156 'name' => 'Locale::Codes',
157 'modification' => $getModifyType->( $changes{'Locale::Codes'} ),
158 'data' => $changes{'Locale::Codes'}
163 'name' => 'File::Spec',
164 'modification' => $getModifyType->( $changes{'Cwd'} ),
165 'data' => $changes{'Cwd'}
168 'Scalar-List-Utils' => [
170 'name' => 'List::Util',
171 'modification' => $getModifyType->( $changes{'List::Util'} ),
172 'data' => $changes{'List::Util'}
175 'name' => 'Scalar::Util',
176 'modification' => $getModifyType->( $changes{'Scalar::Util'} ),
177 'data' => $changes{'Scalar::Util'}
180 'Text-Tabs+Wrap' => [
182 'name' => 'Text::Tabs',
183 'modification' => $getModifyType->( $changes{'Text::Tabs'} ),
184 'data' => $changes{'Text::Tabs'}
187 'name' => 'Text::Wrap',
188 'modification' => $getModifyType->( $changes{'Text::Wrap'} ),
189 'data' => $changes{'Text::Wrap'}
194 # structure is (new|removed|updated) => [ [ModuleName, previousVersion, newVersion] ]
195 my $deltaGrouping = {};
197 # list of distributions listed in %Modules that need to be manually checked because there is no module that represents it
200 # %Modules defines what is currently in core
201 for my $k ( keys %Modules ) {
202 next if $k eq '_PERLLIB'; #these are taken care of by being listed in @unclaimed
203 next if Module::CoreList::is_core($k) and !exists $changes{$k}; #modules that have not changed
205 my ( $distName, $modifyType, $data );
207 if ( exists $changes{$k} ) {
209 $modifyType = $getModifyType->( $changes{$k} );
210 $data = $changes{$k};
212 elsif ( exists $distToModules{$k} ) {
213 # modification will be undef if the distribution has not changed
214 my @modules = grep { $_->{modification} } @{ $distToModules{$k} };
216 $deltaGrouping->{ $_->{modification} }->{ $_->{name} } = [ $_->{name}, $_->{data}->{left}, $_->{data}->{right} ];
221 push @manuallyCheck, $k and next;
224 $deltaGrouping->{$modifyType}->{$distName} = [ $distName, $data->{left}, $data->{right} ];
227 for my $k (@unclaimed) {
228 if ( exists $changes{$k} ) {
229 $deltaGrouping->{ $getModifyType->( $changes{$k} ) }->{$k} =
230 [ $k, $changes{$k}->{left}, $changes{$k}->{right} ];
234 # in old corelist, but not this one => removed
235 # N.B. This is exhaustive -- not just what's in %Modules, so modules removed from
236 # distributions will show up here, too. Some person will have to review to see what's
237 # important. That's the best we can do without a historical Maintainers.pl
238 for my $k ( keys %{ $corelist->{$old} } ) {
239 if ( ! exists $corelist->{$new}{$k} ) {
240 $deltaGrouping->{'removed'}->{$k} = [ $k, $corelist->{$old}{$k}, undef ];
245 \%{ $deltaGrouping->{'new'} },
246 \%{ $deltaGrouping->{'removed'} },
247 \%{ $deltaGrouping->{'updated'} },
252 # currently does not update the Removed Module section
253 sub do_update_existing {
254 my ( $existing, $old, $new ) = @_;
256 my ( $added, $removed, $updated, $manuallyCheck ) = corelist_delta( $old => $new );
257 if ($manuallyCheck) {
258 print "It cannot be determined whether the following distributions have changed.\n";
259 print "Please check and list accordingly:\n";
260 say "\t* $_" for sort @{$manuallyCheck};
267 #removed => $removed, ignore removed for now
270 my $text = DeltaUpdater::transform_pod( $existing, $data );
271 open my $out, '>', $existing or die "can't open perldelta file $existing: $!";
275 say "The New and Updated Modules and Pragamata sections in $existing have been updated";
276 say "Please ensure the Removed Modules and Pragmata section is up-to-date";
280 my ($old, $new) = @_;
281 my ($added, $removed, $updated, $manuallyCheck) = corelist_delta($old => $new);
283 if ($manuallyCheck) {
284 print "\nXXXIt cannot be determined whether the following distributions have changed.\n";
285 print "Please check and list accordingly:\n";
286 say "\t$_" for @{$manuallyCheck};
293 #removed => $removed, ignore removed for now
296 say DeltaUpdater::sections_to_pod($data)
300 my ($in, $old, $new) = @_;
302 my $delta = DeltaParser->new($in);
303 my ($added, $removed, $updated) = corelist_delta($old => $new);
305 # because of the difficulty in identifying the distribution for removed modules
306 # don't bother checking them
307 for my $ck ([ 'new', $delta->new_modules, $added ],
308 #[ 'removed', $delta->removed_modules, $removed ],
309 [ 'updated', $delta->updated_modules, $updated ] ) {
310 my @delta = @{ $ck->[1] };
311 my @corelist = sort { lc $a->[0] cmp lc $b->[0] } values %{ $ck->[2] };
313 printf $ck->[0] . ":\n";
315 require Algorithm::Diff;
316 my $diff = Algorithm::Diff->new(map {
317 [map { join q{ } => grep defined, @{ $_ } } @{ $_ }]
318 } \@delta, \@corelist);
320 while ($diff->Next) {
323 if (!$diff->Items(2)) {
324 printf "%d,%dd%d\n", $diff->Get(qw( Min1 Max1 Max2 ));
325 } elsif(!$diff->Items(1)) {
326 printf "%da%d,%d\n", $diff->Get(qw( Max1 Min2 Max2 ));
329 printf "%d,%dc%d,%d\n", $diff->Get(qw( Min1 Max1 Min2 Max2 ));
331 print "Delta< $_\n" for $diff->Items(1);
333 print "Corelist> $_\n" for $diff->Items(2);
342 package DeltaUpdater;
343 use List::Util 'reduce';
345 sub get_section_name_from_heading {
347 while (my ($key, $expression) = each %sections) {
348 if ($heading =~ $expression) {
349 return $titles{$key};
352 die "$heading did not match any section";
355 sub is_desired_section_name {
356 for (values %sections) {
357 return 1 if $_[0] =~ $_;
362 # verify the module and pragmata in the section, changing the stated version if necessary
363 # this subroutine warns if the module name cannot be parsed or if it is not listed in
364 # the results returned from corelist_delta()
366 # a side-effect of calling this function is that modules present in the section are
367 # removed from $data, resulting in $data containing only those modules and pragmata
368 # that were not listed in the perldelta file. This means we can then pass $data to
369 # add_to_section() without worrying about filtering out duplicates
371 my ( $section, $data, $title ) = @_;
372 my @items = @{ $section->{items} };
374 for my $item (@items) {
376 my $content = $item->{text};
377 my $module = $item->{name};
380 next if !$module and $content =~ /\s*xx*\s*/i;
382 say "Could not parse module name; line is:\n\t$content" and next unless $module;
384 if ( !$data->{$title}{$module} ) {
385 print "$module is not listed as being $title in Module::CoreList.\n";
386 print "Ensure Module::CoreList has been updated and\n";
387 print "check to see that the distribution is not listed under another name.\n\n";
391 if ( $title eq 'new' ) {
392 my ($new) = $content =~ /(\d[^\s]+)\s+has\s+been.*$/m;
393 say "Could not parse new version for $module; line is:\n\t$content" and next unless $new;
394 if ( $data->{$title}{$module}[2] ne $new ) {
395 say "$module: new version differs; version in pod: $new; version in corelist: " . $data->{$title}{$module}[2];
397 $content =~ s/\d[^\s]+(\s+has\s+been.*$)/$data->{$title}{$module}[2].$1/me;
400 elsif ( $title eq 'updated' ) {
401 my ( $prev, $new ) = $content =~ /from\s+(?:version\s+)?(\d[^\s]+)\s+to\s+(?:version\s+)?(\d[^\s,]+?)(?=[\s,]|\.\s|\.$|$).*/s;
402 say "Could not parse old and new version for $module; line is:\n\t$content" and next
403 unless $prev and $new;
404 if ( $data->{$title}{$module}[1] ne $prev ) {
405 say "$module: previous version differs; version in pod: $prev; version in corelist: " . $data->{$title}{$module}[1];
407 if ( $data->{$title}{$module}[2] ne $new ) {
408 say "$module: new version differs; version in pod: $new; version in corelist: " . $data->{$title}{$module}[2];
411 s/(from\s+(?:version\s+)?)\d[^\s]+(\s+to\s+(?:version\s+)?)\d[^\s,]+?(?=[\s,]|\.\s|\.$|$)(.*)/$1.$data->{$title}{$module}[1].$2.$data->{$title}{$module}[2].$3/se;
414 elsif ( $title eq 'removed' ) {
415 my ($prev) = $content =~ /^.*?was\s+(\d[^\s]+?)/m;
416 say "Could not parse old version for $module; line is:\n\t$content" and next unless $prev;
417 if ( $data->{$title}{$module}[1] ne $prev ) {
418 say "$module: previous version differs; $prev " . $data->{$title}{$module}[1];
420 $content =~ s/(^.*?was\s+)\d[^\s]+?/$1.$data->{$title}{$module}[1]/me;
423 delete $data->{$title}{$module};
424 $item->{text} = $content;
429 # add modules and pragmata present in $data to the section
431 my ( $section, $data, $title ) = @_;
433 #undef is a valid version name in Module::CoreList so supress warnings about concatenating undef values
434 no warnings 'uninitialized';
435 for ( values %{ $data->{$title} } ) {
436 my ( $mod, $old_v, $new_v ) = @{$_};
439 $item = { name => $mod, text => "=item *\n" };
440 if ( $title eq 'new' ) {
441 $text = "L<$mod> $new_v has been added to the Perl core.\n";
444 elsif ( $title eq 'updated' ) {
445 $text = "L<$mod> has been upgraded from version $old_v to $new_v.\n";
446 if ( $deprecated->{$mod} ) {
447 $text .= "NOTE: L<$mod> is deprecated and may be removed from a future version of Perl.\n";
451 elsif ( $title eq 'removed' ) {
452 $text = "C<$mod> has been removed from the Perl core. Prior version was $old_v.\n";
455 $item->{text} .= "\n$text\n";
456 push @{ $section->{items} }, $item;
461 sub sort_items_in_section {
464 # if we could not parse the module name, it will be uninitalized
465 # in sort. This is not a problem as it will just result in these
466 # sections being placed near the begining of the section
467 no warnings 'uninitialized';
469 [ sort { lc $a->{name} cmp lc $b->{name} } @{ $section->{items} } ];
473 # given a hashref of the form returned by corelist_delta()
474 # and a hash structured as documented in transform_pod(), it returns
475 # a pod string representation of the sections, creating sections
477 sub sections_to_pod {
478 my ( $data, %sections ) = @_;
483 [ 'New Modules and Pragmata', 'new' ],
484 [ 'Updated Modules and Pragmata', 'updated' ],
485 [ 'Removed Modules and Pragmata', 'removed' ]
489 my ( $section_name, $title ) = @{$_};
491 my $section = $sections{$section_name} // {
492 name => $section_name,
493 preceding_text => "=head2 $_->[0]\n=over 4\n",
494 following_text => "=back\n",
499 $section = update_section( $section, $data, $title );
500 $section = add_to_section( $section, $data, $title );
501 $section = sort_items_in_section( $section );
503 next if $section->{manual} and scalar @{ $section->{items} } == 0;
505 my $items = reduce { no warnings 'once'; $a . $b->{text} }
506 ( '', @{ $section->{items} } );
508 ( $section->{preceding_text} // '' )
510 . ( $section->{following_text} // '' );
515 # given a filename corresponding to an existing perldelta file
516 # and a hashref of the form returned by corelist_delta(), it
517 # returns a string of the resulting file after the module
518 # information has been added.
520 my ( $existing, $data ) = @_;
522 # will contain hashrefs corresponding to new, updated and removed
523 # modules and pragmata keyed by section name
524 # each section is hashref of the structure
525 # preceding_text => Text occurring before and including the over
526 # region containing the list of modules,
527 # items => [Arrayref of hashrefs corresponding to a module
529 # an entry has the form:
530 # name => Module name or undef if the name could not be determined
531 # text => The text of the entry, including the item heading
533 # following_text => Any text not corresponding to a module
534 # that occurs after the first module
536 # the sections are converted to a pod string by calling sections_to_pod()
539 # we are in the Modules_and_Pragmata's section
540 my $in_Modules_and_Pragmata;
542 # we are the Modules_and_Pragmata's section but have not
543 # encountered any of the desired sections. We use this
544 # flag to determine whether we should append the text to $out
545 # or we need to delay appending until the module listings are
546 # processed and instead append to $append_to_out
547 my $in_Modules_and_Pragmata_preamble;
549 my $done_processing_Modules_and_Pragmata;
553 # $nested_element_level == 0 : not in an over region, treat lines as text
554 # $nested_element_level == 1 : presumably in the top over region that
555 # corresponds to the module listing. Treat
556 # each item as a module
557 # $nested_element_level > 1 : we only consider these values when we are in an item
558 # We treat lines as the text of the current item.
559 my $nested_element_level = 0;
562 my $need_to_parse_module_name;
565 my $append_to_out = '';
567 open my $fh, '<', $existing or die "can't open perldelta file $existing: $!";
571 # treat the rest of the file as plain text
572 if ($done_processing_Modules_and_Pragmata) {
577 elsif ( !$in_Modules_and_Pragmata ) {
578 # entering Modules and Pragmata
579 if (/^=head1 Modules and Pragmata/) {
580 $in_Modules_and_Pragmata = 1;
581 $in_Modules_and_Pragmata_preamble = 1;
587 # leaving Modules and Pragmata
589 if ($current_section) {
590 push @{ $current_section->{items} }, $current_item
592 $sections{ $current_section->{name} } = $current_section;
594 $done_processing_Modules_and_Pragmata = 1;
596 sections_to_pod( $data, %sections ) . $append_to_out . $_;
600 # new section in Modules and Pragmata
601 elsif (/^=head2 (.*?)$/) {
603 if ($current_section) {
604 push @{ $current_section->{items} }, $current_item
606 $sections{ $current_section->{name} } = $current_section;
607 undef $current_section;
610 if ( is_desired_section_name($name) ) {
611 undef $in_Modules_and_Pragmata_preamble;
612 if ( $nested_element_level > 0 ) {
613 die "Unexpected head2 at line no. $.";
615 my $title = get_section_name_from_heading($name);
616 if ( exists $sections{$title} ) {
617 die "$name occurred twice at line no. $.";
619 $current_section = {};
620 $current_section->{name} = $title;
621 $current_section->{preceding_text} = $_;
622 $current_section->{items} = [];
623 $nested_element_level = 0;
627 # otherwise treat section as plain text
629 if ($in_Modules_and_Pragmata_preamble) {
633 $append_to_out .= $_;
639 elsif ($current_section) {
641 # not in an over region
642 if ( $nested_element_level == 0 ) {
644 $nested_element_level++;
646 if ( scalar @{ $current_section->{items} } > 0 ) {
647 $current_section->{following_text} .= $_;
650 $current_section->{preceding_text} .= $_;
656 if ($need_to_parse_module_name) {
657 # the item may not have a parsable module name, which means that
658 # $current_item->{name} will never be defined.
659 if (/^(?:L|C)<(.+?)>/) {
660 $current_item->{name} = $1;
661 undef $need_to_parse_module_name;
663 # =item or =back signals the end of an item
664 # block, which we handle below
665 if ( !/^=(?:item|back)/ ) {
666 $current_item->{text} .= $_;
670 # currently in an over region
671 # treat text inside region as plain text
672 if ( $nested_element_level > 1 ) {
674 $nested_element_level--;
677 $nested_element_level++;
679 $current_item->{text} .= $_;
682 # entering over region
684 $nested_element_level++;
685 $current_item->{text} .= $_;
688 # =item or =back signals the end of an item
689 # block, which we handle below
690 if ( !/^=(?:item|back)/ ) {
691 $current_item->{text} .= $_;
697 push @{ $current_section->{items} }, $current_item
699 $current_item = { text => $_ };
700 $need_to_parse_module_name = 1;
705 push @{ $current_section->{items} }, $current_item
708 $nested_element_level--;
711 if ( scalar @{ $current_section->{items} } == 0 ) {
712 $current_section->{preceding_text} .= $_;
715 $current_section->{following_text} .= $_;
720 # text in Modules and Pragmata not in a head2 region
722 if ($in_Modules_and_Pragmata_preamble) {
726 $append_to_out .= $_;
732 die 'Never saw Modules and Pragmata section' unless $in_Modules_and_Pragmata;
740 use Pod::Simple::SimpleTree;
743 my ($class, $input) = @_;
745 my $self = bless {} => $class;
747 my $parsed_pod = Pod::Simple::SimpleTree->new->parse_file($input)->root;
748 splice @{ $parsed_pod }, 0, 2; # we don't care about the document structure,
749 # just the nodes within it
751 $self->_parse_delta($parsed_pod);
756 # creates the accessor methods:
760 for my $k (keys %sections) {
762 my $m = "${k}_modules";
763 *$m = sub { $_[0]->{$m} };
767 my ($self, $pod) = @_;
769 my $new_section = $self->_look_for_section( $pod, $sections{new} );
770 my $updated_section = $self->_look_for_section( $pod, $sections{updated} );
771 my $removed_section = $self->_look_for_section( $pod, $sections{removed} );
773 $self->_parse_new_section($new_section);
774 $self->_parse_updated_section($updated_section);
775 $self->_parse_removed_section($removed_section);
777 for (qw/new_modules updated_modules removed_modules/) {
779 [ sort { lc $a->[0] cmp lc $b->[0] } @{ $self->{$_} } ];
785 sub _parse_new_section {
786 my ($self, $section) = @_;
788 $self->{new_modules} = [];
789 return unless $section;
790 $self->{new_modules} = $self->_parse_section($section => sub {
793 my ($first, $second) = @{ $el }[2, 3];
794 my ($ver) = $second =~ /(\d[^\s]+)\s+has\s+been/;
796 return [ $first->[2], undef, $ver ];
802 sub _parse_updated_section {
803 my ($self, $section) = @_;
805 $self->{updated_modules} = [];
806 return unless $section;
807 $self->{updated_modules} = $self->_parse_section($section => sub {
810 my ($first, $second) = @{ $el }[2, 3];
811 my $module = $first->[2];
813 # the regular expression matches the following:
814 # from VERSION_NUMBER to VERSION_NUMBER
815 # from VERSION_NUMBER to VERSION_NUMBER.
816 # from version VERSION_NUMBER to version VERSION_NUMBER.
817 # from VERSION_NUMBER to VERSION_NUMBER and MODULE from VERSION_NUMBER to VERSION_NUMBER
818 # from VERSION_NUMBER to VERSION_NUMBER, and MODULE from VERSION_NUMBER to VERSION_NUMBER
820 # some perldeltas contain more than one module listed in an entry, this only attempts to match the
822 my ($old, $new) = $second =~
823 /from\s+(?:version\s+)?(\d[^\s]+)\s+to\s+(?:version\s+)?(\d[^\s,]+?)(?=[\s,]|\.\s|\.$|$).*/s;
825 warn "Unable to extract old or new version of $module from perldelta"
826 if !defined $old || !defined $new;
828 return [ $module, $old, $new ];
834 sub _parse_removed_section {
835 my ($self, $section) = @_;
837 $self->{removed_modules} = [];
838 return unless $section;
839 $self->{removed_modules} = $self->_parse_section($section => sub {
842 my ($first, $second) = @{ $el }[2, 3];
843 my ($old) = $second =~ /was\s+(\d[^\s]+?)\.?$/;
845 return [ $first->[2], $old, undef ];
852 my ($self, $section, $parser) = @_;
854 my $items = $self->_look_down($section => sub {
856 return unless ref $el && $el->[0] =~ /^item-/
857 && @{ $el } > 2 && ref $el->[2];
858 return unless $el->[2]->[0] =~ /C|L/;
863 return [map { $parser->($_) } @{ $items }];
867 my ($self, $pod, $predicate) = @_;
871 while (my $el = shift @pod) {
872 push @l, $el if $predicate->($el);
876 unshift @pod, @el if @el;
880 return @l ? \@l : undef;
883 sub _look_for_section {
884 my ($self, $pod, $section) = @_;
887 $self->_look_for_range($pod,
890 my ($heading) = $el->[0] =~ /^head(\d)$/;
891 my $f = $heading && $el->[2] =~ /^$section/;
892 $level = $heading if $f && !$level;
897 $el->[0] =~ /^head(\d)$/ && $1 <= $level;
902 sub _look_for_range {
903 my ($self, $pod, $start_predicate, $stop_predicate) = @_;
906 for my $el (@{ $pod }) {
908 return \@l if $stop_predicate->($el);
911 next unless $start_predicate->($el);