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   281718 use strict;
  8         21  
  8         3246  
9 8     8   56 use warnings;
  8         18  
  8         458  
10              
11             our $VERSION = '0.05';
12              
13 8     8   47 use Exporter 'import';
  8         27  
  8         633  
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   71 no warnings 'redefine';
  8         17  
  8         1634  
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 $scalar = sentinel %args
71              
72             Returns (as an lvalue) a scalar with magic attached to it. This magic is used
73             to get the value of the scalar, or to inform of a new value being set, by
74             invoking callback functions supplied to the sentinel. Takes the following
75             named arguments:
76              
77             =over 8
78              
79             =item get => CODE
80              
81             A C reference or C method name to invoke when the value of the
82             scalar is read, to obtain its value. The value returned from this code will
83             appear as the value of the scalar.
84              
85             =item set => CODE
86              
87             A C reference or C method name to invoke when a new value for the
88             scalar is written. The code will be passed the new value as its only argument.
89              
90             =item value => SCALAR
91              
92             If no C callback is provided, this value is given as the initial value of
93             the scalar. If the scalar manages to survive longer than a single assignment,
94             its value on read will retain the last value set to it.
95              
96             =item obj => SCALAR
97              
98             Optional value to pass as the first argument into the C and C
99             callbacks. If this value is provided, then the C and C callbacks may
100             be given as direct sub references to object methods, or simply method names,
101             rather than closures that capture the referent object. This avoids the runtime
102             overhead of creating lots of small one-use closures around the object.
103              
104             =back
105              
106             =head1 MUTATION ACCESSORS
107              
108             A useful behaviour of this module is generation of mutation accessor methods
109             that automatically wrap C/C accessor/mutator pairs:
110              
111             foreach (qw( name address age height )) {
112             my $name = $_;
113              
114             no strict 'refs';
115             *$name = sub :lvalue {
116             sentinel obj => shift, get => "get_$name", set => "set_$name";
117             };
118             }
119              
120             This is especially useful for methods whose values are simple strings or
121             numbers, because they allow Perl's rich set of mutation operators to be
122             applied to the object's values.
123              
124             $obj->name =~ s/-/_/g;
125              
126             substr( $obj->address, 100 ) = "";
127              
128             $obj->age++;
129              
130             $obj->height /= 100;
131              
132             =head1 XS vs PUREPERL
133              
134             If an XS compiler is available at build time, this module is implemented using
135             XS. If not, it falls back on an implementation using a Cd scalar. A
136             pureperl installation can also be requested at build time by passing the
137             C<--pp> argument to F:
138              
139             $ perl Build.PL --pp
140             $ ./Build
141              
142             =head1 ACKNOWLEDGEMENTS
143              
144             With thanks to C, C, and others from C for
145             assisting with trickier bits of XS logic. Thanks to C for suggesting a
146             pureperl implementation for XS-challenged systems.
147              
148             =head1 AUTHOR
149              
150             Paul Evans
151              
152             =cut
153              
154             package # Hide from CPAN
155             Sentinel::PP;
156              
157             sub sentinel :lvalue
158             {
159 0     0     my %args = @_;
160 0           tie my $scalar, "Sentinel::PP", $args{value}, $args{get}, $args{set}, $args{obj};
161 0           $scalar;
162             }
163              
164 8     8   48 use constant { VALUE => 0, GET => 1, SET => 2, OBJ => 3 };
  8         12  
  8         6186  
165             sub TIESCALAR
166             {
167 0     0     my $class = shift;
168 0           bless [ @_ ], $class;
169             }
170              
171             sub FETCH
172             {
173 0     0     my $self = shift;
174 0           my $get = $self->[GET];
175 0           my $obj = $self->[OBJ];
176 0 0 0       if( defined $get and !ref $get and defined $obj ) {
    0 0        
177             # Method
178 0           return $obj->$get;
179             }
180             elsif( defined $get ) {
181 0 0         return $get->( defined $obj ? ( $obj ) : () );
182             }
183             else {
184 0           return $self->[VALUE];
185             }
186             }
187              
188             sub STORE
189             {
190 0     0     my $self = shift;
191 0           my ( $value ) = @_;
192 0           my $set = $self->[SET];
193 0           my $obj = $self->[OBJ];
194 0 0 0       if( defined $set and !ref $set and defined $obj ) {
    0 0        
195             # Method
196 0           $obj->$set( $value );
197             }
198             elsif( defined $set ) {
199 0 0         $set->( defined $obj ? ( $obj ) : (), $value );
200             }
201              
202 0           $self->[VALUE] = $value;
203             }
204              
205             0x55AA;