File Coverage

blib/lib/Object/Adhoc.pm
Criterion Covered Total %
statement 55 70 78.5
branch 6 16 37.5
condition 6 10 60.0
subroutine 12 14 85.7
pod 2 2 100.0
total 81 112 72.3


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