File Coverage

blib/lib/Splunk/Base.pm
Criterion Covered Total %
statement 67 85 78.8
branch 21 42 50.0
condition 5 15 33.3
subroutine 25 27 92.5
pod 4 4 100.0
total 122 173 70.5


line stmt bran cond sub pod time code
1             package Splunk::Base;
2              
3 5     5   727 use strict;
  5         19  
  5         178  
4 5     5   31 use warnings;
  5         12  
  5         168  
5 5     5   2229 use utf8;
  5         74  
  5         28  
6 5     5   170 use feature ();
  5         12  
  5         88  
7              
8             # No imports because we get subclassed, a lot!
9 5     5   26 use Carp ();
  5         11  
  5         79  
10 5     5   30 use Scalar::Util ();
  5         11  
  5         78  
11              
12             # Only Perl 5.14+ requires it on demand
13 5     5   1456 use IO::Handle ();
  5         24228  
  5         301  
14              
15             # Role support requires Role::Tiny 2.000001+
16 5     5   42 use constant ROLES => !!(eval { require Role::Tiny; Role::Tiny->VERSION('2.000001'); 1 });
  5         14  
  5         11  
  5         2292  
  5         22722  
  5         937  
17              
18             # Supported on Perl 5.22+
19             my $NAME = eval { require Sub::Util; Sub::Util->can('set_subname') } || sub { $_[1] };
20              
21             # Protect subclasses using AUTOLOAD
22       0     sub DESTROY { }
23              
24             sub _monkey_patch {
25 32     32   113 my ($class, %patch) = @_;
26 5     5   53 no strict 'refs';
  5         14  
  5         179  
27 5     5   34 no warnings 'redefine';
  5         12  
  5         3361  
28 32         308 *{"${class}::$_"} = $NAME->("${class}::$_", $patch{$_}) for keys %patch;
  32         280  
29             }
30              
31             sub attr {
32 27     27 1 79 my ($self, $attrs, $value) = @_;
33 27 50 33     171 return unless (my $class = ref $self || $self) && $attrs;
      33        
34              
35 27 50 66     94 Carp::croak 'Default has to be a code reference or constant value'
36             if ref $value && ref $value ne 'CODE';
37              
38 27 50       46 for my $attr (@{ref $attrs eq 'ARRAY' ? $attrs : [$attrs]}) {
  27         97  
39 27 50       141 Carp::croak qq{Attribute "$attr" invalid} unless $attr =~ /^[a-zA-Z_]\w*$/;
40              
41             # Very performance-sensitive code with lots of micro-optimizations
42 27 100       76 if (ref $value) {
    50          
43             _monkey_patch $class, $attr, sub {
44 2 50   2   772 return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value->($_[0])) if @_ == 1;
    50   8      
        8      
45 0         0 $_[0]{$attr} = $_[1];
46 0         0 $_[0];
47 8         43 };
48             }
49             elsif (defined $value) {
50             _monkey_patch $class, $attr, sub {
51 12 100   12   84 return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value) if @_ == 1;
    100   6      
        6      
        0      
52 1         4 $_[0]{$attr} = $_[1];
53 1         3 $_[0];
54 19         86 };
55             }
56             else {
57             _monkey_patch $class, $attr,
58 0 0   6   0 sub { return $_[0]{$attr} if @_ == 1; $_[0]{$attr} = $_[1]; $_[0] };
  0         0  
  0         0  
  0         0  
59             }
60             }
61             }
62              
63             sub import {
64 12     18   80 my $class = shift;
65 12 100       203 return unless my $flag = shift;
66              
67             # Base
68 7 100 0     33 if ($flag eq '-base') { $flag = $class }
  5 50       12  
    0          
69              
70             # Strict
71 2         5 elsif ($flag eq '-strict') { $flag = undef }
72              
73             # Module
74             elsif ((my $file = $flag) && !$flag->can('new')) {
75 0         0 $file =~ s!::|'!/!g;
76 0         0 require "$file.pm";
77             }
78              
79             # ISA
80 7 100       25 if ($flag) {
81 5         14 my $caller = caller;
82 5     5   45 no strict 'refs';
  5         14  
  5         2337  
83 5         12 push @{"${caller}::ISA"}, $flag;
  5         63  
84 5     33   36 _monkey_patch $caller, 'has', sub { attr($caller, @_) };
  27         73  
85             }
86              
87             # Splunk modules are strict!
88 7         133 $_->import for qw(strict warnings utf8);
89 7         928 feature->import(':5.10');
90             }
91              
92             sub new {
93 2     35 1 154 my $class = shift;
94 2 0 33     18 bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class;
  0 50          
95             }
96              
97             sub tap {
98 0     22 1   my ($self, $cb) = (shift, shift);
99 0           $_->$cb(@_) for $self;
100 0           return $self;
101             }
102              
103             sub with_roles {
104 0     16 1   Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
105 0           my ($self, @roles) = @_;
106              
107             return Role::Tiny->create_class_with_roles($self,
108 0 0         map { /^\+(.+)$/ ? "${self}::Role::$1" : $_ } @roles)
  0 0          
109             unless my $class = Scalar::Util::blessed $self;
110              
111             return Role::Tiny->apply_roles_to_object($self,
112 0 0         map { /^\+(.+)$/ ? "${class}::Role::$1" : $_ } @roles);
  0            
113             }
114              
115             1;
116              
117             =encoding utf8
118              
119             =head1 NAME
120              
121             Splunk::Base - Minimal base class (fork of Mojo::Base)
122              
123             =head1 SYNOPSIS
124              
125             package Cat;
126             use Splunk::Base -base;
127              
128             has name => 'Nyan';
129             has ['age', 'weight'] => 4;
130              
131             package Tiger;
132             use Splunk::Base 'Cat';
133              
134             has friend => sub { Cat->new };
135             has stripes => 42;
136              
137             package main;
138             use Splunk::Base -strict;
139              
140             my $mew = Cat->new(name => 'Longcat');
141             say $mew->age;
142             say $mew->age(3)->weight(5)->age;
143              
144             my $rawr = Tiger->new(stripes => 38, weight => 250);
145             say $rawr->tap(sub { $_->friend->name('Tacgnol') })->weight;
146              
147             =head1 DESCRIPTION
148              
149             L is a simple base class for L projects with fluent
150             interfaces.
151              
152             # Automatically enables "strict", "warnings", "utf8" and Perl 5.10 features
153             use Splunk::Base -strict;
154             use Splunk::Base -base;
155             use Splunk::Base 'SomeBaseClass';
156              
157             All three forms save a lot of typing.
158              
159             # use Splunk::Base -strict;
160             use strict;
161             use warnings;
162             use utf8;
163             use feature ':5.10';
164             use IO::Handle ();
165              
166             # use Splunk::Base -base;
167             use strict;
168             use warnings;
169             use utf8;
170             use feature ':5.10';
171             use IO::Handle ();
172             push @ISA, 'Splunk::Base';
173             sub has { Splunk::Base::attr(__PACKAGE__, @_) }
174              
175             # use Splunk::Base 'SomeBaseClass';
176             use strict;
177             use warnings;
178             use utf8;
179             use feature ':5.10';
180             use IO::Handle ();
181             require SomeBaseClass;
182             push @ISA, 'SomeBaseClass';
183             sub has { Splunk::Base::attr(__PACKAGE__, @_) }
184              
185             =head1 FUNCTIONS
186              
187             L implements the following functions, which can be imported with
188             the C<-base> flag or by setting a base class.
189              
190             =head2 has
191              
192             has 'name';
193             has ['name1', 'name2', 'name3'];
194             has name => 'foo';
195             has name => sub {...};
196             has ['name1', 'name2', 'name3'] => 'foo';
197             has ['name1', 'name2', 'name3'] => sub {...};
198              
199             Create attributes for hash-based objects, just like the L method.
200              
201             =head1 METHODS
202              
203             L implements the following methods.
204              
205             =head2 attr
206              
207             $object->attr('name');
208             SubClass->attr('name');
209             SubClass->attr(['name1', 'name2', 'name3']);
210             SubClass->attr(name => 'foo');
211             SubClass->attr(name => sub {...});
212             SubClass->attr(['name1', 'name2', 'name3'] => 'foo');
213             SubClass->attr(['name1', 'name2', 'name3'] => sub {...});
214              
215             Create attribute accessors for hash-based objects, an array reference can be
216             used to create more than one at a time. Pass an optional second argument to set
217             a default value, it should be a constant or a callback. The callback will be
218             executed at accessor read time if there's no set value, and gets passed the
219             current instance of the object as first argument. Accessors can be chained, that
220             means they return their invocant when they are called with an argument.
221              
222             =head2 new
223              
224             my $object = SubClass->new;
225             my $object = SubClass->new(name => 'value');
226             my $object = SubClass->new({name => 'value'});
227              
228             This base class provides a basic constructor for hash-based objects. You can
229             pass it either a hash or a hash reference with attribute values.
230              
231             =head2 tap
232              
233             $object = $object->tap(sub {...});
234             $object = $object->tap('some_method');
235             $object = $object->tap('some_method', @args);
236              
237             Tap into a method chain to perform operations on an object within the chain
238             (also known as a K combinator or Kestrel). The object will be the first argument
239             passed to the callback, and is also available as C<$_>. The callback's return
240             value will be ignored; instead, the object (the callback's first argument) will
241             be the return value. In this way, arbitrary code can be used within (i.e.,
242             spliced or tapped into) a chained set of object method calls.
243              
244             # Longer version
245             $object = $object->tap(sub { $_->some_method(@args) });
246              
247             # Inject side effects into a method chain
248             $object->foo('A')->tap(sub { say $_->foo })->foo('B');
249              
250             =head2 with_roles
251              
252             my $new_class = SubClass->with_roles('SubClass::Role::One');
253             my $new_class = SubClass->with_roles('+One', '+Two');
254             $object = $object->with_roles('+One', '+Two');
255              
256             Create a new class with one or more L roles. If called on a class
257             returns the new class, or if called on an object reblesses the object into the
258             new class. For roles following the naming scheme C you
259             can use the shorthand C<+RoleName>. Note that role support depends on
260             L (2.000001+).
261              
262             # Create a new class with the role "SubClass::Role::Foo" and instantiate it
263             my $new_class = SubClass->with_roles('+Foo');
264             my $object = $new_class->new;
265              
266             =head1 SEE ALSO
267              
268             L, L, L
269              
270             =cut