File Coverage

blib/lib/Meta/Builder/Base.pm
Criterion Covered Total %
statement 141 143 98.6
branch 28 44 63.6
condition 4 12 33.3
subroutine 34 34 100.0
pod 22 25 88.0
total 229 258 88.7


line stmt bran cond sub pod time code
1             package Meta::Builder::Base;
2 2     2   11 use strict;
  2         4  
  2         71  
3 2     2   16 use warnings;
  2         4  
  2         51  
4              
5 2     2   10 use Meta::Builder::Util;
  2         4  
  2         11  
6 2     2   10 use Carp qw/croak carp/;
  2         3  
  2         5663  
7              
8             sub new {
9 5     5 1 2664 my $class = shift;
10 5         13 my ( $package, %metrics ) = @_;
11 5         14 my $meta = $class->meta_meta;
12 5         15 my $self = bless( [ $package ], $class );
13              
14 5         9 for my $metric ( keys %{ $meta->{metrics} }) {
  5         17  
15 11         22 my $idx = $meta->{metrics}->{$metric};
16 11   33     43 $self->[$idx] = $metrics{$metric}
17             || $meta->{generators}->[$idx]->();
18             }
19              
20             inject(
21             $package,
22             ($meta->{accessor} || croak "$class does not have an accessor set."),
23 1     1   891 sub { $self }
24 5   33     40 );
25              
26 5 50       31 $self->init( %metrics ) if $self->can( 'init' );
27              
28 5         15 return $self;
29             }
30              
31             sub meta_meta {
32 58     58 1 129 my $class = shift;
33 58 100       325 return $class->_meta_meta
34             if $class->can( '_meta_meta' );
35              
36 2         8 my $meta = { index => 1 };
37 2     56   19 inject( $class, "_meta_meta", sub { $meta });
  56         169  
38 2         9 return $meta;
39             }
40              
41 1     1 1 5 sub package { shift->[0] }
42              
43             sub set_accessor {
44 2     2 1 4 my $class = shift;
45 2         18 ($class->meta_meta->{accessor}) = @_;
46             }
47              
48             sub add_hash_metric {
49 3     3 1 47 my $class = shift;
50 3         9 my ( $metric, %actions ) = @_;
51 3         32 $class->add_metric(
52             $metric,
53             \&gen_hash,
54             add => \&default_hash_add,
55             get => \&default_hash_get,
56             has => \&default_hash_has,
57             clear => \&default_hash_clear,
58             pull => \&default_hash_pull,
59             merge => \&default_hash_merge,
60             %actions,
61             );
62             }
63              
64             sub add_lists_metric {
65 3     3 1 46 my $class = shift;
66 3         10 my ( $metric, %actions ) = @_;
67 3         24 $class->add_metric(
68             $metric,
69             \&gen_hash,
70             push => \&default_list_push,
71             get => \&default_list_get,
72             has => \&default_list_has,
73             clear => \&default_list_clear,
74             pull => \&default_list_pull,
75             merge => \&default_list_merge,
76             %actions,
77             );
78             }
79              
80             sub add_metric {
81 8     8 1 739 my $class = shift;
82 8         40 my ( $metric, $generator, %actions ) = @_;
83 8         21 my $meta = $class->meta_meta;
84 8         20 my $index = $meta->{index}++;
85              
86 8 50       154 croak "Already tracking metric '$metric'"
87             if $meta->{metrics}->{$metric};
88              
89 8         19 $meta->{metrics}->{$metric} = $index;
90 8         21 $meta->{generators}->[$index] = $generator;
91 8         25 $meta->{indexes}->{$index} = $metric;
92              
93 8     58   43 inject( $class, $metric, sub { shift->[$index] });
  58         2135  
94 8         43 $class->add_action( $metric, %actions );
95             }
96              
97             sub add_action {
98 10     10 1 35 my $class = shift;
99 10         33 my ( $metric, %actions ) = @_;
100             $class->_add_action( $metric, $_, $actions{ $_ })
101 10         55 for keys %actions;
102             }
103              
104             sub _add_action {
105 39     39   49 my $class = shift;
106 39         67 my ( $metric, $action, $code ) = @_;
107 39 50 33     259 croak "You must specify a metric, an action name, and a coderef"
      33        
108             unless $metric && $action && $code;
109              
110 39         81 my $meta = $class->meta_meta;
111 39         95 my $name = $class->action_method_name( $metric, $action );
112              
113             inject( $class, $name, sub {
114 36     36   152 my $self = shift;
115 36         52 my $args = \@_;
116 36 100       217 $_->( $self, $self->$metric, $metric, $action, @$args )
117 36         45 for @{ $meta->{before}->{$name} || [] };
118 35         105 my @out = $code->( $self, $self->$metric, $metric, $action, @$args );
119 34 100       182 $_->( $self, $self->$metric, $metric, $action, @$args )
120 34         67 for @{ $meta->{after}->{$name} || [] };
121 33 100       253 return @out ? (@out > 1 ? @out : $out[0]) : ();
    50          
122 39         220 });
123             }
124              
125             sub action_method_name {
126 75     75 1 98 my $class = shift;
127 75         107 my ( $metric, $action ) = @_;
128 75         217 return "$metric\_$action";
129             }
130              
131             sub hook_before {
132 1     1 1 775 my $class = shift;
133 1         3 my ( $metric, $action, $code ) = @_;
134 1         6 my $name = $class->action_method_name( $metric, $action );
135 1         2 push @{ $class->meta_meta->{before}->{$name} } => $code;
  1         4  
136             }
137              
138             sub hook_after {
139 1     1 1 10 my $class = shift;
140 1         3 my ( $metric, $action, $code ) = @_;
141 1         3 my $name = $class->action_method_name( $metric, $action );
142 1         2 push @{ $class->meta_meta->{after}->{$name} } => $code;
  1         3  
143             }
144              
145 10     10 1 55 sub gen_hash { {} }
146              
147             sub default_hash_add {
148 9     9 1 12 my $self = shift;
149 9         25 my ( $data, $metric, $action, $item, @value ) = @_;
150 9         23 my $name = $self->action_method_name( $metric, $action );
151 9 50       24 croak "$name() called without anything to add"
152             unless $item;
153              
154 9 50       20 croak "$name('$item') called without a value to add"
155             unless @value;
156              
157 9 50       22 croak "'$item' already added for metric $metric"
158             if $data->{$item};
159              
160 9         39 ($data->{$item}) = @value;
161             }
162              
163             sub default_hash_get {
164 3     3 1 7 my $self = shift;
165 3         6 my ( $data, $metric, $action, $item ) = @_;
166 3         9 my $name = $self->action_method_name( $metric, $action );
167 3 50       10 croak "$name() called without anything to get"
168             unless $item;
169              
170             # Prevent autovivication
171 3 50       15 return exists $data->{$item}
172             ? $data->{$item}
173             : undef;
174             }
175              
176             sub default_hash_has {
177 8     8 1 12 my $self = shift;
178 8         17 my ( $data, $metric, $action, $item ) = @_;
179 8         199 my $name = $self->action_method_name( $metric, $action );
180 8 50       21 croak "$name() called without anything to find"
181             unless $item;
182 8 100       35 return exists $data->{$item} ? 1 : 0;
183             }
184              
185             sub default_hash_clear {
186 3     3 1 4 my $self = shift;
187 3         6 my ( $data, $metric, $action, $item ) = @_;
188 3         8 my $name = $self->action_method_name( $metric, $action );
189 3 50       10 croak "$name() called without anything to clear"
190             unless $item;
191 3         8 delete $data->{$item};
192 3         8 return 1;
193             }
194              
195             sub default_hash_pull {
196 1     1 1 3 my $self = shift;
197 1         3 my ( $data, $metric, $action, $item ) = @_;
198 1         35 my $name = $self->action_method_name( $metric, $action );
199 1 50       5 croak "$name() called without anything to pull"
200             unless $item;
201 1         3 return delete $data->{$item};
202             }
203              
204             sub default_hash_merge {
205 2     2 0 4 my $self = shift;
206 2         5 my ( $data, $metric, $action, $merge ) = @_;
207 2         5 for my $key ( keys %$merge ) {
208 3 100       41 croak "$key is defined for $metric in both meta-objects"
209             if $data->{$key};
210 2         10 $data->{$key} = $merge->{$key};
211             }
212             }
213              
214             sub default_list_push {
215 6     6 1 11 my $self = shift;
216 6         21 my ( $data, $metric, $action, $item, @values ) = @_;
217 6         18 my $name = $self->action_method_name( $metric, $action );
218 6 50       22 croak "$name() called without an item to which data should be pushed"
219             unless $item;
220              
221 6 50       16 croak "$name('$item') called without values to push"
222             unless @values;
223              
224 6         10 push @{$data->{$item}} => @values;
  6         81  
225             }
226              
227             sub default_list_get {
228 2     2 1 14 my $data = default_hash_get(@_);
229 2 50       9 return $data ? @$data : ();
230             }
231              
232             sub default_list_has {
233 6     6 1 17 default_hash_has( @_ );
234             }
235              
236             sub default_list_clear {
237 2     2 1 8 default_hash_clear( @_ );
238             }
239              
240             sub default_list_pull {
241 1     1 1 4 my @out = default_list_get( @_ );
242 1         4 default_list_clear( @_ );
243 1         4 return @out;
244             }
245              
246             sub default_list_merge {
247 2     2 0 3 my $self = shift;
248 2         3 my ( $data, $metric, $action, $merge ) = @_;
249 2         10 for my $key ( keys %$merge ) {
250 1         2 push @{ $data->{$key} } => @{ $merge->{$key} };
  1         2  
  1         7  
251             }
252             }
253              
254             sub merge {
255 2     2 0 53 my $self = shift;
256 2         4 my ( $merge ) = @_;
257 2         2 for my $metric ( keys %{ $self->meta_meta->{ metrics }}) {
  2         7  
258 4         11 my $mergesub = $self->action_method_name( $metric, 'merge' );
259 4 50       15 unless( $self->can( $mergesub )) {
260 0         0 carp "Cannot merge metric '$metric', define a 'merge' action for it.";
261 0         0 next;
262             }
263 4         11 $self->$mergesub( $merge->$metric );
264             }
265             }
266              
267             1;
268              
269             __END__
270              
271             =head1 NAME
272              
273             Meta::Builder::Base - Base class for Meta::Builder Meta Objects.
274              
275             =head1 DESCRIPTION
276              
277             Base class for all L<Meta::Builder> Meta objects. This is where the methods
278             used to define new metrics and actions live. This class allows for the creation
279             of dynamic meta objects.
280              
281             =head1 SYNOPSIS
282              
283             My/Meta.pm:
284              
285             package My::Meta;
286             use strict;
287             use warnings;
288              
289             use base 'Meta::Builder::Base';
290              
291             # Name the accessor that will be defined in the class that uses the meta object
292             # It is used to retrieve the classes meta object.
293             __PACKAGE__->set_accessor( "mymeta" );
294              
295             # Add a metric with two actions
296             __PACKAGE__->add_metric(
297             mymetric => sub { [] },
298             pop => sub {
299             my $self = shift;
300             my ( $data ) = @_;
301             pop @$data;
302             },
303             push => sub {
304             my $self = shift;
305             my ( $data, $metric, $action, @args ) = @_;
306             push @$data => @args;
307             }
308             );
309              
310             # Add an additional action to the metric
311             __PACKAGE__->add_action( 'mymetric', get_ref => sub { shift });
312              
313             # Add some predefined metric types + actions
314             __PACKAGE__->add_hash_metric( 'my_hashmetric' );
315             __PACKAGE__->add_lists_metric( 'my_listsmetric' );
316              
317             My.pm:
318              
319             package My;
320             use strict;
321             use warnings;
322              
323             use My::Meta;
324              
325             My::Meta->new( __PACKAGE__ );
326              
327             # My::Meta defines mymeta() as the accessor we use to get our meta object.
328             # this is the ONLY way to get the meta object for this class.
329              
330             mymeta()->mymetric_push( "some data" );
331             mymeta()->my_hashmetric_add( key => 'value' );
332             mymeta()->my_listsmetric_push( list => qw/valueA valueB/ );
333              
334             # It works fine as an object/class method as well.
335             __PACKAGE__->mymeta->do_thing(...);
336              
337             ...;
338              
339             =head1 PACKAGE METRIC
340              
341             Whenever you create a new instance of a meta-object you must provide the name
342             of the package to which the meta-object belongs. The 'package' metric will be
343             set to this package name, and can be retirved via the 'package' method:
344             C<$meta->package()>.
345              
346             =head1 HASH METRICS
347              
348             Hash metrics are metrics that hold key/value pairs. A hash metric is defined
349             using either the C<hash_metric()> function, or the C<$meta->add_hash_metric()>
350             method. The following actions are automatically defined for hash metrics:
351              
352             =over 4
353              
354             =item $meta->add_METRIC( $key, $value )
355              
356             Add a key/value pair to the metric. Will throw an exception if the metric
357             already has a value for the specified key.
358              
359             =item $value = $meta->get_METRIC( $key )
360              
361             Get the value for a specified key.
362              
363             =item $bool = $meta->has_METRIC( $key )
364              
365             Check that the metric has the specified key defined.
366              
367             =item $meta->clear_METRIC( $key )
368              
369             Clear the specified key/value pair in the metric. (returns nothing)
370              
371             =item $value = $meta->pull_METRIC( $key )
372              
373             Get the value for the specified key, then clear the pair form the metric.
374              
375             =back
376              
377             =head1 LISTS METRICS
378              
379             =over 4
380              
381             =item $meta->push_METRIC( $key, @values )
382              
383             Push values into the specified list for the given metric.
384              
385             =item @values = $meta->get_METRIC( $key )
386              
387             Get the values for a specified key.
388              
389             =item $bool = $meta->has_METRIC( $key )
390              
391             Check that the metric has the specified list.
392              
393             =item $meta->clear_METRIC( $key )
394              
395             Clear the specified list in the metric. (returns nothing)
396              
397             =item @values = $meta->pull_METRIC( $key )
398              
399             Get the values for the specified list in the metric, then clear the list.
400              
401             =back
402              
403             =head1 CLASS METHODS
404              
405             =over 4
406              
407             =item $meta = $class->new( $package, %metrics )
408              
409             Create a new instance of the meta-class, and apply it to $package.
410              
411             =item $metadata = $class->meta_meta()
412              
413             Get the meta data for the meta-class itself. (The meta-class is build using
414             meta-data)
415              
416             =item $new_hashref = $class->gen_hash()
417              
418             Generate a new empty hashref.
419              
420             =item $name = $class->action_method_name( $metric, $action )
421              
422             Generate the name of the method for the given metric and action. Override this
423             if you do not like the METRIC_ACTION() method names.
424              
425             =back
426              
427             =head1 OBJECT METHODS
428              
429             =over 4
430              
431             =item $package = $meta->package()
432              
433             Get the name of the package to which this meta-class applies.
434              
435             =item $meta->set_accessor( $name )
436              
437             Set the accessor that is used to retrieve the meta-object from the class to
438             which it applies.
439              
440             =item $meta->add_hash_metric( $metric, %actions )
441              
442             Add a hash metric (see L</"HASH METRICS">).
443              
444             %actions should contain C<action =<gt> sub {...}> pairs for constructing
445             actions (See add_action()).
446              
447             =item $meta->add_lists_metric( $metric, %actions )
448              
449             Add a lists metric (see L</"LISTS METRICS">)
450              
451             %actions should contain C<action =<gt> sub {...}> pairs for constructing
452             actions (See add_action()).
453              
454             =item $meta->add_metric( $metric, \&generator, %actions )
455              
456             Add a custom metric. The second argument should be a sub that generates a
457             default value for the metric.
458              
459             %actions should contain C<action =<gt> sub {...}> pairs for constructing
460             actions (See add_action()).
461              
462             =item $meta->add_action( $metric, $action => sub { ... } )
463              
464             Add an action for the specified metric. See L</"ACTION AND HOOK METHODS"> for
465             details on how to write an action coderef.
466              
467             =item $meta->hook_before( $metric, $action, sub { ... })
468              
469             Add a hook for the specified metric. See L</"ACTION AND HOOK METHODS"> for
470             details on how to write a hook coderef.
471              
472             =item $meta->hook_after( $metric, $action, sub { ... })
473              
474             Add a hook for the specified metric. See L</"ACTION AND HOOK METHODS"> for
475             details on how to write a hook coderef.
476              
477             =back
478              
479             =head1 ACTION AND HOOK METHODS
480              
481             sub {
482             my $self = shift;
483             my ( $data, $metric, $action, @args ) = @_;
484             ...;
485             }
486              
487             Action and hook methods are called when someone calls
488             C<$meta-<gt>metric_action(...)>. First all before hooks will be called, the the
489             action itself, and finally the after hooks will be called. All methods in the
490             chain get the exact same unaltered arguments. Only the main action sub can
491             return anything.
492              
493             Arguments are:
494              
495             =over 4
496              
497             =item 0: $self
498              
499             These are methods, so the first argument is the meta object itself.
500              
501             =item 1: $data
502              
503             This is the data structure stored for the metric. This is the same as calling
504             $meta->metric()
505              
506             =item 2: $metric
507              
508             Name of the metric
509              
510             =item 3: $action
511              
512             Name of the action
513              
514             =item 4+: @args
515              
516             Arguments that metric_action() was called with.
517              
518             =back
519              
520             =head1 DEFAULT ACTION METHODS
521              
522             There are the default action methods used by hashmetrics and listsmetrics.
523              
524             =over 4
525              
526             =item $meta->default_hash_add( $data, $metric, $action, $item, $value )
527              
528             =item $value = $meta->default_hash_get( $data, $metric, $action, $item )
529              
530             =item $bool = $meta->default_hash_has( $data, $metric, $action, $item )
531              
532             =item $meta->default_hash_clear( $data, $metric, $action, $item )
533              
534             =item $value = $meta->default_hash_pull( $data, $metric, $action, $item )
535              
536             =item $meta->default_list_push( $data, $metric, $action, $item, @values )
537              
538             =item @values = $meta->default_list_get( $data, $metric, $action, $item )
539              
540             =item $bool = $meta->default_list_has( $data, $metric, $action, $item )
541              
542             =item $meta->default_list_clear( $data, $metric, $action, $item )
543              
544             =item @values = $meta->default_list_pull( $data, $metric, $action, $item )
545              
546             =back
547              
548             =head1 AUTHORS
549              
550             Chad Granum L<exodist7@gmail.com>
551              
552             =head1 COPYRIGHT
553              
554             Copyright (C) 2010 Chad Granum
555              
556             Meta-Builder is free software; Standard perl licence.
557              
558             Meta-Builder is distributed in the hope that it will be useful, but WITHOUT
559             ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
560             FOR A PARTICULAR PURPOSE. See the license for more details.