File Coverage

blib/lib/Mojo/Base/Tiny.pm
Criterion Covered Total %
statement 108 124 87.1
branch 58 74 78.3
condition 10 16 62.5
subroutine 51 54 94.4
pod 4 4 100.0
total 231 272 84.9


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