File Coverage

lib/MooseX/Extended.pm
Criterion Covered Total %
statement 81 86 94.1
branch 17 22 77.2
condition 4 6 66.6
subroutine 17 17 100.0
pod 0 1 0.0
total 119 132 90.1


line stmt bran cond sub pod time code
1              
2             # ABSTRACT: Extend Moose with safe defaults and useful features
3              
4             use 5.20.0;
5 19     19   3820812 use warnings;
  19         176  
6 19     19   91  
  19         33  
  19         467  
7             use Moose::Exporter;
8 19     19   7821 use Moose ();
  19         1778892  
  19         125  
9 19     19   9034 use MooseX::StrictConstructor ();
  19         4656171  
  19         588  
10 19     19   8911 use mro ();
  19         477497  
  19         450  
11 19     19   135 use namespace::autoclean ();
  19         38  
  19         253  
12 19     19   84 use Module::Load 'load';
  19         33  
  19         327  
13 19     19   1420 use MooseX::Extended::Core qw(
  19         3081  
  19         193  
14 19         1782 _assert_import_list_is_valid
15             _debug
16             _disabled_warnings
17             _enabled_features
18             _our_import
19             _our_init_meta
20             field
21             param
22             );
23 19     19   9659 use feature _enabled_features();
  19         67  
24 19     19   126 no warnings _disabled_warnings();
  19         33  
  19         79  
25 19     19   113 use B::Hooks::AtRuntime 'after_runtime';
  19         34  
  19         69  
26 19     19   9153 use Import::Into;
  19         25034  
  19         166  
27 19     19   6250  
  19         1143  
  19         11804  
28             our $VERSION = '0.33';
29              
30             my ( $class, %args ) = @_;
31             my @caller = caller(0);
32 47     47   98856 $args{_import_type} = 'class';
33 47         212 $args{_caller_eval} = ( $caller[1] =~ /^\(eval/ ); # https://github.com/Ovid/moosex-extended/pull/34
34 47         576 my $target_class = _assert_import_list_is_valid( $class, \%args );
35 47         163 my @with_meta = grep { not $args{excludes}{$_} } qw(field param);
36 47         178 if (@with_meta) {
37 46         113 @with_meta = ( with_meta => [@with_meta] );
  92         270  
38 46 50       144 }
39 46         149 my ( $import, undef, undef ) = Moose::Exporter->setup_import_methods(
40             @with_meta,
41 46         414 install => [qw/unimport/],
42             also => ['Moose'],
43             );
44             _our_import( $class, $import, $target_class );
45             }
46 46         51527  
47             # Internal method setting up exports. No public
48             # documentation by design
49             Moose->init_meta(%params);
50             _our_init_meta( $class, \&_apply_default_features, %params );
51 46     46 0 3921 }
  46         106  
  46         201  
  46         63  
52 46         2248  
53 46         199717 # XXX we don't actually use the $params here, even though we need it for
54             # MooseX::Extended::Role. But we need to declare it in the signature to make
55             # this code work
56             if ( my $types = $config->{types} ) {
57             _debug("$for_class: importing types '@$types'");
58             MooseX::Extended::Types->import::into( $for_class, @$types );
59 46     46   122 }
  46         74  
  46         70  
  46         71  
  46         78  
