File Coverage

lib/Class/Forward.pm
Criterion Covered Total %
statement 82 85 96.4
branch 49 54 90.7
condition 11 18 61.1
subroutine 9 11 81.8
pod 6 8 75.0
total 157 176 89.2


line stmt bran cond sub pod time code
1             # ABSTRACT: Namespace Dispatch and Resolution
2              
3             package Class::Forward;
4              
5 1     1   40397 use strict;
  1         2  
  1         41  
6 1     1   5 use warnings;
  1         2  
  1         48  
7              
8             our $VERSION = '0.100006'; # VERSION
9              
10 1     1   6 use Exporter ();
  1         6  
  1         1663  
11              
12             our @ISA = qw(Exporter);
13             our @EXPORT = qw(clsf clsr);
14             our %CACHE = ();
15              
16              
17              
18             sub clsf {
19 17     17 1 980 return Class::Forward->new(namespace => (caller)[0])->forward(@_);
20             }
21              
22              
23             sub clsr {
24 24     24 1 351 return Class::Forward->new(namespace => (caller)[0])->reverse(@_);
25             }
26              
27              
28             sub new {
29 41     41 1 105 my $self = bless {}, (shift);
30              
31 41 50       148 my %args = @_ ? @_ : ();
32              
33 41 50       168 $self->{namespace} = $args{namespace} if defined $args{namespace};
34              
35 41         136 return $self;
36             }
37              
38              
39             sub namespace {
40 65     65 1 79 my ($self, $namespace) = @_;
41              
42 65 50       119 $self->{namespace} = $namespace if $namespace;
43              
44 65         212 return $self->{namespace};
45             }
46              
47              
48             sub forward {
49 41     41 1 80 my ($self, $shorthand, @arguments) = @_;
50              
51 41   50     69 my $namespace = $self->namespace() || (caller)[0] || 'main';
52              
53 41         51 my $backspace;
54             my $methods;
55 0         0 my $myspace;
56              
57 41         45 my $class = '';
58 41         59 my @class = ();
59 41         45 my @methods = ();
60              
61 41         49 my $CACHE_KEY = $shorthand;
62 41         57 $CACHE_KEY .= "\@$namespace";
63              
64 41   66     727 my $DATA = $CACHE{$CACHE_KEY} ||= do {
65              
66 20 100       44 if ($shorthand) {
67              
68             # capture path relativity notation
69              
70 17 100       72 $backspace = $1 if $shorthand =~ s/^((\.{1,2}\/){1,})//;
71              
72 17 100 66     99 $backspace = $1 if $shorthand =~ s/^(\/+)// && !$backspace;
73              
74             # capture method call notation
75              
76 17 100       47 ($methods) = $1 if $shorthand =~ s/((\.\w+){1,})$//;
77              
78             # convert shorthand to package notation
79              
80 8         27 $myspace = join "::", map {
81 17 100       78 /_/ ? join '', map { ucfirst lc } split /_/, $_ : ucfirst $_
  19         78  
82             } split /(?:::|\-|\/)/, $shorthand;
83              
84 17 100       38 if ($backspace) {
85 12 100       38 unless ($backspace =~ /^\/$/) {
86 5         16 @class = split /::/, $namespace;
87 5 100       16 if ($backspace =~ /^\/\/$/) {
88 2         7 while (@class > 1) {
89 2         5 pop @class;
90             }
91             }
92             else {
93 3 100       13 unless ($backspace =~ /^\.\/$/) {
94 1         6 my @backspaces = $backspace =~ /\.\.\//g;
95 1         3 for (@backspaces) {
96 3 50       13 pop @class unless @class == 1;
97             }
98             }
99             }
100             }
101             }
102             else {
103 5         9 push @class, $namespace;
104             }
105              
106 17 100       51 push @class, split /::/, $myspace if $myspace;
107              
108 17 100       44 push @methods, grep /\w+/, split /\./, $methods if $methods;
109              
110             }
111              
112 20 100       45 push @class, $namespace if !@class;
113              
114             # build class namespace
115              
116 20 100       55 my $class = @class > 1 ? join('::', @class) : $class[0];
117              
118             # leverage @INC to validate and possibly correct any case issues
119              
120 20         32 my $file = "$class.pm";
121 20         45 $file =~ s/::/\//g;
122              
123 20 100       128 unless ($INC{$file}) {
124              
125             # don't assume $#!+
126              
127 16         1271 my @matches = grep(/^$file/i, keys %INC);
128              
129 16 100       114 if (@matches == 1) {
130              
131 1         3 $class = $matches[0];
132 1         18 $class =~ s/\//::/g;
133 1         5 $class =~ s/\.pm$//;
134              
135             }
136              
137             }
138              
139             # cache the results
140 20         136 $CACHE{$CACHE_KEY} = {
141             'CLASS' => $class,
142             'METHODS' => [@methods]
143             };
144              
145             };
146              
147 41         82 $class = $DATA->{'CLASS'};
148 41 100 66     179 @methods = @{$DATA->{'METHODS'}} if $DATA && ! @methods;
  39         84  
149              
150             # return result of method call(s) or class name
151              
152 41 100       82 if (@methods) {
153              
154 2         7 for (my $i = 0 ; $i < @methods ; $i++) {
155              
156 3         10 my $method = $methods[$i];
157              
158 3 100       24 $class =
159             $i == $#methods ? $class->$method(@arguments) : $class->$method;
160              
161             }
162              
163 2         21 return $class;
164              
165             }
166              
167             else {
168              
169 39         154 return $class;
170              
171             }
172             }
173              
174             sub forward_lookup {
175 0     0 0 0 goto \&forward
176             }
177              
178              
179             sub reverse {
180 24     24 1 44 my ($self, $shorthand, $offset, $delimiter) = @_;
181              
182 24 50 0     43 $self->namespace((caller)[0] || 'main') unless $self->namespace;
183              
184 24 100       69 $shorthand =~ s/((\.\w+){1,})$// if $shorthand;
185 24   100     110 $delimiter ||= '/';
186              
187 24         48 my $result = $self->forward($shorthand);
188 24         81 my @pieces = split /::/, $result;
189              
190 24 100 66     82 if (defined $offset and $offset >= 0) {
191 9 100       15 if ($offset == 0) {
192 4         12 unshift @pieces, '';
193             }
194             else {
195 5         21 shift @pieces for (1..$offset);
196             }
197             }
198             else {
199 15         28 unshift @pieces, '';
200             }
201              
202             return join $delimiter,
203 24 100       47 map { if ($_) { s/([a-z])([A-Z])/$1_\l$2/g; lc } } @pieces;
  61         131  
  42         93  
  42         218  
204             }
205              
206             sub reverse_lookup {
207 0     0 0   goto \&reverse
208             }
209              
210              
211             1;
212              
213             __END__