use roles_with_args
[catagits/CatalystX-Declare.git] / lib / CatalystX / Declare / Keyword / Action.pm
1 use MooseX::Declare;
2 use MooseX::Role::Parameterized ();
3
4 class CatalystX::Declare::Keyword::Action {
5
6
7     use Carp                qw( croak );
8     use Perl6::Junction     qw( any );
9     use Data::Dump          qw( pp );
10     use MooseX::Types::Util qw( has_available_type_export );
11     use Moose::Util         qw( add_method_modifier ensure_all_roles );
12     use Class::Inspector;
13     use Class::MOP;
14
15
16     use constant STOP_PARSING   => '__MXDECLARE_STOP_PARSING__';
17     use constant UNDER_VAR      => '$CatalystX::Declare::SCOPE::UNDER';
18     use constant UNDER_STACK    => '@CatalystX::Declare::SCOPE::UNDER_STACK';
19
20     use aliased 'CatalystX::Declare::Action::CatchValidationError';
21     use aliased 'CatalystX::Declare::Context::StringParsing';
22     use aliased 'MooseX::Method::Signatures::Meta::Method';
23     use aliased 'MooseX::MethodAttributes::Role::Meta::Method', 'AttributeRole';
24     use aliased 'MooseX::MethodAttributes::Role::Meta::Role',   'AttributeMetaRole';
25
26
27     method parse (Object $ctx, Str :$modifier?, Int :$skipped_declarator = 0) {
28
29         # somewhere to put the attributes
30         my %attributes;
31         my @populators;
32
33         # parse declarations
34         until (do { $ctx->skipspace; $ctx->peek_next_char } eq any qw( ; { } )) {
35
36             $ctx->skipspace;
37             
38             # optional commas
39             if ($ctx->peek_next_char eq ',') {
40
41                 my $linestr = $ctx->get_linestr;
42                 substr($linestr, $ctx->offset, 1) = '';
43                 $ctx->set_linestr($linestr);
44
45                 next;
46             }
47
48             # next thing should be an option name
49             my $option = (
50                 $skipped_declarator 
51                 ? $ctx->strip_name 
52                 : do { 
53                     $ctx->skip_declarator; 
54                     $skipped_declarator++;
55                     $ctx->declarator;
56                 })
57               or croak "Expected option token, not " . substr($ctx->get_linestr, $ctx->offset);
58
59             # we need to be able to handle the rest
60             my $handler = $self->can("_handle_${option}_option")
61                 or croak "Unknown action option: $option";
62
63             # call the handler
64             my $populator = $self->$handler($ctx, \%attributes);
65
66             if ($populator and $populator eq STOP_PARSING) {
67
68                 return $ctx->shadow(sub (&) {
69                     my ($body) = @_;
70                     return $body->();
71                 });
72             }
73
74             push @populators, $populator
75                 if defined $populator;
76         }
77
78         croak "Need an action specification"
79             unless exists $attributes{Signature};
80
81         my $name   = $attributes{Subname};
82
83         if ($attributes{Private}) {
84             $attributes{Signature} ||= '@';
85         }
86
87         my $method = Method->wrap(
88             signature       => qq{($attributes{Signature})},
89             package_name    => $ctx->get_curstash_name,
90             name            => $name,
91         );
92
93         AttributeRole->meta->apply($method);
94
95         my $count = $self->_count_positional_arguments($method);
96         $attributes{CaptureArgs} = $count
97             if defined $count;
98
99         $_->($method)
100             for @populators;
101
102         unless ($attributes{Private}) {
103             $attributes{PathPart} ||= $name;
104
105             delete $attributes{CaptureArgs}
106                 if exists $attributes{Args};
107
108             $attributes{CaptureArgs} = 0
109                 unless exists $attributes{Args}
110                     or exists $attributes{CaptureArgs};
111         }
112
113         if ($attributes{Private}) {
114             delete $attributes{ $_ }
115                 for qw( Args CaptureArgs Chained Signature Private );
116         }
117
118         # inject a hashref for resolving runtime attribute values
119         $self->_inject_attributes($ctx, \%attributes);
120
121         # our declaration is followed by a block
122         if ($ctx->peek_next_char eq '{') {
123             $ctx->inject_if_block($ctx->scope_injector_call . $method->injectable_code);
124         }
125
126         # there is no block, so we insert one.
127         else {
128             $ctx->inject_code_parts_here(
129                 sprintf '{ %s%s }',
130                     $ctx->scope_injector_call,
131                     $method->injectable_code,
132             );
133         }
134
135         my $compile_attrs = sub {
136             my $attributes = shift;;
137             my @attributes;
138
139             for my $attr (keys %$attributes) {
140                 my $value = $attributes->{ $attr };
141
142                 # the compiletime chained attr might contain the under global var name
143                 next if $attr eq 'Chained' and $value eq UNDER_VAR;
144
145                 push @attributes, 
146                     map { sprintf '%s%s', $attr, defined($_) ? sprintf('(%s)', $_) : '' }
147                         (ref($value) eq 'ARRAY') 
148                         ? @$value
149                         : $value;
150             }
151
152             return \@attributes;
153         };
154
155         return $ctx->shadow(sub {
156             my $class = caller;
157             my $attrs = shift;
158             my $body  = shift;
159
160             # the runtime-resolved name
161             my $name  = $attrs->{Subname};
162
163             # in case no hashref was specified
164             $body = $attrs and $attrs = {}
165                 if ref $attrs eq 'CODE';
166
167             # default path part to runtime-resolved name
168             unless ($attrs->{Private}) {
169
170                 $attrs->{PathPart} = $attrs->{Subname}
171                     unless defined $attrs->{PathPart};
172             }
173
174             # in CXD we are explicit about chained values, an undefined
175             # value means we defaulted to the outer-scope under and there
176             # was none.
177             delete $attrs->{Chained}
178                 unless defined $attrs->{Chained};
179
180             # some attrs need to be single quoted in their stringified forms
181             defined($attrs->{ $_ }) and $attrs->{ $_ } = sprintf "'%s'", $attrs->{ $_ }
182                 for qw( Chained PathPart );
183
184             # merge runtime and compiletime attrs
185             my %full_attrs = (%attributes, %$attrs);
186             my $compiled_attrs = $compile_attrs->(\%full_attrs);
187
188             my $real_method = $method->reify(
189                 actual_body => $body,
190                 attributes  => $compiled_attrs,
191                 name        => $name,
192             );
193
194             # NYI
195             if ($modifier) {
196
197                 add_method_modifier $class, $modifier, [$name, $real_method];
198             }
199             else {
200
201                 my $prepare_meta = sub {
202                     my ($meta) = @_;
203
204                     $meta->add_method($name, $real_method);
205                     $meta->register_method_attributes($meta->name->can($real_method->name), $compiled_attrs);
206                 };
207
208                 if ($ctx->stack->[-1] and $ctx->stack->[-1]->is_parameterized) {
209                     my $real_meta = MooseX::Role::Parameterized->current_metaclass;
210
211                     $real_meta->meta->make_mutable
212                         if $real_meta->meta->is_immutable;
213                     ensure_all_roles $real_meta->meta, AttributeMetaRole
214                         if $real_meta->isa('Moose::Meta::Role');
215
216                     $real_meta->$prepare_meta;
217                 }
218                 else {
219
220                     $class->meta->$prepare_meta;
221                 }
222             }
223         });
224     }
225
226     method _handle_with_option (Object $ctx, HashRef $attrs) {
227
228         my @roles_with_args = ();
229         push @roles_with_args, @{ $ctx->strip_names_and_args };
230
231         # we need to fish for aliases here since we are still unclean
232         my @roles = ();
233         for my $role_with_arg(@roles_with_args) {
234             my $role = $role_with_arg->[0];
235             if (defined(my $alias = $self->_check_for_available_import($ctx, $role))) {
236                 $role = $alias;
237             }
238             push @roles, $role;
239         }
240
241         push @{ $attrs->{CatalystX_Declarative_ActionRoles} ||= [] }, @roles;
242
243         return;
244     }
245
246     method _handle_isa_option (Object $ctx, HashRef $attrs) {
247
248         my $class = $ctx->strip_name
249             or croak "Expected bareword action class specification for action after isa";
250
251         if (defined(my $alias = $self->_check_for_available_import($ctx, $class))) {
252             $class = $alias;
253         }
254
255         $attrs->{CatalystX_Declarative_ActionClass} = $class;
256
257         return;
258     }
259
260     method _check_for_available_import (Object $ctx, Str $name) {
261
262         if (my $code = $ctx->get_curstash_name->can($name)) {
263             return $code->();
264         }
265
266         return undef;
267     }
268
269     method _handle_action_option (Object $ctx, HashRef $attrs) {
270
271         # action name
272         my $name = $self->_strip_actionpath($ctx, interpolate => 1)
273             or croak "Anonymous actions not yet supported";
274
275         $ctx->skipspace;
276         my $populator;
277
278         # shortcut under base option is basically handled by the under handler
279         if (substr($ctx->get_linestr, $ctx->offset, 2) eq '<-') {
280             my $linestr = $ctx->get_linestr;
281             substr($linestr, $ctx->offset, 2) = '';
282             $ctx->set_linestr($linestr);
283             $populator = $self->_handle_under_option($ctx, $attrs);
284         }
285
286         # signature
287         my $proto = $ctx->strip_proto || '';
288         $proto = join(', ', 'Object $self: Object $ctx', $proto || ());
289
290         $attrs->{Subname}   = $name;
291         $attrs->{Signature} = $proto;
292         $attrs->{Action}    = [];
293
294         push @{ $attrs->{CatalystX_Declarative_ActionRoles} ||= [] }, CatchValidationError;
295
296         # default chained base to the global under var, to be resolved at runtime
297         $attrs->{Chained} ||= UNDER_VAR;
298
299         return unless $populator;
300         return $populator;
301     }
302
303     method _handle_final_option (Object $ctx, HashRef $attrs) {
304
305         return $self->_build_flag_populator($ctx, $attrs, 'final');
306     }
307
308     method _handle_is_option (Object $ctx, HashRef $attrs) {
309
310         my $what = $ctx->strip_name
311             or croak "Expected symbol token after is symbol, not " . substr($ctx->get_linestr, $ctx->offset);
312
313         return $self->_build_flag_populator($ctx, $attrs, $what);
314     }
315
316     method _build_flag_populator (Object $ctx, HashRef $attrs, Str $what) {
317
318         $attrs->{Private} = []
319             if $what eq 'private';
320
321         return sub {
322             my $method = shift;
323
324             if ($what eq any qw( end endpoint final )) {
325                 $attrs->{Args} = delete $attrs->{CaptureArgs};
326             }
327             elsif ($what eq 'private') {
328                 $attrs->{Private} = [];
329             }
330         };
331     }
332
333     method _handle_under_option (Object $ctx, HashRef $attrs) {
334
335         my $target = $self->_strip_actionpath($ctx, interpolate => 1);
336         $ctx->skipspace;
337
338         if ($ctx->peek_next_char eq '{' and $self->identifier eq 'under') {
339             $ctx->inject_if_block(
340                 $ctx->scope_injector_call .
341                 sprintf ';local %s = %s;',
342                     UNDER_VAR,
343                     $target,
344             );
345             return STOP_PARSING;
346         }
347
348         $attrs->{Chained} = $target;
349
350         return sub {
351             my $method = shift;
352         };
353     }
354
355     method _handle_chains_option (Object $ctx, HashRef $attrs) {
356
357         $ctx->skipspace;
358         $ctx->strip_name eq 'to'
359             or croak "Expected to token after chains symbol, not " . substr($ctx->get_linestr, $ctx->offset);
360
361         return $self->_handle_under_option($ctx, $attrs);
362     }
363
364     method _handle_as_option (Object $ctx, HashRef $attrs) {
365
366         $ctx->skipspace;
367
368         my $path = $self->_strip_actionpath($ctx, interpolate => 1);
369         $attrs->{PathPart} = $path;
370
371         return;
372     }
373
374     method _count_positional_arguments (Object $method) {
375         my $signature = $method->parsed_signature;
376
377         if ($signature->has_positional_params) {
378             my $count = @{ scalar($signature->positional_params) };
379
380             if ($count and ($signature->positional_params)[-1]->sigil eq '@') {
381                 return undef;
382             }
383
384             return $count - 1;
385         }
386
387         return 0;
388     }
389
390     method _inject_attributes (Object $ctx, HashRef $attrs) {
391
392         # attrs that need to be runtime-resolved
393         my @inject = qw( Chained PathPart Subname );
394
395         # turn specific attributes into a hashref
396         my $code = sprintf ' +{ %s }, sub ',        # the ', sub ' turns method +{ ... } { ... } into 
397             join ', ',                              #   method +{ ... }, sub { ... }
398             map  { (@$_) }
399             map  { defined( $_->[1] ) ? $_ : [$_->[0], 'undef'] }
400             map  { [pp($_), $attrs->{ $_ }] }
401             grep { defined $attrs->{ $_ } }
402             @inject;
403
404         # inject the hashref code before the action body
405         $ctx->inject_code_parts_here($code);
406         $ctx->inc_offset(length $code);
407     }
408
409     method _strip_actionpath (Object $ctx, :$interpolate?) {
410
411         $ctx->skipspace;
412         my $linestr = $ctx->get_linestr;
413         my $rest    = substr($linestr, $ctx->offset);
414         my $interp  = sub { $interpolate ? "'$_[0]'" : $_[0] };
415
416         # find simple barewords
417         if ($rest =~ /^ ( [_a-z] [_a-z0-9]* ) \b/ix) {
418             substr($linestr, $ctx->offset, length($1)) = '';
419             $ctx->set_linestr($linestr);
420             return $interp->($1);
421         }
422
423         # allow single quoted more complex barewords
424         elsif ($rest =~ /^ ' ( (?:[.:;,_a-z0-9]|\/)* ) ' /ix) {
425             substr($linestr, $ctx->offset, length($1) + 2) = '';
426             $ctx->set_linestr($linestr);
427             return $interp->($1);
428         }
429
430         # double quoted strings and variables
431         elsif ($interpolate and my $str = $ctx->get_string) {
432             return $str;
433         }
434
435         # not suitable as action path
436         else {
437             croak "Invalid syntax for action path: $rest";
438         }
439     }
440     
441     # down here because it requires the parse method
442     with 'MooseX::Declare::Syntax::KeywordHandling';
443
444     around context_traits { $self->$orig, StringParsing }
445 }
446
447 __END__
448
449 =head1 NAME
450
451 CatalystX::Declare::Keyword::Action - Declare Catalyst Actions
452
453 =head1 SYNOPSIS
454
455     use CatalystX::Declare;
456
457     controller MyApp::Web::Controller::Example {
458
459         # chain base action with path part setting of ''
460         # body-less actions don't do anything by themselves
461         action base as '' under '/';
462
463         # simple end-point action
464         action controller_class is final under base {
465             $ctx->response->body( 'controller: ' . ref $self );
466         }
467
468         # chain part actions can have arguments
469         action str (Str $string) under base {
470
471             $ctx->stash(chars => [split //, $string]);
472         }
473
474         # and end point actions too, of course
475         action uc_chars (Int $count) under str is final {
476     
477             my $chars = $ctx->stash->{chars};
478             ...
479         }
480
481
482         # you can use a shortcut for multiple actions with
483         # a common base
484         under base {
485
486             # this is an endpoint after base
487             action normal is final;
488
489             # the final keyword can be used to be more 
490             # visually explicit about end-points
491             final action some_action { ... }
492
493             # type dispatching works
494             final action with_str (Str $x) as via_type;
495             final action with_int (Int $x) as via_type;
496         }
497
498         # of course you can also chain to external actions
499         final action some_end under '/some/controller/some/action';
500     }
501
502 =head1 DESCRIPTION
503
504 This handler class provides the user with C<action>, C<final> and C<under> 
505 keywords. There are multiple ways to define actions to allow for greater
506 freedom of expression. While the parts of the action declaration itself do
507 not care about their order, their syntax is rather strict.
508
509 You can choose to separate syntax elements via C<,> if you think it is more
510 readable. The action declaration
511
512     action foo is final under base;
513
514 is parsed in exactly the same way if you write it as
515
516     action foo, is final, under base;
517
518 =head2 Basic Action Declaration
519
520 The simplest possible declaration is
521
522     action foo;
523
524 This would define a chain-part action chained to nothing with the name C<foo>
525 and no arguments. Since it isn't followed by a block, the body of the action
526 will be empty.
527
528 You will automatically be provided with two variables: C<$self> is, as you
529 might expect, your controller instance. C<$ctx> will be the Catalyst context
530 object. Thus, the following code would stash the value returned by the 
531 C<get_item> method:
532
533     action foo {
534         $ctx->stash(item => $self->get_item);
535     }
536
537 =head2 Why $ctx instead of $c
538
539 Some might ask why the context object is called C<$ctx> instead of the usual
540 C<$c>. The reason is simple: It's an opinionated best practice, since C<$ctx>
541 stands out more.
542
543 =head2 Setting a Path Part
544
545 As usual with Catalyst actions, the path part (the public name of this part of
546 the URI, if you're not familiar with the term yet) will default to the name of
547 the action itself (or more correctly: to whatever Catalyst defaults).
548
549 To change that, use the C<as> option:
550
551     under something {
552         action base      as '';             # <empty>
553         action something as 'foo/bar';      # foo/bar
554         action barely    as bareword;       # bareword
555     }
556
557 =head2 Chaining Actions
558
559 Currently, L<CatalystX::Declare> is completely based on the concept of
560 L<chained actions|Catalyst::DispatchType::Chained>. Every action you declare is
561 chained or private. You can specify the action you want to chain to with the 
562 C<under> option:
563
564     action foo;                     # chained to nothing
565     action foo under '/';           # also chained to /
566     action foo under bar;           # chained to the local bar action
567     action foo under '/bar/baz';    # chained to baz in /bar
568
569 C<under> is also provided as a grouping keyword. Every action inside the block
570 will be chained to the specified action:
571
572     under base {
573         action foo { ... }
574         action bar { ... }
575     }
576
577 You can also use the C<under> keyword for a single action. This is useful if
578 you want to highlight a single action with a significant diversion from what
579 is to be expected:
580
581     action base under '/';
582
583     under '/the/sink' is final action foo;
584
585     final action bar under base;
586
587     final action baz under base;
588
589 Instead of the C<under> option declaration, you can also use a more english
590 variant named C<chains to>. While C<under> might be nice and concise, some
591 people might prefer this if they confuse C<under> with the specification of
592 a public path part. The argument to C<chains to> is the same as to C<under>:
593
594     action foo chains to bar;
595     action foo under bar;
596
597 By default all actions are chain-parts, not end-points. If you want an action 
598 to be picked up as end-point and available via a public path, you have to say
599 so explicitely by  using the C<is final> option:
600
601     action base under '/';
602     action foo under base is final;   # /base/foo
603
604 You can also drop the C<is> part of the C<is final> option if you want:
605
606     under base, final action foo { ... }
607
608 You can make end-points more visually distinct by using the C<final> keyword
609 instead of the option:
610
611     action base under '/';
612     final action foo under base;      # /base/foo
613
614 And of course, the C<final>, C<under> and C<action> keywords can be used in
615 combination whenever needed:
616
617     action base as '' under '/';
618
619     under base {
620
621         final action list;          # /list
622
623         action load;
624
625         under load {
626
627             final action view;      # /list/load/view
628             final action edit;      # /list/load/edit
629         }
630     }
631
632 There is also one shorthand alternative for declaring chain targets. You can
633 specify an action after a C<E<lt>-> following the action name:
634
635     action base under '/';
636     final action foo <- base;       # /base/foo
637
638 =head2 Arguments
639
640 You can use signatures like you are use to from L<MooseX::Method::Signatures>
641 to declare action parameters. The number of positinoal arguments will be used 
642 during dispatching as well as their types.
643
644 The signature follows the action name:
645
646     # /foo/*/*/*
647     final action foo (Int $year, Int $month, Int $day);
648
649 If you are using the shorthand definition, the signature follows the chain 
650 target:
651
652     # /foo/*
653     final action foo <- base ($x) under '/' { ... }
654
655 Parameters may be specified on chain-parts and end-points:
656
657     # /base/*/foo/*
658     action base (Str $lang) under '/';
659     final action page (Int $page_num) under base;
660
661 Named parameters will be populated with the values in the query parameters:
662
663     # /view/17/?page=3
664     final action view (Int $id, Int :$page = 1) under '/';
665
666 If you specify a query parameter to be an C<ArrayRef>, it will be specially
667 handled. For one, it will match even if there is no such value in the
668 parameters. Second, it will always be wrapped as an array reference.
669
670 Your end-points can also take an unspecified amount of arguments by specifying
671 an array as a variable:
672
673     # /find/some/deep/path/spec
674     final action find (@path) under '/';
675
676 =head2 Validation
677
678 The signatures are now validated during dispatching-time, and an action with
679 a non-matching signature (number of positional arguments and their types) will
680 not be dispatched to. This means that
681
682     action base under '/' as '';
683
684     under base {
685
686         final as double, action double_integer (Int $x) {
687             $ctx->response->body( $x * 2 );
688         }
689
690         final as double, action double_string (Str $x) {
691             $ctx->response->body( $x x 2 );
692         }
693     }
694
695 will return C<foofoo> when called as C</double/foo> and C<46> when called as
696 C</double/23>.
697
698 =head2 Actions and Method Modifiers
699
700 Method modifiers can not only be applied to methods, but also to actions. There
701 is no way yet to override the attributes of an already established action via
702 modifiers. However, you can modify the method underlying the action.
703
704 The following code is an example role modifying the consuming controller's
705 C<base> action:
706
707     use CatalystX::Declare;
708
709     controller_role MyApp::Web::ControllerRole::RichBase {
710
711         before base (Object $ctx) {
712             $ctx->stash(something => $ctx->model('Item'));
713         }
714     }
715
716 Note that you have to specify the C<$ctx> argument yourself, since you are 
717 modifying a method, not an action.
718
719 Any controller having a C<base> action (or method, for this purpose), can now
720 consume the C<RichBase> role declared above:
721
722     use CatalystX::Declare;
723
724     controller MyApp::Web::Controller::Foo
725         with   MyApp::Web::Controller::RichBase {
726
727         action base as '' under '/';
728
729         action show, final under base { 
730             $ctx->response->body(
731                 $ctx->stash->{something}->render,
732             );
733         }
734     }
735
736 =head2 Action Classes
737
738 B<This option is even more experimental>
739
740 You might want to create an action with a different class than the usual
741 L<Catalyst::Action>. A usual suspect here is L<Catalyst::Action::RenderView>.
742 You can use the C<isa> option (did I mention it's experimental?) to specify
743 what class to use:
744
745     controller MyApp::Web::Controller::Root {
746
747         $CLASS->config(namespace => '');
748
749         action end isa RenderView;
750     }
751
752 The loaded class will be L<Moose>ified, so we are able to apply essential
753 roles.
754
755 =head2 Private Actions
756
757 B<This option is a bit less, but still pretty experimental>
758
759 You can declare private actions with the C<is private> trait:
760
761     action end is private isa RenderView;
762
763 =head1 ROLES
764
765 =over
766
767 =item L<MooseX::Declare::Syntax::KeywordHandling>
768
769 =back
770
771 =head1 METHODS
772
773 These methods are implementation details. Unless you are extending or 
774 developing L<CatalystX::Declare>, you should not be concerned with them.
775
776 =head2 parse
777
778     Object->parse (Object $ctx, Str :$modifier?, Int :$skipped_declarator = 0)
779
780 A hook that will be invoked by L<MooseX::Declare> when this instance is called 
781 to handle syntax. It will parse the action declaration, prepare attributes and 
782 add the actions to the controller.
783
784 =head1 SEE ALSO
785
786 =over
787
788 =item L<CatalystX::Declare>
789
790 =item L<CatalystX::Declare::Keyword::Controller>
791
792 =item L<MooseX::Method::Signatures>
793
794 =back
795
796 =head1 AUTHOR
797
798 See L<CatalystX::Declare/AUTHOR> for author information.
799
800 =head1 LICENSE
801
802 This program is free software; you can redistribute it and/or modify it under 
803 the same terms as perl itself.
804
805 =cut
806