File Coverage

blib/lib/Scalar/Properties.pm
Criterion Covered Total %
statement 160 175 91.4
branch 41 70 58.5
condition 12 14 85.7
subroutine 57 62 91.9
pod 54 54 100.0
total 324 375 86.4


line stmt bran cond sub pod time code
1 1     1   1060 use 5.008;
  1         3  
  1         37  
2 1     1   4 use strict;
  1         2  
  1         26  
3 1     1   5 use warnings;
  1         1  
  1         190  
4              
5             package Scalar::Properties;
6             our $VERSION = '1.100860';
7             # ABSTRACT: Run-time properties on scalar variables
8             use overload
9 1         28 q{""} => \&value,
10             bool => \&is_true,
11             '+' => \&plus,
12             '-' => \&minus,
13             '*' => \×,
14             '/' => \÷,
15             '%' => \&modulo,
16             '**' => \&exp,
17             '<=>' => \&numcmp,
18             'cmp' => \&cmp,
19              
20             # the following would be autogenerated from 'cmp', but
21             # we want to make the methods available explicitly, along
22             # with case-insensitive versions
23             'eq' => \&eq,
24             'ne' => \&ne,
25             'lt' => \<,
26             'gt' => \>,
27             'le' => \&le,
28 1     1   1670 'ge' => \≥
  1         1165  
29              
30             sub import {
31 1     1   7 my $pkg = shift;
32 1         2 my @defs = qw/integer float binary q qr/;
33 1         2 my @req;
34 1 50       6 @_ = ':all' unless @_;
35 1         5 for my $key (@_) {
36 1 50       5 if ($key eq ':all') {
37 1         4 @req = @defs;
38             } else {
39 0 0       0 die __PACKAGE__ . " does not export '$key'"
40             unless grep /^$key$/ => @defs;
41 0         0 push @req => $key;
42             }
43             }
44 1         1 overload::constant map { $_ => \&handle } @req;
  5         13  
45              
46             # also manually export some routines
47 1         61 my $callpkg = caller(1);
48 1     1   377 no strict 'refs';
  1         2  
  1         1734  
49 1         3 *{"$callpkg\::$_"} = \&{"$pkg\::$_"} for qw/pass_on passed_on get_pass_on/;
  3         27  
  3         8  
50             }
51              
52             # object's hash keys that aren't properties (apart from those starting with
53             # and underscore, which are private anyway)
54             our %NON_PROPS = map { $_ => 1 } our @NON_PROPS = qw/true/;
55              
56             # property propagation
57             sub pass_on {
58 1     1 1 12 our %PASS_ON = map { $_ => 1 } our @PASS_ON = @_;
  1         5  
59             }
60 9     9 1 11 sub passed_on { our %PASS_ON; exists $PASS_ON{ +shift } }
  9         44  
