File Coverage

blib/lib/Benchmark/Featureset/SetOps.pm
Criterion Covered Total %
statement 30 168 17.8
branch 0 48 0.0
condition 0 11 0.0
subroutine 10 24 41.6
pod 1 3 33.3
total 41 254 16.1


line stmt bran cond sub pod time code
1             package Benchmark::Featureset::SetOps;
2              
3 1     1   45780 use strict;
  1         1  
  1         24  
4 1     1   3 use warnings;
  1         1  
  1         19  
5              
6 1     1   338 use Benchmark::Featureset::SetOps::Config;
  1         1  
  1         25  
7              
8 1     1   8 use Config;
  1         1  
  1         30  
9 1     1   3 use Config::Tiny;
  1         7  
  1         16  
10              
11 1     1   435 use Date::Simple;
  1         3438  
  1         31  
12              
13 1     1   370 use File::Slurper 'read_lines';
  1         9515  
  1         45  
14              
15 1     1   5 use Moo;
  1         1  
  1         4  
16              
17 1     1   659 use Text::Xslate 'mark_raw';
  1         6613  
  1         50  
18              
19 1     1   6 use Types::Standard qw/Any HashRef/;
  1         1  
  1         6  
20              
21             has html_config =>
22             (
23             default => sub{return {} },
24             is => 'rw',
25             isa => HashRef,
26             required => 0,
27             );
28              
29             has module_config =>
30             (
31             default => sub{return ''},
32             is => 'rw',
33             isa => Any,
34             required => 0,
35             );
36              
37             our $VERSION = '1.05';
38              
39             # --------------------------------------------------
40              
41             sub BUILD
42             {
43 0     0 0   my($self) = @_;
44              
45 0           $self -> html_config(Benchmark::Featureset::SetOps::Config -> new -> config);
46 0           $self -> module_config(Config::Tiny -> read('config/module.list.ini') );
47              
48             } # End of BUILD.
49              
50             # ------------------------------------------------
51              
52             sub _build_environment
53             {
54 0     0     my($self) = @_;
55              
56 0           my(@environment);
57              
58             # mark_raw() is needed because of the HTML tag .
59              
60             push @environment,
61             {left => 'Author', right => mark_raw(qq|Ron Savage|)},
62             {left => 'Date', right => Date::Simple -> today},
63             {left => 'OS', right => 'Debian V 6.0.4'},
64 0           {left => 'Perl', right => $Config{version} };
65              
66 0           return \@environment;
67             }
68             # End of _build_environment.
69              
70             # ------------------------------------------------
71              
72             sub _build_excluded_list
73             {
74 0     0     my($self, $module_config) = @_;
75 0           my($count) = 0;
76              
77 0           my($href);
78             my(@tr);
79              
80 0           push @tr, [{td => 'Name'}, {td => 'Notes'}];
81              
82 0           for my $module (sort keys %$module_config)
83             {
84 0 0         next if ($$module_config{$module}{include} eq 'Yes');
85              
86 0           $count++;
87              
88 0           ($href = $module) =~ s/::/-/g;
89              
90             # mark_raw() is needed because notes contain the HTML tag
.
91              
92             push @tr,
93             [
94             {td => mark_raw(qq|$count: $module|)},
95 0   0       {td => mark_raw($$module_config{$module}{notes} || '')},
96             ];
97             }
98              
99 0           push @tr, [{td => 'Name'}, {td => 'Notes'}];
100              
101 0           return [@tr];
102             }
103             # End of _build_excluded_list.
104              
105             # ------------------------------------------------
106              
107             sub _build_method_list
108             {
109 0     0     my($self, $module_config, $method_list, $overload) = @_;
110              
111 0           my(@name, %name);
112              
113 0           for my $module (keys %$method_list)
114             {
115 0           @name = keys %{$$method_list{$module} };
  0            
116 0           @name{@name} = (1) x @name;
117             }
118              
119 0           my(@tr, @th, @td);
120              
121 0           push @th, {td => 'Method'};
122 0           push @th, {td => $_} for sort keys %$method_list;
123              
124 0           push @tr, [@th];
125              
126 0           my($count) = 0;
127              
128 0           my($alias);
129              
130 0           for my $name (sort keys %name)
131             {
132 0           $count++;
133              
134 0           @td = ({td => "$count: $name"});
135              
136 0           for my $module (sort keys %$method_list)
137             {
138 0 0         $alias = $$overload{$module} ? $$overload{$module}{$name} ? $$overload{$module}{$name} : '' : '';
    0          
139              
140 0 0         push @td, {td => $$method_list{$module}{$name} ? $alias ? "Yes. $alias" : 'Yes' : ''};
    0          
141             }
142              
143 0           push @tr, [@td];
144             }
145              
146 0           push @tr, [@th];
147              
148 0           return [@tr];
149              
150             } # End of _build_method_list.
151              
152             # ------------------------------------------------
153              
154             sub _build_module_list
155             {
156 0     0     my($self, $module_config) = @_;
157 0           my($count) = 0;
158              
159 0           my($href);
160             my(%method_list);
161 0           my($overload, %overload);
162 0           my(@tr);
163 0           my($version);
164              
165 0           push @tr, [{td => 'Name'}, {td => 'Version'}, {td => 'Method count'}, {td => 'Notes'}];
166              
167 0           for my $module (sort keys %$module_config)
168             {
169 0 0         next if ($$module_config{$module}{include} eq 'No');
170              
171 0           $count++;
172              
173 0           ($href = $module) =~ s/::/-/g;
174 0           ($method_list{$module}, $overload) = $self -> _scan_source($module_config, $module);
175 0           $overload{$module} = {%$overload};
176 0           $version = `mversion $module`;
177              
178             push @tr,
179             [
180             {td => mark_raw(qq|$count: $module|)},
181             {td => $version},
182 0           {td => scalar keys %{$method_list{$module} } },
183 0           {td => mark_raw($$module_config{$module}{notes})},
184             ];
185             }
186              
187 0           push @tr, [{td => 'Name'}, {td => 'Version'}, {td => 'Method count'}, {td => 'Notes'}];
188              
189 0           return (\@tr, \%method_list, \%overload);
190             }
191             # End of _build_module_list.
192              
193             # ------------------------------------------------
194              
195             sub _build_purpose
196             {
197 0     0     my($self) = @_;
198              
199 0           my(@purpose);
200              
201 0           push @purpose,
202             {left => 'Array and Set modules', right => 'Method lists'},
203             {left => '"', right => 'Overloaded methods'};
204              
205 0           return \@purpose;
206              
207             } # End of _build_purpose;
208              
209             # ------------------------------------------------
210              
211             sub _build_report_generator
212             {
213 0     0     my($self) = @_;
214 0           my($module) = __PACKAGE__;
215 0           my($href) = $module;
216 0           $href =~ s/::/-/g;
217              
218 0           my(@report);
219              
220 0           push @report,
221             {left => 'Module', right => 'Version'},
222             {left => mark_raw(qq|$module|), right => $VERSION};
223              
224 0           return \@report;
225              
226             } # End of _build_report_generator;
227              
228             # ------------------------------------------------
229              
230             sub _build_templater
231             {
232 0     0     my($self, $html_config) = @_;
233              
234             return Text::Xslate -> new
235             (
236             input_layer => '',
237             path => $$html_config{template_path},
238 0           );
239              
240             } # End of _build_templater.
241              
242             # --------------------------------------------------
243              
244             sub log
245             {
246 0     0 0   my($self, $level, $s) = @_;
247 0   0       $level ||= 'debug';
248 0   0       $s ||= '';
249              
250 0           print "$level: $s\n";
251              
252             } # End of log.
253              
254             # --------------------------------------------------
255              
256             sub _process_overload_1
257             {
258 0     0     my($self, $name, $source) = @_;
259 0           my($inside_overload) = 0;
260              
261 0           my(%overload);
262              
263 0           for my $line (@$source)
264             {
265 0 0         if ($line =~ /use overload/)
266             {
267 0           $inside_overload = 1;
268              
269 0           next;
270             }
271              
272 0 0         next if (! $inside_overload);
273              
274             # The || '' is for Set::Object and Set::Toolkit.
275              
276 0 0         $overload{$2} = $1 if ($line =~ /\s+"(.+)"\s+=>\s+"(.+)"/);
277 0 0 0       $overload{$2 || ''} = $1 if ($line =~ /\s+'(.+)'\s+=>\s+\\&(.+),/);
278 0 0         $overload{$2} = $1 if ($line =~ /\s+q\((.+)\)\s+=>\s+\\&(.+),/);
279              
280 0 0         $inside_overload = 0 if ($line =~ /;/);
281             }
282              
283 0           return {%overload};
284              
285             } # End of _process_overload_1.
286              
287             # --------------------------------------------------
288              
289             sub _process_overload_2
290             {
291 0     0     my($self, $name, $source) = @_;
292 0           my($inside_overload) = 0;
293              
294 0           my(%overload);
295              
296 0           for my $line (@$source)
297             {
298 0 0         if ($line =~ /\{/)
299             {
300 0           $inside_overload = 1;
301              
302 0           next;
303             }
304              
305 0 0         next if (! $inside_overload);
306              
307 0 0         $overload{$2} = $1 if ($line =~ /\s+\*(.+)\s+=\s+\\&(.+);/);
308              
309 0 0         $inside_overload = 0 if ($line =~ /\}/);
310             }
311              
312 0           return {%overload};
313              
314             } # End of _process_overload_2.
315              
316             # --------------------------------------------------
317              
318             sub _process_overload_3
319             {
320 0     0     my($self, $name, $source) = @_;
321 0           my($inside_overload) = 0;
322              
323 0           my(@name);
324              
325 0           for my $line (@$source)
326             {
327 0 0         if ($line =~ /\@UTILS\s+=\s+qw\(/)
328             {
329 0           $inside_overload = 1;
330              
331 0           next;
332             }
333              
334 0 0         next if (! $inside_overload);
335              
336 0           push @name, $1 while ($line =~ /\s+(\w+)/g);
337              
338 0 0         $inside_overload = 0 if ($line =~ /\);/);
339             }
340              
341 0           return [@name];
342              
343             } # End of _process_overload_3.
344              
345             # ------------------------------------------------
346              
347             sub run
348             {
349 0     0 1   my($self) = @_;
350 0           my($html_config) = $self -> html_config;
351 0           my($module_config) = $self -> module_config;
352 0           my(@module_list) = $self -> _build_module_list($module_config);
353 0           my($templater) = $self -> _build_templater($html_config);
354              
355 0           print $templater -> render
356             (
357             'setops.report.tx',
358             {
359             default_css => "$$html_config{css_url}/default.css",
360             environment => $self -> _build_environment,
361             fancy_table_css => "$$html_config{css_url}/fancy.table.css",
362             method_data => $self -> _build_method_list($module_config, $module_list[1], $module_list[2]),
363             modules_excluded => $self -> _build_excluded_list($module_config),
364             modules_included => $module_list[0],
365             purpose => $self -> _build_purpose,
366             report_generator => $self -> _build_report_generator,
367             }
368             );
369              
370             } # End of run.
371              
372             # --------------------------------------------------
373              
374             sub _scan_source
375             {
376 0     0     my($self, $module_config, $name) = @_;
377 0           my($path) = `mwhere $name`;
378              
379 0           chomp $path; # :-(.
380              
381 0           my(@line) = read_lines($path);
382              
383             # 1: Process sub-classes.
384              
385 0 0         if ($$module_config{$name}{sub_classes})
386             {
387 0           for my $sub_class (split(/\s*,\s*/, $$module_config{$name}{sub_classes}) )
388             {
389 0           $path = `mwhere $sub_class`;
390              
391 0           chomp $path; # :-(.
392              
393 0           push @line, read_lines($path);
394             }
395             }
396              
397             # 2: Get the sub names.
398              
399 0           my(@name) = grep{s/^sub\s+([a-zA-Z][a-zA-Z_]+).*$/$1/; $1} @line;
  0            
  0            
400              
401 0 0 0       if ($$module_config{$name}{overload_type} && ($$module_config{$name}{overload_type} == 3) )
402             {
403             # In Object::Array::Plugin::ListMoreUtils, a list of method names
404             # is installed by transferring them from List::MoreUtils.
405              
406 0           push @name, @{$self -> _process_overload_3($name, \@line)};
  0            
407             }
408              
409 0           my(%name);
410              
411 0           @name{@name} = (1) x @name;
412              
413             # 3: Get the overloads.
414              
415 0           my($overload) = {};
416             #my($method_name) = "_process_overload_$$module_config{$name}{overload_type}";
417              
418 0 0         if ($$module_config{$name}{overload_type})
419             {
420 0 0         if ($$module_config{$name}{overload_type} == 1)
    0          
421             {
422 0           $overload = $self -> _process_overload_1($name, \@line)
423             }
424             elsif ($$module_config{$name}{overload_type} == 2)
425             {
426 0           $overload = $self -> _process_overload_2($name, \@line)
427             }
428             }
429              
430 0           return (\%name, $overload);
431              
432             } # End of _scan_source.
433              
434             # ------------------------------------------------
435              
436             1;
437              
438             =pod
439              
440             =head1 NAME
441              
442             Benchmark::Featureset::SetOps - Compare various array/set handling modules
443              
444             =head1 Synopsis
445              
446             #!/usr/bin/env perl
447              
448             use Benchmark::Featureset::SetOps;
449              
450             Benchmark::Featureset::SetOps -> new -> run;
451              
452             See scripts/setops.report.pl.
453              
454             Hint: Redirect the output of that script to your $doc_root/setops.report.html.
455              
456             A copy of the report ships in html/setops.report.html.
457              
458             L.
459              
460             =head1 Description
461              
462             L compares various array/set handling modules.
463              
464             The list of modules processed is shipped in data/module.list.ini, and can easily be edited before re-running:
465              
466             shell> scripts/copy.config.pl
467             shell> scripts/setops.report.pl
468              
469             The config stuff is explained below.
470              
471             =head1 Distributions
472              
473             This module is available as a Unix-style distro (*.tgz).
474              
475             See L
476             for help on unpacking and installing distros.
477              
478             =head1 Installation
479              
480             =head2 The Module Itself
481              
482             Install L as you would for any C module:
483              
484             Run:
485              
486             cpanm Benchmark::Featureset::SetOps
487              
488             or run:
489              
490             sudo cpan Benchmark::Featureset::SetOps
491              
492             or unpack the distro, and then either:
493              
494             perl Build.PL
495             ./Build
496             ./Build test
497             sudo ./Build install
498              
499             or:
500              
501             perl Makefile.PL
502             make (or dmake or nmake)
503             make test
504             make install
505              
506             =head2 The Configuration File
507              
508             All that remains is to tell L your values for some options.
509              
510             For that, see config/.htbenchmark.featureset.setops.conf.
511              
512             If you are using Build.PL, running Build (without parameters) will run scripts/copy.config.pl,
513             as explained next.
514              
515             If you are using Makefile.PL, running make (without parameters) will also run scripts/copy.config.pl.
516              
517             Either way, before editing the config file, ensure you run scripts/copy.config.pl. It will copy
518             the config file using L, to a directory where the run-time code in
519             L will look for it.
520              
521             shell>cd Benchmark-Featureset-SetOps-1.00
522             shell>perl scripts/copy.config.pl
523              
524             Under Debian, this directory will be $HOME/.perl/Benchmark-Featureset-SetOps/. When you
525             run copy.config.pl, it will report where it has copied the config file to.
526              
527             Check the docs for L to see what your operating system returns for a
528             call to my_dist_config().
529              
530             The point of this is that after the module is installed, the config file will be
531             easily accessible and editable without needing permission to write to the directory
532             structure in which modules are stored.
533              
534             That's why L and L are pre-requisites for this module.
535              
536             Although this is a good mechanism for modules which ship with their own config files, be advised that some
537             CPAN tester machines run tests as users who don't have home directories, resulting in test failures.
538              
539             =head1 Constructor and Initialization
540              
541             C is called as C<< my($builder) = Benchmark::Featureset::SetOps -> new(k1 => v1, k2 => v2, ...) >>.
542              
543             It returns a new object of type C.
544              
545             Key-value pairs in accepted in the parameter list (see corresponding methods for details):
546              
547             =over 4
548              
549             =item o (None as yet)
550              
551             =back
552              
553             =head1 Methods
554              
555             =head2 new()
556              
557             For use by subclasses.
558              
559             =head2 run()
560              
561             Does the real work.
562              
563             See scripts/setops.report.pl and its output html/setops.report.html.
564              
565             Hint: Redirect the output of that script to $doc_root/setops.report.html.
566              
567             =head1 FAQ
568              
569             =head2 Where is the HTML template for the report?
570              
571             Templates ship in htdocs/assets/templates/benchmark/featureset/setops/.
572              
573             See also htdocs/assets/css/benchmark/featureset/setops/.
574              
575             =head2 How did you choose the modules to review?
576              
577             I maintain (but did not write) L. I have never really liked its interface, so when I started a
578             home-grown script that Kim Ryan (author of L) and I use to compare his module with my
579             L, I wondered if there was some module more to my liking. Hence the search
580             for alternatives. Then I realized my work could benefit the Perl community if I formalized the results of this
581             search.
582              
583             Also, I have 7 modules on CPAN which use L, so I wanted a good idea of the array/set modules before
584             I decided to switch.
585              
586             =head1 Repository
587              
588             L
589              
590             =head1 See Also
591              
592             The modules compared in this package often have links to various modules, which I won't repeat here...
593              
594             The other module in this series is L.
595              
596             =head1 Machine-Readable Change Log
597              
598             The file Changes was converted into Changelog.ini by L.
599              
600             =head1 Version Numbers
601              
602             Version numbers < 1.00 represent development versions. From 1.00 up, they are production versions.
603              
604             =head1 Support
605              
606             Email the author, or log a bug on RT:
607              
608             L.
609              
610             =head1 Author
611              
612             L was written by Ron Savage Iron@savage.net.auE> in 2012.
613              
614             Home page: L.
615              
616             =head1 Copyright
617              
618             Australian copyright (c) 2012, Ron Savage.
619              
620             All Programs of mine are 'OSI Certified Open Source Software';
621             you can redistribute them and/or modify them under the terms of
622             The Artistic License, a copy of which is available at:
623             http://www.opensource.org/licenses/index.html
624              
625             =cut