File Coverage

blib/lib/List/OrderBy.pm
Criterion Covered Total %
statement 53 72 73.6
branch 1 2 50.0
condition 3 6 50.0
subroutine 11 25 44.0
pod 8 8 100.0
total 76 113 67.2


line stmt bran cond sub pod time code
1             package List::OrderBy;
2 2     2   67623 use strict;
  2         5  
  2         78  
3 2     2   10 use warnings;
  2         3  
  2         56  
4 2     2   10 use Exporter;
  2         7  
  2         104  
5            
6 2     2   14 use vars qw{ $VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS };
  2         4  
  2         394  
7            
8             BEGIN {
9 2     2   39 @ISA = qw(Exporter);
10 2         12 %EXPORT_TAGS = ( 'all' => [ qw(
11             order_by then_by
12             order_cmp_by then_cmp_by
13             order_by_desc then_by_desc
14             order_cmp_by_desc then_cmp_by_desc
15             ) ] );
16            
17 2         3 @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
  2         8  
18 2         7 @EXPORT = @EXPORT_OK;
19 2         891 $VERSION = '0.2';
20             };
21            
22             sub order_by(&;@) {
23 3     3 1 8 List::OrderBy::Container->new(sub { $_[0] <=> $_[1] }, @_)->get();
  1     1   22  
24             }
25            
26             sub order_by_desc(&;@) {
27 0     0 1 0 List::OrderBy::Container->new(sub { $_[1] <=> $_[0] }, @_)->get();
  0     0   0  
28             }
29            
30             sub order_cmp_by_desc(&;@) {
31 0     0 1 0 List::OrderBy::Container->new(sub { $_[1] cmp $_[0] }, @_)->get();
  0     0   0  
32             }
33            
34             sub order_cmp_by(&;@) {
35 0     0 1 0 List::OrderBy::Container->new(sub { $_[0] cmp $_[1] }, @_)->get();
  0     0   0  
36             }
37            
38             sub then_by(&;@) {
39 0     0 1 0 List::OrderBy::Container->new(sub { $_[0] <=> $_[1] }, @_)
  0     0   0  
40             }
41            
42             sub then_by_desc(&;@) {
43 0     0 1 0 List::OrderBy::Container->new(sub { $_[1] <=> $_[0] }, @_)
  0     0   0  
44             }
45            
46             sub then_cmp_by(&;@) {
47 0     0 1 0 List::OrderBy::Container->new(sub { $_[0] cmp $_[1] }, @_)
  0     0   0  
48             }
49            
50             sub then_cmp_by_desc(&;@) {
51 0     0 1 0 List::OrderBy::Container->new(sub { $_[1] cmp $_[0] }, @_)
  0     0   0  
52             }
53            
54             package List::OrderBy::Container;
55 2     2   11 use strict;
  2         4  
  2         68  
56 2     2   9 use warnings;
  2         4  
  2         772  
57            
58             sub new {
59 1     1   3 my $class = shift;
60 1         2 my $key_comparer = shift;
61 1         2 my $key_extractor = shift;
62 1         3 my @list = @_;
63 1         3 my $self = bless { }, $class;
64            
65             # Chained then_by calls are merged into a single object
66 1 50 33     13 if (@list and UNIVERSAL::isa($list[0], __PACKAGE__)) {
67            
68             # Copy reference to the list and the existing key extractors
69 0         0 $self->{key_extractors} = [ @{ $list[0]->{key_extractors} } ];
  0         0  
70 0         0 $self->{key_comparers} = [ @{ $list[0]->{key_comparers} } ];
  0         0  
71 0         0 $self->{list} = $list[0]->{list};
72            
73             } else {
74 1         7 $self->{list} = \@list;
75             }
76            
77             # A sequence `order_by { ... } then_by { ... }` is evaluated from
78             # the right to the left, and to make the first element the first
79             # extractor to be applied, elements are unshifted into the list.
80            
81 1         2 unshift @{ $self->{key_extractors} }, $key_extractor;
  1         3  
82 1         1 unshift @{ $self->{key_comparers} }, $key_comparer;
  1         2  
83            
84 1         6 $self;
85             }
86            
87             sub get {
88 1     1   2 my $merged = shift;
89            
90             # Extract all keys
91 1         2 my @keys = map {
92 1         3 my $code = $_;
93            
94             # When a sub is used as key extractor instead of a code block,
95             # authors would expect the data passed in as argument, so this
96             # does both, pass through $_ and pass through the parameter.
97            
98 1         1 [ map { scalar $code->($_); } @{ $merged->{list} } ]
  3         11  
  1         2  
99            
100 1         2 } @{ $merged->{key_extractors} };
101            
102 3         2 my @sorted_indices = sort {
103 1         7 my $compare = 0;
104 3   66     17 for (my $ix = 0; !$compare and $ix <= $#keys; ++$ix) {
105 3         9 $compare = $merged->{key_comparers}[$ix]
106             ->($keys[$ix]->[$a], $keys[$ix]->[$b]);
107             }
108 3         7 $compare;
109 1         7 } 0 .. $#{ $merged->{list} };
110            
111 1         2 return map { $merged->{list}[$_] } @sorted_indices;
  3         8  
112             }
113            
114             1;
115            
116             __END__