File Coverage

blib/lib/Mojo/Base/Tiny.pm
Criterion Covered Total %
statement 111 127 87.4
branch 58 74 78.3
condition 10 16 62.5
subroutine 52 55 94.5
pod 4 4 100.0
total 235 276 85.1


line stmt bran cond sub pod time code
1             package Mojo::Base::Tiny;
2             ## no critic (ProhibitNoStrict, ProhibitStringyEval)
3              
4 1     1   437 use strict;
  1         8  
  1         28  
5 1     1   5 use warnings;
  1         2  
  1         23  
6 1     1   589 use utf8;
  1         14  
  1         5  
7 1     1   32 use feature ':5.10';
  1         3  
  1         213  
8 1     1   430 use mro;
  1         665  
  1         3  
9              
10             # No imports because we get subclassed, a lot!
11 1     1   32 use Carp ();
  1         2  
  1         14  
12 1     1   4 use Scalar::Util ();
  1         1  
  1         13  
13 1     1   460 use Sub::Util ();
  1         310  
  1         22  
14              
15             # Only Perl 5.14+ requires it on demand
16 1     1   506 use IO::Handle ();
  1         6299  
  1         76  
17              
18             our $VERSION = '0.04';
19              
20             # Role support requires Role::Tiny 2.000001+
21             use constant ROLES =>
22 1     1   7 !!(eval { require Role::Tiny; Role::Tiny->VERSION('2.000001'); 1 });
  1         2  
  1         2  
  1         564  
  1         4217  
  1         324  