61 1     1 1 28 sub get_pass_on { our @PASS_ON }
62              
63             sub get_props {
64              
65             # get a list of the value's properties
66 466     466 1 513 my $self = shift;
67 466         443 our %NON_PROPS;
68 466   100     1058 return grep { !(/^_/ || exists $NON_PROPS{$_}) } keys %$self;
  1882         7124  
69             }
70              
71             sub del_prop {
72              
73             # delete one or more properties
74 1     1 1 2 my $self = shift;
75 1         2 our %NON_PROPS;
76 1         9 for my $prop (@_) {
77 1 50       3 die "$prop is private, not a property"
78             if substr($prop, 0, 1) eq '_';
79 1 50       3 die "$prop cannot be deleted" if exists $NON_PROPS{$prop};
80 1         2 delete $self->{$prop};
81             }
82             }
83              
84             sub del_all_props {
85 1     1 1 138 my $self = shift;
86 1         4 my @props = $self->get_props;
87 1         6 delete $self->{$_} for @props;
88             }
89              
90             sub handle {
91              
92             # create a new overloaded object
93 563     563 1 1096 my ($orig, $interp, $context, $sub, @prop) = @_;
94 563 100       2782 my $self = bless {
95             _value => $orig,
96             _interp => $interp,
97             _context => $context,
98             true => ($orig) ? 1 : 0,
99             },
100             __PACKAGE__;
101              
102             # propagate properties marked as such via pass_on from
103             # participating overloaded values passed in @prop
104 563         971 for my $val (grep { ref $_ eq __PACKAGE__ } @prop) {
  668         1428  
105 461         810 for my $prop ($val->get_props) {
106 8 100       14 $self->{$prop} = $val->{$prop} if passed_on($prop);
107             }
108             }
109 563         6533 return $self;
110             }
111              
112             sub create {
113              
114             # take a value and a list of participating values and create
115             # a new object from them by filling in the gaps that handle()
116             # expects with defaults. As seen from handle(), the participating
117             # values (i.e., the values that the first arg was derived from)
118             # are passed so that properties can be properly propagated
119 355     355 1 635 my ($val, @props) = @_;
120 355     0   1258 handle($val, $val, '', sub { }, @props);
  0         0  
121             }
122              
123             # call this as a sub, not a method as it also takes unblessed scalars
124             # anything not of this package is stringified to give any potential
125             # other overloading a chance to get at it's actual value
126             sub value {
127              
128             # my $v = ref $_[0] eq __PACKAGE__ ? $_[0]->{_value} : "$_[0]";
129             # $v =~ s/\\n/\n/gs; # no idea why newlines become literal '\n'
130 1415 100   1415 1 4726 my $v = ref $_[0] eq __PACKAGE__ ? $_[0]->{_interp} : "$_[0]";
131 1415         30334 return $v;
132             }
133              
134             # ==================== Generated methods ====================
135             # Generate some string, numeric and boolean methods
136             sub gen_meth {
137 3     3 1 4 my $template = shift;
138 3         11 while (my ($name, $op) = splice(@_, 0, 2)) {
139 31         279 (my $code = $template) =~ s/NAME/$name/g;
140 31         260 $code =~ s/OP/$op/g;
141 31 50   3 1 7727 eval $code;
  3 0   1 1 44  
  1 50   0 1 4  
  1 0   2 1 3  
  1 100   0 1 4  
  0 0   53 1 0  
  0 50   2 1 0  
  0 50   0 1 0  
  2 50   5 1 98  
  2 50   3 1 9  
  2 100   3 1 5  
  0 0   2 1 0  
  0 50   4 1 0  
  0 100   2 1 0  
  53 50   2 1 308  
  53 50   5 1 141  
  53     3 1 128  
  2     3 1 51  
  0     3 1 0  
  0     2 1 0  
  0     8 1 0  
  5     0 1 16  
  5     3 1 13  
  5     2 1 14  
  3     55 1 12  
  3     3 1 10  
  3     160 1 10  
  3     1 1 10  
  2     2 1 8  
  4     3 1 51  
  2     2 1 8  
  2         8  
  5         11  
  5         12  
  5         14  
  3         12  
  3         60  
  3         9  
  3         10  
  3         8  
  2         9  
  8         513  
  8         24  
  8         22  
  0         0  
  0         0  
  0         0  
  3         9  
  3         8  
  3         9  
  2         9  
  55         397  
  55         131  
  55         126  
  3         10  
  160         4351  
  160         339  
  160         360  
  1         4  
  2         43  
  2         7  
  2         5  
  3         71  
  2         7  
142 31 50       140 die "Internal error: $@" if $@;
143             }
144             }
145             my $binop = 'sub NAME {
146             my($n, $m) = @_[0,1];
147             ($m, $n) = ($n, $m) if($_[2]);
148             create(value($n) OP value($m), $n, $m)
149             }';
150             gen_meth $binop, qw!
151             plus +
152             minus -
153             times *
154             divide /
155             modulo %
156             exp **
157             numcmp <=>
158             cmp cmp
159             eq eq
160             ne ne
161             lt lt
162             gt gt
163             le le
164             ge ge
165             concat .
166             append .
167             !;
168              
169             # needs 'CORE::lc', otherwise 'Ambiguous call resolved as CORE::lc()'
170             my $bool_i = 'sub NAME {
171             create( CORE::lc(value($_[0])) OP CORE::lc(value($_[1])), @_[0,1] )
172             }';
173             gen_meth $bool_i, qw!
174             eqi eq
175             nei ne
176             lti lt
177             gti gt
178             lei le
179             gei ge
180             !;
181             my $func = 'sub NAME {
182             create(OP(value($_[0])), $_[0])
183             }';
184             gen_meth $func, qw!
185             abs abs
186             length CORE::length
187             size CORE::length
188             uc uc
189             ucfirst ucfirst
190             lc lc
191             lcfirst lcfirst
192             hex hex
193             oct oct
194             !;
195              
196             # ==================== Miscellaneous Numeric methods ====================
197 4     4 1 120 sub zero { create($_[0] == 0, $_[0]) }
198              
199             # ==================== Miscellaneous Boolean methods ====================
200 126     126 1 847 sub is_true { $_[0]->{true} }
201 2     2 1 8 sub is_false { !$_[0]->{true} }
202              
203             sub true {
204 7     7 1 17 my $self = shift;
205 7 100       18 $self->{true} = @_ ? shift : 1;
206 7         22 return $self;
207             }
208 3     3 1 9 sub false { $_[0]->true(0) }
209              
210             # ==================== Miscellaneous String methods ====================
211 2     2 1 5 sub reverse { create(scalar reverse(value($_[0])), $_[0]) }
212 1     1 1 12 sub swapcase { my $s = shift; $s =~ y/A-Za-z/a-zA-Z/; return create($s) }
  1         3  
  1         3  
