2 use MooseX::Role::Parameterized ();
4 class CatalystX::Declare::Keyword::Action {
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 );
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';
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';
27 method parse (Object $ctx, Str :$modifier?, Int :$skipped_declarator = 0) {
29 # somewhere to put the attributes
34 until (do { $ctx->skipspace; $ctx->peek_next_char } eq any qw( ; { } )) {
39 if ($ctx->peek_next_char eq ',') {
41 my $linestr = $ctx->get_linestr;
42 substr($linestr, $ctx->offset, 1) = '';
43 $ctx->set_linestr($linestr);
48 # next thing should be an option name
53 $ctx->skip_declarator;
54 $skipped_declarator++;
57 or croak "Expected option token, not " . substr($ctx->get_linestr, $ctx->offset);
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";
64 my $populator = $self->$handler($ctx, \%attributes);
66 if ($populator and $populator eq STOP_PARSING) {
68 return $ctx->shadow(sub (&) {
74 push @populators, $populator
75 if defined $populator;
78 croak "Need an action specification"
79 unless exists $attributes{Signature};
81 my $name = $attributes{Subname};
83 my $method = Method->wrap(
84 signature => qq{($attributes{Signature})},
85 package_name => $ctx->get_curstash_name,
89 AttributeRole->meta->apply($method);
91 my $count = $self->_count_positional_arguments($method);
92 $attributes{CaptureArgs} = $count
98 unless ($attributes{Private}) {
99 $attributes{PathPart} ||= $name;
101 delete $attributes{CaptureArgs}
102 if exists $attributes{Args};
104 $attributes{CaptureArgs} = 0
105 unless exists $attributes{Args}
106 or exists $attributes{CaptureArgs};
109 if ($attributes{Private}) {
110 delete $attributes{ $_ }
111 for qw( Args CaptureArgs Chained Signature Private );
114 $self->_inject_attributes($ctx, \%attributes);
116 if ($ctx->peek_next_char eq '{') {
117 $ctx->inject_if_block($ctx->scope_injector_call . $method->injectable_code);
120 $ctx->inject_code_parts_here(
122 $ctx->scope_injector_call,
123 $method->injectable_code,
127 my $compile_attrs = sub {
128 my $attributes = shift;;
131 for my $attr (keys %$attributes) {
132 my $value = $attributes->{ $attr };
134 next if $attr eq 'Chained' and $value eq UNDER_VAR;
136 # $value = sprintf "'%s'", $value
137 # if grep { $attr eq $_ } qw( Chained PathPart );
140 map { sprintf '%s%s', $attr, defined($_) ? sprintf('(%s)', $_) : '' }
141 (ref($value) eq 'ARRAY')
149 return $ctx->shadow(sub {
153 my $name = $attrs->{Subname};
155 $body = $attrs and $attrs = {}
156 if ref $attrs eq 'CODE';
158 unless ($attrs->{Private}) {
160 $attrs->{PathPart} = $attrs->{Subname}
161 unless defined $attrs->{PathPart};
164 delete $attrs->{Chained}
165 unless defined $attrs->{Chained};
167 defined($attrs->{ $_ }) and $attrs->{ $_ } = sprintf "'%s'", $attrs->{ $_ }
168 for qw( Chained PathPart );
172 my %full_attrs = (%attributes, %$attrs);
174 my $compiled_attrs = $compile_attrs->(\%full_attrs);
175 # pp $compiled_attrs;
177 my $real_method = $method->reify(
178 actual_body => $body,
179 attributes => $compiled_attrs,
185 add_method_modifier $class, $modifier, [$name, $real_method];
189 my $prepare_meta = sub {
192 $meta->add_method($name, $real_method);
193 $meta->register_method_attributes($meta->name->can($real_method->name), $compiled_attrs);
196 if ($ctx->stack->[-1] and $ctx->stack->[-1]->is_parameterized) {
197 my $real_meta = MooseX::Role::Parameterized->current_metaclass;
199 $real_meta->meta->make_mutable
200 if $real_meta->meta->is_immutable;
201 ensure_all_roles $real_meta->meta, AttributeMetaRole
202 if $real_meta->isa('Moose::Meta::Role');
204 $real_meta->$prepare_meta;
207 $class->meta->$prepare_meta;
212 method _handle_with_option (Object $ctx, HashRef $attrs) {
214 my $role = $ctx->strip_name
215 or croak "Expected bareword role specification for action after with";
217 # we need to fish for aliases here since we are still unclean
218 if (defined(my $alias = $self->_check_for_available_import($ctx, $role))) {
222 push @{ $attrs->{CatalystX_Declarative_ActionRoles} ||= [] }, $role;
227 method _handle_isa_option (Object $ctx, HashRef $attrs) {
229 my $class = $ctx->strip_name
230 or croak "Expected bareword action class specification for action after isa";
232 if (defined(my $alias = $self->_check_for_available_import($ctx, $class))) {
236 $attrs->{CatalystX_Declarative_ActionClass} = $class;
241 method _check_for_available_import (Object $ctx, Str $name) {
243 if (my $code = $ctx->get_curstash_name->can($name)) {
250 method _handle_action_option (Object $ctx, HashRef $attrs) {
253 my $name = $self->_strip_actionpath($ctx, interpolate => 1)
254 or croak "Anonymous actions not yet supported";
259 if (substr($ctx->get_linestr, $ctx->offset, 2) eq '<-') {
260 my $linestr = $ctx->get_linestr;
261 substr($linestr, $ctx->offset, 2) = '';
262 $ctx->set_linestr($linestr);
263 $populator = $self->_handle_under_option($ctx, $attrs);
267 my $proto = $ctx->strip_proto || '';
268 $proto = join(', ', 'Object $self: Object $ctx', $proto || ());
270 $attrs->{Subname} = $name;
271 $attrs->{Signature} = $proto;
272 $attrs->{Action} = [];
274 push @{ $attrs->{CatalystX_Declarative_ActionRoles} ||= [] }, CatchValidationError;
275 $attrs->{Chained} ||= UNDER_VAR;
277 return unless $populator;
281 method _handle_final_option (Object $ctx, HashRef $attrs) {
283 return $self->_build_flag_populator($ctx, $attrs, 'final');
286 method _handle_is_option (Object $ctx, HashRef $attrs) {
288 my $what = $ctx->strip_name
289 or croak "Expected symbol token after is symbol, not " . substr($ctx->get_linestr, $ctx->offset);
291 return $self->_build_flag_populator($ctx, $attrs, $what);
294 method _build_flag_populator (Object $ctx, HashRef $attrs, Str $what) {
299 if ($what eq any qw( end endpoint final )) {
300 $attrs->{Args} = delete $attrs->{CaptureArgs};
302 elsif ($what eq 'private') {
303 $attrs->{Private} = [];
308 method _handle_under_option (Object $ctx, HashRef $attrs) {
310 my $target = $self->_strip_actionpath($ctx, interpolate => 1);
313 if ($ctx->peek_next_char eq '{' and $self->identifier eq 'under') {
314 $ctx->inject_if_block(
315 $ctx->scope_injector_call .
316 sprintf ';local %s = %s;',
323 $attrs->{Chained} = $target;
330 method _handle_chains_option (Object $ctx, HashRef $attrs) {
333 $ctx->strip_name eq 'to'
334 or croak "Expected to token after chains symbol, not " . substr($ctx->get_linestr, $ctx->offset);
336 return $self->_handle_under_option($ctx, $attrs);
339 method _handle_as_option (Object $ctx, HashRef $attrs) {
343 my $path = $self->_strip_actionpath($ctx, interpolate => 1);
344 $attrs->{PathPart} = $path;
349 method _count_positional_arguments (Object $method) {
350 my $signature = $method->parsed_signature;
352 if ($signature->has_positional_params) {
353 my $count = @{ scalar($signature->positional_params) };
355 if ($count and ($signature->positional_params)[-1]->sigil eq '@') {
365 method _inject_attributes (Object $ctx, HashRef $attrs) {
367 my @inject = qw( Chained PathPart Subname );
369 my $code = sprintf ' +{ %s }, sub ',
372 map { defined( $_->[1] ) ? $_ : [$_->[0], 'undef'] }
373 map { [pp($_), $attrs->{ $_ }] }
374 grep { defined $attrs->{ $_ } }
377 $ctx->inject_code_parts_here($code);
378 $ctx->inc_offset(length $code);
381 method _strip_actionpath (Object $ctx, :$interpolate?) {
384 my $linestr = $ctx->get_linestr;
385 my $rest = substr($linestr, $ctx->offset);
386 my $interp = sub { $interpolate ? "'$_[0]'" : $_[0] };
388 if ($rest =~ /^ ( [_a-z] [_a-z0-9]* ) \b/ix) {
389 substr($linestr, $ctx->offset, length($1)) = '';
390 $ctx->set_linestr($linestr);
391 return $interp->($1);
393 elsif ($rest =~ /^ ' ( (?:[.:;,_a-z0-9]|\/)* ) ' /ix) {
394 substr($linestr, $ctx->offset, length($1) + 2) = '';
395 $ctx->set_linestr($linestr);
396 return $interp->($1);
398 elsif ($interpolate and my $str = $ctx->get_string) {
402 croak "Invalid syntax for action path: $rest";
406 with 'MooseX::Declare::Syntax::KeywordHandling';
408 around context_traits { $self->$orig, StringParsing }
415 CatalystX::Declare::Keyword::Action - Declare Catalyst Actions
419 use CatalystX::Declare;
421 controller MyApp::Web::Controller::Example {
423 # chain base action with path part setting of ''
424 # body-less actions don't do anything by themselves
425 action base as '' under '/';
427 # simple end-point action
428 action controller_class is final under base {
429 $ctx->response->body( 'controller: ' . ref $self );
432 # chain part actions can have arguments
433 action str (Str $string) under base {
435 $ctx->stash(chars => [split //, $string]);
438 # and end point actions too, of course
439 action uc_chars (Int $count) under str is final {
441 my $chars = $ctx->stash->{chars};
446 # you can use a shortcut for multiple actions with
450 # this is an endpoint after base
451 action normal is final;
453 # the final keyword can be used to be more
454 # visually explicit about end-points
455 final action some_action { ... }
457 # type dispatching works
458 final action with_str (Str $x) as via_type;
459 final action with_int (Int $x) as via_type;
462 # of course you can also chain to external actions
463 final action some_end under '/some/controller/some/action';
468 This handler class provides the user with C<action>, C<final> and C<under>
469 keywords. There are multiple ways to define actions to allow for greater
470 freedom of expression. While the parts of the action declaration itself do
471 not care about their order, their syntax is rather strict.
473 You can choose to separate syntax elements via C<,> if you think it is more
474 readable. The action declaration
476 action foo is final under base;
478 is parsed in exactly the same way if you write it as
480 action foo, is final, under base;
482 =head2 Basic Action Declaration
484 The simplest possible declaration is
488 This would define a chain-part action chained to nothing with the name C<foo>
489 and no arguments. Since it isn't followed by a block, the body of the action
492 You will automatically be provided with two variables: C<$self> is, as you
493 might expect, your controller instance. C<$ctx> will be the Catalyst context
494 object. Thus, the following code would stash the value returned by the
498 $ctx->stash(item => $self->get_item);
501 =head2 Why $ctx instead of $c
503 Some might ask why the context object is called C<$ctx> instead of the usual
504 C<$c>. The reason is simple: It's an opinionated best practice, since C<$ctx>
507 =head2 Setting a Path Part
509 As usual with Catalyst actions, the path part (the public name of this part of
510 the URI, if you're not familiar with the term yet) will default to the name of
511 the action itself (or more correctly: to whatever Catalyst defaults).
513 To change that, use the C<as> option:
516 action base as ''; # <empty>
517 action something as 'foo/bar'; # foo/bar
518 action barely as bareword; # bareword
521 =head2 Chaining Actions
523 Currently, L<CatalystX::Declare> is completely based on the concept of
524 L<chained actions|Catalyst::DispatchType::Chained>. Every action you declare is
525 chained or private. You can specify the action you want to chain to with the
528 action foo; # chained to nothing
529 action foo under '/'; # also chained to /
530 action foo under bar; # chained to the local bar action
531 action foo under '/bar/baz'; # chained to baz in /bar
533 C<under> is also provided as a grouping keyword. Every action inside the block
534 will be chained to the specified action:
541 You can also use the C<under> keyword for a single action. This is useful if
542 you want to highlight a single action with a significant diversion from what
545 action base under '/';
547 under '/the/sink' is final action foo;
549 final action bar under base;
551 final action baz under base;
553 Instead of the C<under> option declaration, you can also use a more english
554 variant named C<chains to>. While C<under> might be nice and concise, some
555 people might prefer this if they confuse C<under> with the specification of
556 a public path part. The argument to C<chains to> is the same as to C<under>:
558 action foo chains to bar;
559 action foo under bar;
561 By default all actions are chain-parts, not end-points. If you want an action
562 to be picked up as end-point and available via a public path, you have to say
563 so explicitely by using the C<is final> option:
565 action base under '/';
566 action foo under base is final; # /base/foo
568 You can also drop the C<is> part of the C<is final> option if you want:
570 under base, final action foo { ... }
572 You can make end-points more visually distinct by using the C<final> keyword
573 instead of the option:
575 action base under '/';
576 final action foo under base; # /base/foo
578 And of course, the C<final>, C<under> and C<action> keywords can be used in
579 combination whenever needed:
581 action base as '' under '/';
585 final action list; # /list
591 final action view; # /list/load/view
592 final action edit; # /list/load/edit
596 There is also one shorthand alternative for declaring chain targets. You can
597 specify an action after a C<E<lt>-> following the action name:
599 action base under '/';
600 final action foo <- base; # /base/foo
604 You can use signatures like you are use to from L<MooseX::Method::Signatures>
605 to declare action parameters. The number of positinoal arguments will be used
606 during dispatching as well as their types.
608 The signature follows the action name:
611 final action foo (Int $year, Int $month, Int $day);
613 If you are using the shorthand definition, the signature follows the chain
617 final action foo <- base ($x) under '/' { ... }
619 Parameters may be specified on chain-parts and end-points:
622 action base (Str $lang) under '/';
623 final action page (Int $page_num) under base;
625 Named parameters will be populated with the values in the query parameters:
628 final action view (Int $id, Int :$page = 1) under '/';
630 Your end-points can also take an unspecified amount of arguments by specifying
631 an array as a variable:
633 # /find/some/deep/path/spec
634 final action find (@path) under '/';
638 The signatures are now validated during dispatching-time, and an action with
639 a non-matching signature (number of positional arguments and their types) will
640 not be dispatched to. This means that
642 action base under '/' as '';
646 final as double, action double_integer (Int $x) {
647 $ctx->response->body( $x * 2 );
650 final as double, action double_string (Str $x) {
651 $ctx->response->body( $x x 2 );
655 will return C<foofoo> when called as C</double/foo> and C<46> when called as
658 =head2 Actions and Method Modifiers
660 Method modifiers can not only be applied to methods, but also to actions. There
661 is no way yet to override the attributes of an already established action via
662 modifiers. However, you can modify the method underlying the action.
664 The following code is an example role modifying the consuming controller's
667 use CatalystX::Declare;
669 controller_role MyApp::Web::ControllerRole::RichBase {
671 before base (Object $ctx) {
672 $ctx->stash(something => $ctx->model('Item'));
676 Note that you have to specify the C<$ctx> argument yourself, since you are
677 modifying a method, not an action.
679 Any controller having a C<base> action (or method, for this purpose), can now
680 consume the C<RichBase> role declared above:
682 use CatalystX::Declare;
684 controller MyApp::Web::Controller::Foo
685 with MyApp::Web::Controller::RichBase {
687 action base as '' under '/';
689 action show, final under base {
690 $ctx->response->body(
691 $ctx->stash->{something}->render,
696 =head2 Action Classes
698 B<This option is even more experimental>
700 You might want to create an action with a different class than the usual
701 L<Catalyst::Action>. A usual suspect here is L<Catalyst::Action::RenderView>.
702 You can use the C<isa> option (did I mention it's experimental?) to specify
705 controller MyApp::Web::Controller::Root {
707 $CLASS->config(namespace => '');
709 action end isa RenderView;
712 The loaded class will be L<Moose>ified, so we are able to apply essential
719 =item L<MooseX::Declare::Syntax::KeywordHandling>
725 These methods are implementation details. Unless you are extending or
726 developing L<CatalystX::Declare>, you should not be concerned with them.
730 Object->parse (Object $ctx, Str :$modifier?, Int :$skipped_declarator = 0)
732 A hook that will be invoked by L<MooseX::Declare> when this instance is called
733 to handle syntax. It will parse the action declaration, prepare attributes and
734 add the actions to the controller.
740 =item L<CatalystX::Declare>
742 =item L<CatalystX::Declare::Keyword::Controller>
744 =item L<MooseX::Method::Signatures>
750 See L<CatalystX::Declare/AUTHOR> for author information.
754 This program is free software; you can redistribute it and/or modify it under
755 the same terms as perl itself.