File Coverage

blib/lib/Module/CoreList/More.pm
Criterion Covered Total %
statement 22 23 95.6
branch 2 2 100.0
condition n/a
subroutine 13 13 100.0
pod 8 8 100.0
total 45 46 97.8


line stmt bran cond sub pod time code
1             package Module::CoreList::More;
2              
3             our $DATE = '2016-02-17'; # DATE
4             our $VERSION = '0.08'; # VERSION
5              
6 2     2   36467 use 5.010001;
  2         6  
7 2     2   7 use strict;
  2         2  
  2         31  
8 2     2   5 use warnings;
  2         1  
  2         38  
9              
10 2     2   2202 use Module::CoreList ();
  2         32323  
  2         2621  
11              
12             sub _firstidx {
13 24     24   28 my ($item, $ary) = @_;
14 24         39 for (0..@$ary-1) {
15 1208 100       1372 return $_ if $ary->[$_] eq $item;
16             }
17 0         0 -1;
18             }
19              
20             # construct our own %delta from Module::CoreList's %delta. our version is a
21             # linear "linked list" (e.g. %delta{5.017} is a delta against %delta{5.016003}
22             # instead of %delta{5.016}. also, version numbers are cleaned (some versions in
23             # Module::CoreList has trailing whitespaces or alphas)
24              
25             # the same for our own %released (version numbers in keys are canonicalized)
26              
27             our @releases; # list of perl release versions, sorted by version
28             our @releases_by_date; # list of perl release versions, sorted by release date
29             our %delta;
30             our %released;
31             my %rel_orig_formats;
32             {
33             # first let's only stored the canonical format of release versions
34             # (Module::Core stores "5.01" as well as "5.010000"), for less headache
35             # let's just store "5.010000"
36             my %releases;
37             for (sort keys %Module::CoreList::delta) {
38             my $canonical = sprintf "%.6f", $_;
39             next if $releases{$canonical};
40             $releases{$canonical} = $Module::CoreList::delta{$_};
41             $released{$canonical} = $Module::CoreList::released{$_};
42             $rel_orig_formats{$canonical} = $_;
43             }
44             @releases = sort keys %releases;
45             @releases_by_date = sort {$released{$a} cmp $released{$b}} keys %releases;
46              
47             for my $i (0..@releases-1) {
48             my $reldelta = $releases{$releases[$i]};
49             my $delta_from = $reldelta->{delta_from};
50             my $changed = {};
51             my $removed = {};
52             # make sure that %delta will be linear "linked list" by release versions
53             if ($delta_from && $delta_from != $releases[$i-1]) {
54             $delta_from = sprintf "%.6f", $delta_from;
55             my $i0 = _firstidx($delta_from, \@releases);
56             #say "D: delta_from jumps from $delta_from (#$i0) -> $releases[$i] (#$i)";
57             # accumulate changes between delta at releases #($i0+1) and #($i-1),
58             # subtract them from delta at #($i)
59             my $changed_between = {};
60             my $removed_between = {};
61             for my $j ($i0+1 .. $i-1) {
62             my $reldelta_between = $releases{$releases[$j]};
63             for (keys %{$reldelta_between->{changed}}) {
64             $changed_between->{$_} = $reldelta_between->{changed}{$_};
65             delete $removed_between->{$_};
66             }
67             for (keys %{$reldelta_between->{removed}}) {
68             $removed_between->{$_} = $reldelta_between->{removed}{$_};
69             }
70             }
71             for (keys %{$reldelta->{changed}}) {
72             next if exists($changed_between->{$_}) &&
73             !defined($changed_between->{$_}) && !defined($reldelta->{changed}{$_}) || # both undef
74             defined ($changed_between->{$_}) && defined ($reldelta->{changed}{$_}) && $changed_between->{$_} eq $reldelta->{changed}{$_}; # both defined & equal
75             $changed->{$_} = $reldelta->{changed}{$_};
76             }
77             for (keys %{$reldelta->{removed}}) {
78             next if $removed_between->{$_};
79             $removed->{$_} = $reldelta->{removed}{$_};
80             }
81             } else {
82             $changed = { %{$reldelta->{changed}} };
83             $removed = { %{$reldelta->{removed} // {}} };
84             }
85              
86             # clean version numbers
87             for my $k (keys %$changed) {
88             for ($changed->{$k}) {
89             next unless defined;
90             s/\s+$//; # eliminate trailing space
91             # for "alpha" version, turn trailing junk such as letters to _
92             # plus a number based on the first junk char
93             s/([^.0-9_])[^.0-9_]*$/'_'.sprintf('%03d',ord $1)/e;
94             }
95             }
96             $delta{$releases[$i]} = {
97             changed => $changed,
98             removed => $removed,
99             };
100             }
101             }
102              
103             my $removed_from = sub {
104             my ($order, $module) = splice @_,0,2;
105             $module = shift if eval { $module->isa(__PACKAGE__) } && @_ > 0 && defined($_[0]) && $_[0] =~ /^\w/;
106              
107             for my $rel ($order eq 'date' ? @releases_by_date : @releases) {
108             return $rel_orig_formats{$rel} if $delta{$rel}{removed}{$module};
109             }
110              
111             return;
112             };
113              
114             sub removed_from {
115 3     3 1 901 $removed_from->('', @_);
116             }
117              
118             sub removed_from_by_date {
119 3     3 1 1491 $removed_from->('date', @_);
120             }
121              
122             my $first_release = sub {
123             my ($order, $module) = splice @_,0,2;
124             $module = shift if eval { $module->isa(__PACKAGE__) } && @_ > 0 && defined($_[0]) && $_[0] =~ /^\w/;
125              
126             for my $rel ($order eq 'date' ? @releases_by_date : @releases) {
127             return $rel_orig_formats{$rel} if exists $delta{$rel}{changed}{$module};
128             }
129              
130             return;
131             };
132              
133             sub first_release {
134 11     11 1 3379 $first_release->('', @_);
135             }
136              
137             sub first_release_by_date {
138 11     11 1 1847 $first_release->('date', @_);
139             }
140              
141             my $is_core = sub {
142             my $all = pop;
143             my $module = shift;
144             $module = shift if eval { $module->isa(__PACKAGE__) } && @_ > 0 && defined($_[0]) && $_[0] =~ /^\w/;
145             my ($module_version, $perl_version);
146              
147             $module_version = shift if @_ > 0;
148             $perl_version = @_ > 0 ? shift : $];
149              
150             my $mod_exists = 0;
151             my $mod_ver; # module version at each perl release, -1 means doesn't exist
152              
153             RELEASE:
154             for my $rel (sort keys %delta) {
155             last if $all && $rel > $perl_version; # this is the difference with is_still_core()
156              
157             my $reldelta = $delta{$rel};
158              
159             if ($rel > $perl_version) {
160             if ($reldelta->{removed}{$module}) {
161             $mod_exists = 0;
162             } else {
163             next;
164             }
165             }
166              
167             if (exists $reldelta->{changed}{$module}) {
168             $mod_exists = 1;
169             $mod_ver = $reldelta->{changed}{$module};
170             } elsif ($reldelta->{removed}{$module}) {
171             $mod_exists = 0;
172             }
173             }
174              
175             if ($mod_exists) {
176             if (defined $module_version) {
177             return 0 unless defined $mod_ver;
178             return version->parse($mod_ver) >= version->parse($module_version) ? 1:0;
179             }
180             return 1;
181             }
182             return 0;
183             };
184              
185 863     863 1 1456929 sub is_core { $is_core->(@_,1) }
186              
187 863     863 1 349169 sub is_still_core { $is_core->(@_,0) }
188              
189             my $list_core_modules = sub {
190             my $all = pop;
191             my $class = shift if @_ && eval { $_[0]->isa(__PACKAGE__) };
192             my $perl_version = @_ ? shift : $];
193              
194             my %added;
195             my %removed;
196              
197             RELEASE:
198             for my $rel (sort keys %delta) {
199             last if $all && $rel > $perl_version; # this is the difference with list_still_core_modules()
200              
201             my $delta = $delta{$rel};
202              
203             next unless $delta->{changed};
204             for my $mod (keys %{$delta->{changed}}) {
205             # module has been removed between perl_version..latest, skip
206             next if $removed{$mod};
207              
208             if (exists $added{$mod}) {
209             # module has been added in a previous version, update first
210             # version
211             $added{$mod} = $delta->{changed}{$mod} if $rel <= $perl_version;
212             } else {
213             # module is first added after perl_version, skip
214             next if $rel > $perl_version;
215              
216             $added{$mod} = $delta->{changed}{$mod};
217             }
218             }
219             next unless $delta->{removed};
220             for my $mod (keys %{$delta->{removed}}) {
221             delete $added{$mod};
222             # module has been removed between perl_version..latest, mark it
223             $removed{$mod}++ if $rel >= $perl_version;
224             }
225              
226             }
227             %added;
228             };
229              
230 4     4 1 4221 sub list_core_modules { $list_core_modules->(@_,1) }
231              
232 3     3 1 1203 sub list_still_core_modules { $list_core_modules->(@_,0) }
233              
234             1;
235              
236             # ABSTRACT: More functions for Module::CoreList
237              
238             __END__
239              
240             =pod
241              
242             =encoding UTF-8
243              
244             =head1 NAME
245              
246             Module::CoreList::More - More functions for Module::CoreList
247              
248             =head1 VERSION
249              
250             This document describes version 0.08 of Module::CoreList::More (from Perl distribution Module-CoreList-More), released on 2016-02-17.
251              
252             =head1 SYNOPSIS
253              
254             use Module::CoreList::More;
255              
256             # true, this module has always been in core since specified perl release
257             Module::CoreList::More->is_still_core("Benchmark", 5.010001);
258              
259             # false, since CGI is removed in perl 5.021000
260             Module::CoreList::More->is_still_core("CGI");
261              
262             # false, never been in core
263             Module::CoreList::More->is_still_core("Foo");
264              
265             my %modules = list_still_core_modules(5.010001);
266              
267             =head1 DESCRIPTION
268              
269             This module is my experiment for providing more functionality to (or related to)
270             L<Module::CoreList>. Some ideas include: faster functions, more querying
271             functions, more convenience functions. When I've got something stable and useful
272             to show for, I'll most probably suggest the appropriate additions to
273             Module::CoreList.
274              
275             Below are random notes:
276              
277             =head1 FUNCTIONS
278              
279             These functions are not exported. They can be called as function (e.g.
280             C<Module::CoreList::More::is_still_core($name)> or as class method (e.g. C<<
281             Module::CoreList::More->is_still_core($name) >>.
282              
283             =head2 first_release( MODULE )
284              
285             Like Module::CoreList's version, but faster (see L</"BENCHMARK">).
286              
287             =head2 first_release_by_date( MODULE )
288              
289             Like Module::CoreList's version, but faster (see L</"BENCHMARK">).
290              
291             =head2 removed_from( MODULE )
292              
293             Like Module::CoreList's version, but faster (see L</"BENCHMARK">).
294              
295             =head2 removed_from_by_date( MODULE )
296              
297             Like Module::CoreList's version, but faster (see L</"BENCHMARK">).
298              
299             =head2 is_core( MODULE, [ MODULE_VERSION, [ PERL_VERSION ] ] )
300              
301             Like Module::CoreList's version, but faster (see L</"BENCHMARK">).
302              
303             =head2 is_still_core( MODULE, [ MODULE_VERSION, [ PERL_VERSION ] ] )
304              
305             Like C<is_core>, but will also check that from PERL_VERSION up to the latest
306             known version, MODULE has never been removed from core.
307              
308             Note/idea: could also be implemented by adding a fourth argument
309             MAX_PERL_VERSION to C<is_core>, defaulting to the latest known version.
310              
311             =head2 list_core_modules([ PERL_VERSION ]) => %modules
312              
313             List modules that are in core at specified perl release.
314              
315             =head2 list_still_core_modules([ PERL_VERSION ]) => %modules
316              
317             List modules that are (still) in core from specified perl release to the latest.
318             Keys are module names, while values are versions of said modules in specified
319             perl release.
320              
321             =head1 BENCHMARK
322              
323             Rate MC->removed_from(Foo) MC->removed_from(CGI) MCM->removed_from(Foo) MCM->removed_from(CGI)
324             MC->removed_from(Foo) 153.77+-0.42/s -- -88.3% -99.7% -99.8%
325             MC->removed_from(CGI) 1314.4+-4/s 754.8+-3.5% -- -97.7% -98.0%
326             MCM->removed_from(Foo) 57760+-280/s 37460+-210% 4294+-25% -- -11.7%
327             MCM->removed_from(CGI) 65407.3+-1.2/s 42440+-120% 4876+-15% 13.25+-0.55% --
328            
329             Rate MC->removed_from_by_date(Foo) MC->removed_from_by_date(CGI) MCM->removed_from_by_date(Foo) MCM->removed_from_by_date(CGI)
330             MC->removed_from_by_date(Foo) 151.41+-0.25/s -- -87.9% -99.7% -99.8%
331             MC->removed_from_by_date(CGI) 1252.7+-1.7/s 727.4+-1.8% -- -97.9% -98.2%
332             MCM->removed_from_by_date(Foo) 59798.3+-0.074/s 39395+-64% 4673.5+-6.5% -- -13.6%
333             MCM->removed_from_by_date(CGI) 69210+-120/s 45610+-110% 5424+-12% 15.73+-0.2% --
334            
335             Rate MC->first_release(Foo) MC->first_release(CGI) MCM->first_release(Foo) MCM->first_release(CGI)
336             MC->first_release(Foo) 154.7+-0.2/s -- -87.0% -99.7% -100.0%
337             MC->first_release(CGI) 1186.2+-2.3/s 666.8+-1.8% -- -97.6% -99.7%
338             MCM->first_release(Foo) 48641+-62/s 31342+-57% 4000.5+-9.4% -- -88.2%
339             MCM->first_release(CGI) 411020+-550/s 265590+-490% 34550+-80% 745+-1.6% --
340            
341             Rate MC->first_release_by_date(Foo) MC->first_release_by_date(CGI) MCM->first_release_by_date(Foo) MCM->first_release_by_date(CGI)
342             MC->first_release_by_date(Foo) 155.92+-0.13/s -- -82.9% -99.7% -100.0%
343             MC->first_release_by_date(CGI) 913.53+-0.71/s 485.9+-0.68% -- -98.2% -99.8%
344             MCM->first_release_by_date(Foo) 50483+-16/s 32277.9% 5426.2% -- -87.7%
345             MCM->first_release_by_date(CGI) 410590+-400/s 263230+-340% 44845+-56% 713.32+-0.83% --
346            
347             Rate MC->is_core(Foo) is_still_core(Foo) MCM->is_core(Foo)
348             MC->is_core(Foo) 155.99+-0.14/s -- -98.7% -99.3%
349             is_still_core(Foo) 11568.8+-3.6/s 7316.4% -- -50.9%
350             MCM->is_core(Foo) 23562+-96/s 15005+-63% 103.66+-0.83% --
351            
352             Rate MC->is_core(Benchmark) is_still_core(Benchmark) MCM->is_core(Benchmark)
353             MC->is_core(Benchmark) 575.3+-1.3/s -- -94.8% -97.4%
354             is_still_core(Benchmark) 11053+-13/s 1821.3+-5% -- -49.6%
355             MCM->is_core(Benchmark) 21930+-130/s 3713+-24% 98.4+-1.2% --
356            
357             Rate MC->is_core(CGI) is_still_core(CGI) MCM->is_core(CGI)
358             MC->is_core(CGI) 680.4+-3.2/s -- -93.9% -96.9%
359             is_still_core(CGI) 11098+-13/s 1531.1+-7.9% -- -49.1%
360             MCM->is_core(CGI) 21818+-32/s 3107+-16% 96.59+-0.37% --
361            
362             Rate list_still_core_modules(5.020002) list_core_modules(5.020002) list_still_core_modules(5.010001) list_core_modules(5.010001)
363             list_still_core_modules(5.020002) 267.21+-0.69/s -- -13.0% -18.6% -66.4%
364             list_core_modules(5.020002) 307.07+-0.57/s 14.92+-0.37% -- -6.5% -61.4%
365             list_still_core_modules(5.010001) 328.3+-0.53/s 22.86+-0.37% 6.91+-0.26% -- -58.7%
366             list_core_modules(5.010001) 795.53+-0.98/s 197.71+-0.85% 159.07+-0.58% 142.32+-0.49% --
367              
368             =head1 SEE ALSO
369              
370             L<Module::CoreList>
371              
372             =head1 HOMEPAGE
373              
374             Please visit the project's homepage at L<https://metacpan.org/release/Module-CoreList-More>.
375              
376             =head1 SOURCE
377              
378             Source repository is at L<https://github.com/perlancar/perl-Module-CoreList-More>.
379              
380             =head1 BUGS
381              
382             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Module-CoreList-More>
383              
384             When submitting a bug or request, please include a test-file or a
385             patch to an existing test-file that illustrates the bug or desired
386             feature.
387              
388             =head1 AUTHOR
389              
390             perlancar <perlancar@cpan.org>
391              
392             =head1 COPYRIGHT AND LICENSE
393              
394             This software is copyright (c) 2016 by perlancar@cpan.org.
395              
396             This is free software; you can redistribute it and/or modify it under
397             the same terms as the Perl 5 programming language system itself.
398              
399             =cut