File Coverage

blib/lib/Object/Adhoc.pm
Criterion Covered Total %
statement 50 65 76.9
branch 6 16 37.5
condition 6 10 60.0
subroutine 11 13 84.6
pod 2 2 100.0
total 75 106 70.7


line stmt bran cond sub pod time code
1 1     1   70366 use 5.008;
  1         5  
2 1     1   6 use strict;
  1         2  
  1         20  
3 1     1   4 use warnings;
  1         2  
  1         55  
4              
5             package Object::Adhoc;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.005';
9              
10 1     1   8 use Digest::MD5 qw( md5_hex );
  1         2  
  1         73  
11 1     1   542 use Exporter::Shiny qw( object make_class );
  1         4702  
  1         6  
12             our @EXPORT = qw( object );
13              
14             BEGIN {
15             *USE_XS = eval 'use Class::XSAccessor 1.19 (); 1'
16             ? sub () { !!1 }
17 1 50   1   162 : sub () { !!0 };
  1     1   541  
  1         2579  
  1         12  
18             };
19              
20             BEGIN {
21 1     1   689 require Hash::Util;
22             *lock_ref_keys = 'Hash::Util'->can('lock_ref_keys')
23 1   50     3729 || sub { return; };
24             };
25              
26             our $RESERVED_REGEXP;
27              
28             # Yes, you can push extra methods onto this array if you need to,
29             # but if you do that, then set $RESERVED_REGEXP to undef so that
30             # make_class will rebuild it!
31             #
32             our @RESERVED_METHODS = qw(
33             import unimport
34             DESTROY
35             AUTOLOAD
36             isa DOES does can VERSION
37             meta new
38             );
39             #
40             # Note that tie-related stuff isn't on the list of reserved methods
41             # because people using those names isn't likely to cause any actual
42             # harm.
43              
44             sub object {
45 3     3 1 1628 my ($data, $keys) = @_;
46 3   100     18 $keys ||= [ keys %$data ];
47 3         12 bless $data, make_class($keys);
48 3         15 lock_ref_keys($data, @$keys);
49 3         103 $data;
50             }
51              
52             my %made;
53             sub make_class {
54 3     3 1 4 my ($keys) = @_;
55 3         10 my $joined = join "|", sort(@$keys);
56 3 100       13 return $made{$joined} if $made{$joined};
57            
58 2         16 my $class = sprintf('%s::__ANON__::%s', __PACKAGE__, md5_hex($joined));
59            
60 2         10 my %getters = map(+($_ => $_), @$keys);
61 2 50       13 my %predicates = map(+((/^_/?"_has$_":"has_$_")=> $_), @$keys);
62            
63 2   66     8 $RESERVED_REGEXP ||= do {
64 1         9 my $re = join "|", map quotemeta($_), @RESERVED_METHODS;
65 1         71 qr/\A(?:$re)\z/;
66             };
67            
68 2         5 for my $key (@$keys) {
69 3 50       6 if (exists $predicates{$key}) {
70 0         0 delete $predicates{$key};
71 0         0 require Carp;
72 0         0 Carp::carp("Ambiguous method '$key' is getter, not predicate");
73             }
74 3 50 33     42 if ($key !~ /^[^\W0-9]\w*$/s or $key =~ $RESERVED_REGEXP) {
75 0         0 require Carp;
76 0         0 Carp::carp("Key '$key' would be bad method name, not generating methods");
77 0 0       0 my $predicate = ($key =~ /^_/) ? "_has$key" : "has_$key";
78 0         0 delete $getters{$key};
79 0         0 delete $predicates{$predicate};
80             }
81             }
82            
83 2         4 if (USE_XS) {
84 2         12 'Class::XSAccessor'->import(
85             class => $class,
86             getters => \%getters,
87             exists_predicates => \%predicates,
88             );
89             }
90             else {
91             require B;
92             my $code = "package $class;\n";
93             while (my ($predicate, $key) = each %predicates) {
94             my $qkey = B::perlstring($key);
95             $code .= "sub $predicate :method { &Object::Adhoc::_usage if \@_ > 1; CORE::exists \$_[0]{$qkey} }\n";
96             }
97             while (my ($getter, $key) = each %getters) {
98             my $qkey = B::perlstring($key);
99             $code .= "sub $getter :method { &Object::Adhoc::_usage if \@_ > 1; \$_[0]{$qkey} }\n";
100             }
101             $code .= "1;\n";
102             eval($code) or do { require Carp; Carp::croak($@) };
103             }
104            
105 2         537 do {
106 1     1   9 no strict 'refs';
  1         2  
  1         268  
107 2         5 *{"$class\::DOES"} = \&_DOES;
  2         10  
108 2         5 *{"$class\::does"} = \&_DOES;
  2         8  
109 2         4 *{"$class\::VERSION"} = \$VERSION;
  2         7  
110             };
111            
112 2         10 $made{$joined} = $class;
113             }
114              
115             sub _usage {
116 0     0     my $caller = (caller(1))[3];
117 0           require Carp;
118 0           local $Carp::CarpLevel = 1 + $Carp::CarpLevel;
119 0           Carp::croak("Usage: $caller\(self)"); # mimic XS usage message
120             }
121              
122             sub _DOES {
123 0 0   0     return !!1 if $_[1] eq __PACKAGE__;
124 0 0         return !!1 if $_[1] eq 'HASH';
125 0           shift->isa(@_);
126             }
127              
128             1;
129              
130             __END__