23              
24             # Protect subclasses using AUTOLOAD
25       0     sub DESTROY { }
26              
27             sub attr {
28 16     16 1 1292 my ($self, $attrs, $value, %kv) = @_;
29 16 50 66     90 return unless (my $class = ref $self || $self) && $attrs;
      33        
30              
31 16 100 100     266 Carp::croak 'Default has to be a code reference or constant value'
32             if ref $value && ref $value ne 'CODE';
33 15 100       41 Carp::croak 'Unsupported attribute option' if grep { $_ ne 'weak' } keys %kv;
  6         175  
34              
35             # Weaken
36 14 100       35 if ($kv{weak}) {
37 5         7 state %weak_names;
38 5 100       13 unless ($weak_names{$class}) {
39 3         7 my $names = $weak_names{$class} = [];
40             my $sub = sub {
41 12     12   2389 my $self = shift->next::method(@_);
        12      
        12      
        12      
42 12   100     48 ref $self->{$_} and Scalar::Util::weaken $self->{$_} for @$names;
43 12         28 return $self;
44 3         12 };
45 3         11 _monkey_patch(my $base = $class . '::_Base', 'new', $sub);
46 1     1   8 no strict 'refs';
  1         2  
  1         958  
47 3         6 unshift @{"${class}::ISA"}, $base;
  3         57  
48             }
49 5 100       14 push @{$weak_names{$class}}, ref $attrs eq 'ARRAY' ? @$attrs : $attrs;
  5         17  
50             }
51              
52 14 100       20 for my $attr (@{ref $attrs eq 'ARRAY' ? $attrs : [$attrs]}) {
  14         48  
53 15 100       164 Carp::croak qq{Attribute "$attr" invalid} unless $attr =~ /^[a-zA-Z_]\w*$/;
54              
55             # Very performance-sensitive code with lots of micro-optimizations
56 14         23 my $sub;
57 14 100       40 if ($kv{weak}) {
    100          
    100          
58 5 100       9 if (ref $value) {
59             $sub = sub {
60             return exists $_[0]{$attr}
61             ? $_[0]{$attr}
62             : (
63             ref($_[0]{$attr} = $value->($_[0]))
64             && Scalar::Util::weaken($_[0]{$attr}),
65 9 100 50 9   48 $_[0]{$attr}
    100   9      
        9      
        9      
66             ) if @_ == 1;
67 4 100       16 ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
68 4         13 $_[0];
69 3         13 };
70             }
71             else {
72             $sub = sub {
73 7 100   7   875 return $_[0]{$attr} if @_ == 1;
        7      
        7      
74 3 100       20 ref($_[0]{$attr} = $_[1]) and Scalar::Util::weaken($_[0]{$attr});
75 3         13 $_[0];
76 2         11 };
77             }
78             }
79             elsif (ref $value) {
80             $sub = sub {
81             return
82 6 100   6   1082 exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value->($_[0]))
    100   6      
        6      
83             if @_ == 1;
84 2         5 $_[0]{$attr} = $_[1];
85 2         6 $_[0];
86 2         8 };
87             }
88             elsif (defined $value) {
89             $sub = sub {
90 11 100   11   66 return exists $_[0]{$attr} ? $_[0]{$attr} : ($_[0]{$attr} = $value)
    100   11      
        11      
91             if @_ == 1;
92 2         4 $_[0]{$attr} = $_[1];
93 2         8 $_[0];
94 2         10 };
95             }
96             else {
97             $sub
98 5 100   17   29 = sub { return $_[0]{$attr} if @_ == 1; $_[0]{$attr} = $_[1]; $_[0] };
  17     17   93  
  7     17   30  
  7     17   23  
        17      
        17      
99             }
100 14         33 _monkey_patch($class, $attr, $sub);
101             }
102             }
103              
104             sub import {
105 14     14   67374 my ($class, $caller) = (shift, caller);
106 14 100       124 return unless my @flags = @_;
107              
108             # Mojo modules are strict!
109 10         151 $_->import for qw(strict warnings utf8);
110 10         375 feature->import(':5.10');
111              
112 10         37 while (my $flag = shift @flags) {
113              
114             # Base
115 13 100       176 if ($flag eq '-base') { push @flags, $class }
  3 50       10  
    50          
    50          
    100          
    50          
116              
117             # Role
118             elsif ($flag eq '-role') {
119 0         0 Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
120 0     0   0 _monkey_patch($caller, 'has', sub { attr($caller, @_) });
  0         0  
121 0 0       0 eval "package $caller; use Role::Tiny; 1" or die $@;
122             }
123              
124             # async/await
125             elsif ($flag eq '-async') {
126 0         0 Carp::croak 'async/await is only available in Mojo::Base';
127             }
128              
129             # Signatures (Perl 5.20+)
130             elsif ($flag eq '-signatures') {
131 0 0       0 Carp::croak 'Subroutine signatures require Perl 5.20+' if $] < 5.020;
132 0         0 require experimental;
133 0         0 experimental->import('signatures');
134             }
135              
136             # Module
137             elsif ($flag !~ /^-/) {
138 1     1   8 no strict 'refs';
  1         2  
  1         564  
139 8 100       85 require(_class_to_path($flag)) unless $flag->can('new');
140 8         24 push @{"${caller}::ISA"}, $flag;
  8         74  
141 8     9   36 _monkey_patch($caller, 'has', sub { attr($caller, @_) });
  9     9   74  
        9      
        9      
        9      
        9      
        9      
        9      
        9      
142             }
143              
144 0         0 elsif ($flag ne '-strict') { Carp::croak "Unsupported flag: $flag" }
145             }
146             }
147              
148             sub new {
149 12     12 1 60 my $class = shift;
150 12 50 33     105 bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class;
  0 100       0  
151             }
152              
153             sub tap {
154 8     8 1 29 my ($self, $cb) = (shift, shift);
155 8         32 $_->$cb(@_) for $self;
156 8         48 return $self;
157             }
158              
159             sub with_roles {
160 0     0 1 0 Carp::croak 'Role::Tiny 2.000001+ is required for roles' unless ROLES;
161 0         0 my ($self, @roles) = @_;
162              
163             return Role::Tiny->create_class_with_roles($self,
164 0 0       0 map { /^\+(.+)$/ ? "${self}::Role::$1" : $_ } @roles)
  0 0       0  
165             unless my $class = Scalar::Util::blessed $self;
166              
167             return Role::Tiny->apply_roles_to_object($self,
168 0 0       0 map { /^\+(.+)$/ ? "${class}::Role::$1" : $_ } @roles);
  0         0  
169             }
170              
171             # internal functions
172              
173 1     1   382 sub _class_to_path { join '.', join('/', split(/::|'/, shift)), 'pm' }
174              
175             sub _monkey_patch {
176 25     25   70 my ($class, %patch) = @_;
177 1     1   8 no strict 'refs';
  1         2  
  1         38  
178 1     1   6 no warnings 'redefine';
  1         2  
  1         171  
179 25         199 *{"${class}::$_"} = Sub::Util::set_subname("${class}::$_", $patch{$_}) for keys %patch;
  25         697  
180             }
181              
182             1;
183              
184             =encoding utf8
185              
186             =head1 NAME
187              
188             Mojo::Base::Tiny - Minimal base class for !Mojo projects
189              
190             =head1 SYNOPSIS
191              
192             package Cat;
193             use Mojo::Base::Tiny -base;
194              
195             has name => 'Nyan';
196             has ['age', 'weight'] => 4;
197              
198             package Tiger;
199             use Mojo::Base::Tiny 'Cat';
200              
201             has friend => sub { Cat->new };
202             has stripes => 42;
203              
204             package main;
205             use Mojo::Base::Tiny -strict;
206              
207             my $mew = Cat->new(name => 'Longcat');
208             say $mew->age;
209             say $mew->age(3)->weight(5)->age;
210              
211             my $rawr = Tiger->new(stripes => 38, weight => 250);
212             say $rawr->tap(sub { $_->friend->name('Tacgnol') })->weight;
213              
214             =head1 DESCRIPTION
215              
216             L is a simple base class for Perl projects with fluent
217             interfaces.
218              
219             It is nothing else than L in a single file without dependencies
220             outside the core modules (or to be correct, on Perl 5.20 and older you need
221             L 1.41). You can copy it directly to your project in all the
222             "I can't (or don't want to) install L" cases.
223              
224             # Automatically enables "strict", "warnings", "utf8" and Perl 5.10 features
225             use Mojo::Base::Tiny -strict;
226             use Mojo::Base::Tiny -base;
227             use Mojo::Base::Tiny 'SomeBaseClass';
228             use Mojo::Base::Tiny -role;
229              
230             All four forms save a lot of typing. Note that role support depends on
231             L (2.000001+).
232              
233             # use Mojo::Base::Tiny -strict;
234             use strict;
235             use warnings;
236             use utf8;
237             use feature ':5.10';
238             use mro;
239             use IO::Handle ();
240              
241             # use Mojo::Base::Tiny -base;
242             use strict;
243             use warnings;
244             use utf8;
245             use feature ':5.10';
246             use mro;
247             use IO::Handle ();
248             push @ISA, 'Mojo::Base::Tiny';
249             sub has { Mojo::Base::Tiny::attr(__PACKAGE__, @_) }
250              
251             # use Mojo::Base::Tiny 'SomeBaseClass';
252             use strict;
253             use warnings;
254             use utf8;
255             use feature ':5.10';
256             use mro;
257             use IO::Handle ();
258             require SomeBaseClass;
259             push @ISA, 'SomeBaseClass';
260             sub has { Mojo::Base::Tiny::attr(__PACKAGE__, @_) }
261              
262             # use Mojo::Base::Tiny -role;
263             use strict;
264             use warnings;
265             use utf8;
266             use feature ':5.10';
267             use mro;
268             use IO::Handle ();
269             use Role::Tiny;
270             sub has { Mojo::Base::Tiny::attr(__PACKAGE__, @_) }
271              
272             On Perl 5.20+ you can also use the C<-signatures> flag with all four forms and
273             enable support for L.
274              
275             # Also enable signatures
276             use Mojo::Base::Tiny -strict, -signatures;
277             use Mojo::Base::Tiny -base, -signatures;
278             use Mojo::Base::Tiny 'SomeBaseClass', -signatures;
279             use Mojo::Base::Tiny -role, -signatures;
280              
281             If you want to activate the C and C keywords to deal much more
282             efficiently with promises, it is finally time to move to a full L
283             installation and to use L.
284              
285             # async/await not available
286             use Mojo::Base::Tiny -strict, -async; # throws an error
287              
288             This will also disable experimental warnings on versions of Perl where this
289             feature was still experimental.
290              
291             =head1 FLUENT INTERFACES
292              
293             Fluent interfaces are a way to design object-oriented APIs around method
294             chaining to create domain-specific languages, with the goal of making the
295             readability of the source code close to written prose.
296              
297             package Duck;
298             use Mojo::Base::Tiny -base;
299              
300             has 'name';
301              
302             sub quack {
303             my $self = shift;
304             my $name = $self->name;
305             say "$name: Quack!"
306             }
307              
308             L will help you with this by having all attribute accessors created
309             with L (or L) return their invocant (C<$self>) whenever they
310             are used to assign a new attribute value.
311              
312             Duck->new->name('Donald')->quack;
313              
314             In this case the C attribute accessor is called on the object created by
315             Cnew>. It assigns a new attribute value and then returns the C
316             object, so the C method can be called on it afterwards. These method
317             chains can continue until one of the methods called does not return the C
318             object.
319              
320             =head1 FUNCTIONS
321              
322             L implements the following functions, which can be imported with
323             the C<-base> flag or by setting a base class.
324              
325             =head2 has
326              
327             has 'name';
328             has ['name1', 'name2', 'name3'];
329             has name => 'foo';
330             has name => sub {...};
331             has ['name1', 'name2', 'name3'] => 'foo';
332             has ['name1', 'name2', 'name3'] => sub {...};
333             has name => sub {...}, weak => 1;
334             has name => undef, weak => 1;
335             has ['name1', 'name2', 'name3'] => sub {...}, weak => 1;
336              
337             Create attributes for hash-based objects, just like the L method.
338              
339             =head1 METHODS
340              
341             L implements the following methods.
342              
343             =head2 attr
344              
345             $object->attr('name');
346             SubClass->attr('name');
347             SubClass->attr(['name1', 'name2', 'name3']);
348             SubClass->attr(name => 'foo');
349             SubClass->attr(name => sub {...});
350             SubClass->attr(['name1', 'name2', 'name3'] => 'foo');
351             SubClass->attr(['name1', 'name2', 'name3'] => sub {...});
352             SubClass->attr(name => sub {...}, weak => 1);
353             SubClass->attr(name => undef, weak => 1);
354             SubClass->attr(['name1', 'name2', 'name3'] => sub {...}, weak => 1);
355              
356             Create attribute accessors for hash-based objects, an array reference can be
357             used to create more than one at a time. Pass an optional second argument to set
358             a default value, it should be a constant or a callback. The callback will be
359             executed at accessor read time if there's no set value, and gets passed the
360             current instance of the object as first argument. Accessors can be chained, that
361             means they return their invocant when they are called with an argument.
362              
363             These options are currently available:
364              
365             =over 2
366              
367             =item weak
368              
369             weak => $bool
370              
371             Weaken attribute reference to avoid
372             L and memory leaks.
373              
374             =back
375              
376             =head2 new
377              
378             my $object = SubClass->new;
379             my $object = SubClass->new(name => 'value');
380             my $object = SubClass->new({name => 'value'});
381              
382             This base class provides a basic constructor for hash-based objects. You can
383             pass it either a hash or a hash reference with attribute values.
384              
385             =head2 tap
386              
387             $object = $object->tap(sub {...});
388             $object = $object->tap('some_method');
389             $object = $object->tap('some_method', @args);
390              
391             Tap into a method chain to perform operations on an object within the chain
392             (also known as a K combinator or Kestrel). The object will be the first argument
393             passed to the callback, and is also available as C<$_>. The callback's return
394             value will be ignored; instead, the object (the callback's first argument) will
395             be the return value. In this way, arbitrary code can be used within (i.e.,
396             spliced or tapped into) a chained set of object method calls.
397              
398             # Longer version
399             $object = $object->tap(sub { $_->some_method(@args) });
400              
401             # Inject side effects into a method chain
402             $object->foo('A')->tap(sub { say $_->foo })->foo('B');
403              
404             =head2 with_roles
405              
406             my $new_class = SubClass->with_roles('SubClass::Role::One');
407             my $new_class = SubClass->with_roles('+One', '+Two');
408             $object = $object->with_roles('+One', '+Two');
409              
410             Create a new class with one or more L roles. If called on a class
411             returns the new class, or if called on an object reblesses the object into the
412             new class. For roles following the naming scheme C you
413             can use the shorthand C<+RoleName>. Note that role support depends on
414             L (2.000001+).
415              
416             # Create a new class with the role "SubClass::Role::Foo" and instantiate it
417             my $new_class = SubClass->with_roles('+Foo');
418             my $object = $new_class->new;
419              
420             =head1 SEE ALSO
421              
422             L, L.
423              
424             =head1 AUTHOR
425              
426             Sebastian Riedel - C
427              
428             William Lindley - C
429              
430             Maxim Vuets - C
431              
432             Joel Berger - C
433              
434             Jan Henning Thorsen - C
435              
436             Dan Book - C
437              
438             Elmar S. Heeb - C
439              
440             Dotan Dimet - C
441              
442             Zoffix Znet - C
443              
444             Ask Bjørn Hansen - C
445              
446             Tekki (Rolf Stöckli) - C
447              
448             Mohammad S Anwar - C
449              
450             =head1 COPYRIGHT
451              
452             © 2008-2019 Sebastian Riedel and others.
453              
454             © 2019 Tekki (Rolf Stöckli).
455              
456             This program is free software, you can redistribute it and/or modify it under the terms of the Artistic License version 2.0.
457              
458             =cut