213              
214             # $foo->split(/PATTERN/, LIMIT)
215             sub split {
216 5     5 1 24 my ($orig, $pat, $limit) = @_;
217 5   100     19 $limit ||= 0;
218 5 100       30 $pat = qr/\s+/ unless ref($pat) eq 'Regexp';
219              
220             # The following should work:
221             # map { create($_, $orig) } split $pat => value($orig), $limit;
222             # But there seems to be a bug in split
223             # (cf. p5p: 'Bug report: split splits on wrong pattern')
224 5         8 my @el;
225 5         420 eval '@el = split $pat => value($orig), $limit;';
226 5 50       22 die $@ if $@;
227 5         8 return map { create($_, $orig) } @el;
  11         22  
228             }
229              
230             # ==================== Code-execution methods ====================
231             sub times_do {
232 2     2 1 20 my ($self, $sub) = @_;
233 2 50       7 die 'times_do() method expected a coderef' unless ref $sub eq 'CODE';
234 2         6 for my $i (1 .. $self) {
235 8         24 $sub->($i);
236             }
237             }
238              
239             sub do_upto_step {
240 3     3 1 6 my ($self, $limit, $step, $sub) = @_;
241 3 50       10 die 'expected last arg to be a coderef'
242             unless ref $sub eq 'CODE';
243              
244             # for my $i ($self..$limit) { $sub->($i); }
245 3         5 my $i = $self;
246 3         111 while ($i <= $limit) {
247 8         36 $sub->($i);
248 8         245 $i += $step;
249             }
250             }
251              
252             sub do_downto_step {
253 3     3 1 6 my ($self, $limit, $step, $sub) = @_;
254 3 50       10 die 'expected last arg to be a coderef'
255             unless ref $sub eq 'CODE';
256 3         6 my $i = $self;
257 3         96 while ($i >= $limit) {
258 5         22 $sub->($i);
259 5         135 $i -= $step;
260             }
261             }
262 2     2 1 8 sub do_upto { do_upto_step($_[0], $_[1], 1, $_[2]) }
263 2     2 1 5 sub do_downto { do_downto_step($_[0], $_[1], 1, $_[2]) }
264              
265             sub AUTOLOAD {
266 29     29   296 my $self = shift;
267 29         161 (my $prop = our $AUTOLOAD) =~ s/.*:://;
268 29 50 33     156 return if $prop eq 'DESTROY' || substr($prop, 0, 1) eq '_';
269              
270             # $x->is_foo or $x->has_foo will return true if 'foo' is
271             # a hash key with a true value
272 29 100 100     74 return defined $self->{ substr($prop, 4) }
273             && $self->{ substr($prop, 4) }
274             if substr($prop, 0, 4) eq 'has_';
275 26 100 100     86 return defined $self->{ substr($prop, 3) }
276             && $self->{ substr($prop, 3) }
277             if substr($prop, 0, 3) eq 'is_';
278 18 100       39 if (@_) {
279 12         21 $self->{$prop} = shift;
280 12         30 return $self;
281             }
282 6         24 return $self->{$prop};
283             }
284             1;
285              
286              
287             __END__