File Coverage

blib/lib/Sentinel.pm
Criterion Covered Total %
statement 15 35 42.8
branch 0 12 0.0
condition 0 12 0.0
subroutine 5 9 55.5
pod n/a
total 20 68 29.4


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2011-2012 -- leonerd@leonerd.org.uk
5              
6             package Sentinel;
7              
8 8     8   487080 use strict;
  8         75  
  8         248  
9 8     8   44 use warnings;
  8         16  
  8         328  
10              
11             our $VERSION = '0.06';
12              
13 8     8   98 use Exporter 'import';
  8         14  
  8         645  
14             our @EXPORT = qw( sentinel );
15              
16             eval {
17             require XSLoader;
18             XSLoader::load( __PACKAGE__, $VERSION );
19             } or do {
20             # pureperl fallback
21 8     8   54 no warnings 'redefine';
  8         18  
  8         1326  
22             *sentinel = \&Sentinel::PP::sentinel;
23             };
24              
25             =head1 NAME
26              
27             C - create lightweight SCALARs with get/set callbacks
28              
29             =head1 SYNOPSIS
30              
31             package Some::Class;
32              
33             use Sentinel;
34              
35             sub foo :lvalue
36             {
37             my $self = shift;
38             sentinel get => sub { return $self->get_foo },
39             set => sub { $self->set_foo( $_[0] ) };
40             }
41              
42             sub bar :lvalue
43             {
44             my $self = shift;
45             sentinel value => $self->get_bar,
46             set => sub { $self->set_bar( $_[0] ) };
47             }
48              
49             sub splot :lvalue
50             {
51             sentinel obj => shift, get => \&get_splot, set => \&set_splot;
52             }
53              
54             sub wibble :lvalue
55             {
56             sentinel obj => shift, get => "get_wibble", set => "set_wibble";
57             }
58              
59             =head1 DESCRIPTION
60              
61             This module provides a single lvalue function, C, which yields a
62             scalar that invoke callbacks to get or set its value. Primarily this is useful
63             to create lvalue object accessors or other functions, to invoke actual code
64             when a new value is set, rather than simply updating a scalar variable.
65              
66             =cut
67              
68             =head1 FUNCTIONS
69              
70             =head2 sentinel
71              
72             $scalar = sentinel %args
73              
74             Returns (as an lvalue) a scalar with magic attached to it. This magic is used
75             to get the value of the scalar, or to inform of a new value being set, by
76             invoking callback functions supplied to the sentinel. Takes the following
77             named arguments:
78              
79             =over 8
80              
81             =item get => CODE
82              
83             A C reference or C method name to invoke when the value of the
84             scalar is read, to obtain its value. The value returned from this code will
85             appear as the value of the scalar.
86              
87             =item set => CODE
88              
89             A C reference or C method name to invoke when a new value for the
90             scalar is written. The code will be passed the new value as its only argument.
91              
92             =item value => SCALAR
93              
94             If no C callback is provided, this value is given as the initial value of
95             the scalar. If the scalar manages to survive longer than a single assignment,
96             its value on read will retain the last value set to it.
97              
98             =item obj => SCALAR
99              
100             Optional value to pass as the first argument into the C and C
101             callbacks. If this value is provided, then the C and C callbacks may
102             be given as direct sub references to object methods, or simply method names,
103             rather than closures that capture the referent object. This avoids the runtime
104             overhead of creating lots of small one-use closures around the object.
105              
106             =back
107              
108             =head1 MUTATION ACCESSORS
109              
110             A useful behaviour of this module is generation of mutation accessor methods
111             that automatically wrap C/C accessor/mutator pairs:
112              
113             foreach (qw( name address age height )) {
114             my $name = $_;
115              
116             no strict 'refs';
117             *$name = sub :lvalue {
118             sentinel obj => shift, get => "get_$name", set => "set_$name";
119             };
120             }
121              
122             This is especially useful for methods whose values are simple strings or
123             numbers, because they allow Perl's rich set of mutation operators to be
124             applied to the object's values.
125              
126             $obj->name =~ s/-/_/g;
127              
128             substr( $obj->address, 100 ) = "";
129              
130             $obj->age++;
131              
132             $obj->height /= 100;
133              
134             =head1 XS vs PUREPERL
135              
136             If an XS compiler is available at build time, this module is implemented using
137             XS. If not, it falls back on an implementation using a Cd scalar. A
138             pureperl installation can also be requested at build time by passing the
139             C<--pp> argument to F:
140              
141             $ perl Build.PL --pp
142             $ ./Build
143              
144             =head1 ACKNOWLEDGEMENTS
145              
146             With thanks to C, C, and others from C for
147             assisting with trickier bits of XS logic. Thanks to C for suggesting a
148             pureperl implementation for XS-challenged systems.
149              
150             =head1 AUTHOR
151              
152             Paul Evans
153              
154             =cut
155              
156             package # Hide from CPAN
157             Sentinel::PP;
158              
159             sub sentinel :lvalue
160             {
161 0     0     my %args = @_;
162 0           tie my $scalar, "Sentinel::PP", $args{value}, $args{get}, $args{set}, $args{obj};
163 0           $scalar;
164             }
165              
166 8     8   58 use constant { VALUE => 0, GET => 1, SET => 2, OBJ => 3 };
  8         13  
  8         2887  
167             sub TIESCALAR
168             {
169 0     0     my $class = shift;
170 0           bless [ @_ ], $class;
171             }
172              
173             sub FETCH
174             {
175 0     0     my $self = shift;
176 0           my $get = $self->[GET];
177 0           my $obj = $self->[OBJ];
178 0 0 0       if( defined $get and !ref $get and defined $obj ) {
    0 0        
179             # Method
180 0           return $obj->$get;
181             }
182             elsif( defined $get ) {
183 0 0         return $get->( defined $obj ? ( $obj ) : () );
184             }
185             else {
186 0           return $self->[VALUE];
187             }
188             }
189              
190             sub STORE
191             {
192 0     0     my $self = shift;
193 0           my ( $value ) = @_;
194 0           my $set = $self->[SET];
195 0           my $obj = $self->[OBJ];
196 0 0 0       if( defined $set and !ref $set and defined $obj ) {
    0 0        
197             # Method
198 0           $obj->$set( $value );
199             }
200             elsif( defined $set ) {
201 0 0         $set->( defined $obj ? ( $obj ) : (), $value );
202             }
203              
204 0           $self->[VALUE] = $value;
205             }
206              
207             0x55AA;