60 46 100       172  
61 15         156 MooseX::StrictConstructor->import( { into => $for_class } ) unless $config->{excludes}{StrictConstructor};
62 15         179 Carp->import::into($for_class) unless $config->{excludes}{carp};
63             namespace::autoclean->import::into($for_class) unless $config->{excludes}{autoclean};
64              
65 46 100       136659 unless ( $config->{excludes}{immutable} or $config->{_caller_eval} ) { # https://github.com/Ovid/moosex-extended/pull/34
66 46 100       207932  
67 46 50       12461 # after_runtime is loaded too late under the debugger
68             eval {
69 46 100 66     9583 load B::Hooks::AtRuntime, 'after_runtime';
70             after_runtime {
71             $for_class->meta->make_immutable;
72             if ( $config->{debug} ) {
73 24         130  
74             # they're doing debug on a class-by-class basis, so
75 22     22   68435 # turn this off after the class compiles
76 22 50       412050 $MooseX::Extended::Debug = 0;
77             }
78             };
79             1;
80 0         0 } or do {
81             my $error = $@;
82 24         7441 warn
83 24         1481 "Could not load 'B::Hooks::AtRuntime': $error. You class is not immutable. You can `use MooseX::Extended excludes => ['immutable'];` to suppress this warning.";
84 24 50       53 };
85 0         0 }
86 0         0 unless ( $config->{excludes}{true} or $config->{_caller_eval} ) { # https://github.com/Ovid/moosex-extended/pull/34
87             eval {
88             load true;
89             true->import::into($for_class); # no need for `1` at the end of the module
90 46 100 66     342 1;
91             } or do {
92 24         2159 my $error = $@;
93 24         52350 warn
94 24         37047 "Could not load 'true': $error. Your class must end in a true value. You can `use MooseX::Extended excludes => ['true'];` to suppress this warning.";
95 24 50       99 };
96 0         0 }
97 0         0  
98             # If we never use multiple inheritance, this should not be needed.
99             mro::set_mro( $for_class, 'c3' )
100             unless $config->{excludes}{c3};
101              
102             feature->import( _enabled_features() );
103             warnings->unimport(_disabled_warnings);
104 46 100       324 }
105             1;
106 46         174  
107 46         259  
108             =pod
109              
110             =encoding UTF-8
111              
112             =head1 NAME
113              
114             MooseX::Extended - Extend Moose with safe defaults and useful features
115              
116             =head1 VERSION
117              
118             version 0.33
119              
120             =head1 SYNOPSIS
121              
122             package My::Names {
123             use MooseX::Extended types => [qw(compile Num NonEmptyStr Str PositiveInt ArrayRef)];
124             use List::Util 'sum';
125              
126             # the distinction between `param` and `field` makes it easier to
127             # see which are available to `new`
128             param _name => ( isa => NonEmptyStr, init_arg => 'name' );
129             param title => ( isa => Str, required => 0 );
130              
131             # forbidden in the constructor
132             field created => ( isa => PositiveInt, default => sub {time} );
133              
134             sub name ($self) {
135             my $title = $self->title;
136             my $name = $self->_name;
137             return $title ? "$title $name" : $name;
138             }
139              
140             sub add ( $self, $args ) {
141             state $check = compile( ArrayRef [ Num, 1 ] ); # at least one number
142             ($args) = $check->($args);
143             return sum( $args->@* );
144             }
145              
146             sub warnit ($self) {
147             carp("this is a warning");
148             }
149             }
150              
151             =head1 DESCRIPTION
152              
153             This module is B<BETA> code. It's feature-complete for release and has no
154             known bugs. We believe it's ready for production, but make no promises.
155              
156             This is a quick overview. See L<MooseX::Extended::Manual::Tutorial> for more
157             information.
158              
159             This class attempts to create a safer version of Moose that defaults to
160             read-only attributes and is easier to read and write.
161              
162             It tries to bring some of the lessons learned from L<the Corinna
163             project|https://github.com/Ovid/Cor>, while acknowledging that you can't
164             always get what you want (such as true encapsulation and true methods).
165              
166             This:
167              
168             package My::Class {
169             use MooseX::Extended;
170              
171             ... your code here
172             }
173              
174             Is sort of the equivalent to:
175              
176             package My::Class {
177             use v5.20.0;
178             use Moose;
179             use MooseX::StrictConstructor;
180             use feature qw( signatures postderef postderef_qq );
181             no warnings qw( experimental::signatures experimental::postderef );
182             use namespace::autoclean;
183             use Carp;
184             use mro 'c3';
185              
186             ... your code here
187              
188             __PACKAGE__->meta->make_immutable;
189             }
190             1;
191              
192             It also exports two functions which are similar to Moose C<has>: C<param> and
193             C<field>.
194              
195             A C<param> is a required parameter (defaults may be used). A C<field> is not
196             intended to be passed to the constructor.
197              
198             B<Note>: the C<has> function is still available, even if it's not needed.
199             Unlike C<param> and C<field>, it still requires an C<is> option.
200              
201             Also, while your author likes the postfix block syntax, it's not required. You
202             can even safely inline multiple packages in the same file:
203              
204             package My::Point;
205             use MooseX::Extended types => 'Num';
206              
207             param [ 'x', 'y' ] => ( isa => Num );
208              
209             package My::Point::Mutable;
210             use MooseX::Extended;
211             extends 'My::Point';
212              
213             param [ '+x', '+y' ] => ( writer => 1, clearer => 1, default => 0 );
214              
215             sub invert ($self) {
216             my ( $x, $y ) = ( $self->x, $self->y );
217             $self->set_x($y);
218             $self->set_y($x);
219             }
220              
221             # MooseX::Extended will cause this to return true, even if we try to return
222             # false
223             0;
224              
225             =head1 CONFIGURATION
226              
227             You may pass an import list to L<MooseX::Extended>.
228              
229             use MooseX::Extended
230             excludes => [qw/StrictConstructor carp/], # I don't want these features
231             types => [qw/compile PositiveInt HashRef/]; # I want these type tools
232              
233             =head2 C<types>
234              
235             Allows you to import any types provided by L<MooseX::Extended::Types>.
236              
237             This:
238              
239             use MooseX::Extended::Role types => [qw/compile PositiveInt HashRef/];
240              
241             Is identical to this:
242              
243             use MooseX::Extended::Role;
244             use MooseX::Extended::Types qw( compile PositiveInt HashRef );
245              
246             =head2 C<excludes>
247              
248             You may find some features to be annoying, or even cause potential bugs (e.g.,
249             if you have a C<croak> method, our importing of C<Carp::croak> will be a
250             problem.
251              
252             A single argument to C<excludes> can be a string. Multiple C<excludes> require
253             an array reference:
254              
255             use MooseX::Extended excludes => [qw/StrictConstructor autoclean/];
256              
257             You can exclude the following:
258              
259             =over 4
260              
261             =item * C<StrictConstructor>
262              
263             use MooseX::Extended excludes => 'StrictConstructor';
264              
265             Excluding this will no longer import C<MooseX::StrictConstructor>.
266              
267             =item * C<autoclean>
268              
269             use MooseX::Extended excludes => 'autoclean';
270              
271             Excluding this will no longer import C<namespace::autoclean>.
272              
273             =item * C<c3>
274              
275             use MooseX::Extended excludes => 'c3';
276              
277             Excluding this will no longer apply the C3 mro.
278              
279             =item * C<carp>
280              
281             use MooseX::Extended excludes => 'carp';
282              
283             Excluding this will no longer import C<Carp::croak> and C<Carp::carp>.
284              
285             =item * C<immutable>
286              
287             use MooseX::Extended excludes => 'immutable';
288              
289             Excluding this will no longer make your class immutable.
290              
291             =item * C<true>
292              
293             use MooseX::Extended excludes => 'true';
294              
295             Excluding this will require your module to end in a true value.
296              
297             =item * C<param>
298              
299             use MooseX::Extended excludes => 'param';
300              
301             Excluding this will make the C<param> function unavailable.
302              
303             =item * C<field>
304              
305             use MooseX::Extended excludes => 'field';
306              
307             Excluding this will make the C<field> function unavailable.
308              
309             =back
310              
311             =head2 C<includes>
312              
313             Several I<optional> features of L<MooseX::Extended> make this module much more
314             powerful. For example, to include try/catch and a C<method> keyword:
315              
316             use MooseX::Extended includes => [ 'method', 'try' ];
317              
318             A single argument to C<includes> can be a string. Multiple C<includes> require
319             an array reference:
320              
321             use MooseX::Extended includes => [qw/method try/];
322              
323             See L<MooseX::Extended::Manual::Includes> for more information.
324              
325             =head1 REDUCING BOILERPLATE
326              
327             Let's say you've settled on the following feature set:
328              
329             use MooseX::Extended
330             excludes => [qw/StrictConstructor carp/],
331             includes => 'method',
332             types => ':Standard';
333              
334             And you keep typing that over and over. We've removed a lot of boilerplate,
335             but we've added different boilerplate. Instead, just create
336             C<My::Custom::Moose> and C<use My::Custom::Moose;>. See
337             L<MooseX::Extended::Custom> for details.
338              
339             =head1 IMMUTABILITY
340              
341             =head2 Making Your Class Immutable
342              
343             You no longer need to end your Moose classes with:
344              
345             __PACKAGE__->meta->make_immutable;
346              
347             That prevents further changes to the class and provides some optimizations to
348             make the code run much faster. However, it's somewhat annoying to type. We do
349             this for you, via L<B::Hooks::AtRuntime>. You no longer need to do this yourself.
350              
351             =head2 Making Your Instance Immutable
352              
353             By default, attributes defined via C<param> and C<field> are read-only.
354             However, if they contain a reference, you can fetch the reference, mutate it,
355             and now everyone with a copy of that reference has mutated state.
356              
357             To handle that, we offer a new C<< clone => $clone_type >> pair for attributes.
358              
359             See the L<MooseX::Extended::Manual::Cloning> documentation.
360              
361             =head1 OBJECT CONSTRUCTION
362              
363             Object construction for L<MooseX::Extended> is identical to Moose because
364             MooseX::Extended I<is> Moose, so no changes are needed. However, in addition
365             to C<has>, we also provide C<param> and C<field> attributes, both of which are
366             C<< is => 'ro' >> by default.
367              
368             The C<param> is I<required>, whether by passing it to the constructor, or using
369             C<default> or C<builder>.
370              
371             The C<field> is I<forbidden> in the constructor and is lazy if it has a
372             builder, because that builder is often dependent on attributes set in the
373             constructor (and why call it if it's not used?).
374              
375             Here's a short example:
376              
377             package Class::Name {
378             use MooseX::Extended types => [qw(compile Num NonEmptyStr Str)];
379              
380             # these default to 'ro' (but you can override that) and are required
381             param _name => ( isa => NonEmptyStr, init_arg => 'name' );
382             param title => ( isa => Str, required => 0 );
383              
384             # fields must never be passed to the constructor
385             # note that ->title and ->name are guaranteed to be set before
386             # this because fields are lazy by default
387             field name => (
388             isa => NonEmptyStr,
389             default => sub ($self) {
390             my $title = $self->title;
391             my $name = $self->_name;
392             return $title ? "$title $name" : $name;
393             },
394             );
395             }
396              
397             See L<MooseX::Extended::Manual::Construction> for a full explanation.
398              
399             =head1 ATTRIBUTE SHORTCUTS
400              
401             When using C<field> or C<param>, we have some attribute shortcuts:
402              
403             param name => (
404             isa => NonEmptyStr,
405             writer => 1, # set_name
406             reader => 1, # get_name
407             predicate => 1, # has_name
408             clearer => 1, # clear_name
409             builder => 1, # _build_name
410             );
411              
412             sub _build_name ($self) {
413             ...
414             }
415              
416             You can also do this:
417              
418             param name ( isa => NonEmptyStr, builder => sub {...} );
419              
420             That's the same as:
421              
422             param name ( isa => NonEmptyStr, builder => '_build_name' );
423              
424             sub _build_name {...}
425              
426             See L<MooseX::Extended::Manual::Shortcuts> for a full explanation.
427              
428             =head1 INVALID ATTRIBUTE DEFINITIONS
429              
430             The following L<Moose> code will print C<WhoAmI>. However, the second attribute
431             name is clearly invalid.
432              
433             package Some::Class {
434             use Moose;
435              
436             has name => ( is => 'ro' );
437             has '-bad' => ( is => 'ro' );
438             }
439              
440             my $object = Some::Class->new( name => 'WhoAmI' );
441             say $object->name;
442              
443             C<MooseX::Extended> will throw a
444             L<Moose::Exception::InvalidAttributeDefinition> exception if it encounters an
445             illegal method name for an attribute.
446              
447             This also applies to various attributes which allow method names, such as
448             C<clone>, C<builder>, C<clearer>, C<writer>, C<reader>, and C<predicate>.
449              
450             Trying to pass a defined C<init_arg> to C<field> will also throw this
451             exception, unless the init_arg begins with an underscore. (It is sometimes
452             useful to be able to define an C<init_arg> for unit testing.)
453              
454             =head1 BUGS AND LIMITATIONS
455              
456             You cannot (at this time) use C<multi> subs with the debugger. This is due to
457             a bug in L<Syntax::Keyword::MultiSub> that should be fixed in the next release
458             of that module.
459              
460             If you must have multisubs and the debugger, the follow patch to
461             L<Syntax::Keyword::MultiSub> fixes the issue:
462              
463             --- old/lib/Syntax/Keyword/MultiSub.xs 2021-12-16 10:59:30 +0000
464             +++ new/lib/Syntax/Keyword/MultiSub.xs 2022-08-12 10:23:06 +0000
465             @@ -129,6 +129,7 @@
466             redo:
467             switch(o->op_type) {
468             case OP_NEXTSTATE:
469             + case OP_DBSTATE:
470             o = o->op_next;
471             goto redo;
472              
473             See also:
474              
475             =over 4
476              
477             =item * L<The github issue|https://github.com/Ovid/moosex-extended/issues/45>
478              
479             =back
480              
481             =head1 MANUAL
482              
483             =over 4
484              
485             =item * L<MooseX::Extended::Manual::Tutorial>
486              
487             =item * L<MooseX::Extended::Manual::Overview>
488              
489             =item * L<MooseX::Extended::Manual::Construction>
490              
491             =item * L<MooseX::Extended::Manual::Includes>
492              
493             =item * L<MooseX::Extended::Manual::Shortcuts>
494              
495             =item * L<MooseX::Extended::Manual::Cloning>
496              
497             =back
498              
499             =head1 RELATED MODULES
500              
501             =over 4
502              
503             =item * L<MooseX::Extended::Types> is included in the distribution.
504              
505             This provides core types for you.
506              
507             =item * L<MooseX::Extended::Role> is included in the distribution.
508              
509             C<MooseX::Extended>, but for roles.
510              
511             =back
512              
513             =head1 TODO
514              
515             Some of this may just be wishful thinking. Some of this would be interesting if
516             others would like to collaborate.
517              
518             =head2 Configurable Types
519              
520             We provide C<MooseX::Extended::Types> for convenience, along with the C<declare>
521             function. We should write up (and test) examples of extending it.
522              
523             =head2 C<BEGIN::Lift>
524              
525             This idea maybe belongs in C<MooseX::Extended::OverKill>, but ...
526              
527             Quite often you see things like this:
528              
529             BEGIN { extends 'Some::Parent' }
530              
531             Or this:
532              
533             sub serial_number; # required by a role, must be compile-time
534             has serial_number => ( ... );
535              
536             In fact, there are a variety of Moose functions which would work better if
537             they ran at compile-time instead of runtime, making them look a touch more
538             like native functions. My various attempts at solving this have failed, but I
539             confess I didn't try too hard.
540              
541             =head1 NOTES
542              
543             There are a few things you might be interested to know about this module when
544             evaluating it.
545              
546             Most of this is written with bog-standard L<Moose>, so there's nothing
547             terribly weird inside, but you may wish to note that we use
548             L<B::Hooks::AtRuntime> and L<true>. They seem sane, but I<caveat emptor>.
549              
550             =head1 SEE ALSO
551              
552             =over 4
553              
554             =item * L<Corinna|https://github.com/Ovid/Cor>
555              
556             The RFC of the new version of OOP planned for the Perl core.
557              
558             =item * L<MooseX::Modern|https://metacpan.org/pod/MooseX::Modern>
559              
560             MooseX::Modern - Precision classes for Modern Perl
561              
562             =item * L<Zydeco|https://metacpan.org/pod/Zydeco>
563              
564             Zydeco - Jazz up your Perl
565              
566             =item * L<Dios|https://metacpan.org/pod/Dios>
567              
568             Dios - Declarative Inside-Out Syntax
569              
570             =item * L<MooseX::AttributeShortcuts|https://metacpan.org/pod/MooseX::AttributeShortcuts>
571              
572             MooseX::AttributeShortcuts - Shorthand for common attribute options
573              
574             =back
575              
576             =head1 AUTHOR
577              
578             Curtis "Ovid" Poe <curtis.poe@gmail.com>
579              
580             =head1 COPYRIGHT AND LICENSE
581              
582             This software is Copyright (c) 2022 by Curtis "Ovid" Poe.
583              
584             This is free software, licensed under:
585              
586             The Artistic License 2.0 (GPL Compatible)
587              
588             =cut