3 class CatalystX::Declarative::Keyword::Action
4 with MooseX::Declare::Syntax::KeywordHandling {
8 use Perl6::Junction qw( any );
9 use Data::Dump qw( pp );
10 use MooseX::Types::Util qw( has_available_type_export );
15 use constant STOP_PARSING => '__MXDECLARE_STOP_PARSING__';
16 use constant UNDER_VAR => '$CatalystX::Declarative::SCOPE::UNDER';
18 use aliased 'MooseX::Method::Signatures::Meta::Method';
19 use aliased 'MooseX::MethodAttributes::Role::Meta::Method', 'AttributeRole';
22 method parse (Object $ctx) {
24 # somewhere to put the attributes
27 my $skipped_declarator;
30 until (do { $ctx->skipspace; $ctx->peek_next_char } eq any qw( ; { } )) {
35 if ($ctx->peek_next_char eq ',') {
37 my $linestr = $ctx->get_linestr;
38 substr($linestr, $ctx->offset, 1) = '';
39 $ctx->set_linestr($linestr);
44 # next thing should be an option name
49 $ctx->skip_declarator;
50 $skipped_declarator++;
53 or croak "Expected option token, not " . substr($ctx->get_linestr, $ctx->offset);
55 # we need to be able to handle the rest
56 my $handler = $self->can("_handle_${option}_option")
57 or croak "Unknown action option: $option";
60 my $populator = $self->$handler($ctx, \%attributes);
62 if ($populator and $populator eq STOP_PARSING) {
64 return $ctx->shadow(sub (&) {
70 push @populators, $populator
71 if defined $populator;
74 croak "Need an action specification"
75 unless exists $attributes{Signature};
77 my $name = $attributes{Subname};
78 my $method = Method->wrap(
79 signature => qq{($attributes{Signature})},
80 package_name => $ctx->get_curstash_name,
84 AttributeRole->meta->apply($method);
89 $attributes{PathPart} ||= "'$name'";
91 delete $attributes{CaptureArgs}
92 if exists $attributes{Args};
94 $attributes{CaptureArgs} = 0
95 unless exists $attributes{Args}
96 or exists $attributes{CaptureArgs};
98 if ($ctx->peek_next_char eq '{') {
99 $ctx->inject_if_block($ctx->scope_injector_call . $method->injectable_code);
102 $ctx->inject_code_parts_here(
104 $ctx->scope_injector_call,
105 $method->injectable_code,
109 my @attributes = map {
113 ref($attributes{ $_ }) eq 'ARRAY'
114 ? join(' ', @{ $attributes{ $_ } })
120 return $ctx->shadow(sub (&) {
123 $method->_set_actual_body(shift);
124 $method->{attributes} = \@attributes;
126 $class->meta->add_method($name, $method);
127 $class->meta->register_method_attributes($class->can($method->name), \@attributes);
131 method _handle_with_option (Object $ctx, HashRef $attrs) {
133 my $role = $ctx->strip_name
134 or croak "Expected bareword role specification for action after with";
136 # we need to fish for aliases here since we are still unclean
137 if (defined(my $alias = $self->_check_for_available_import($ctx, $role))) {
141 push @{ $attrs->{CatalystX_Declarative_ActionRoles} ||= [] }, $role;
146 method _handle_isa_option (Object $ctx, HashRef $attrs) {
148 my $class = $ctx->strip_name
149 or croak "Expected bareword action class specification for action after isa";
151 if (defined(my $alias = $self->_check_for_available_import($ctx, $class))) {
155 $attrs->{CatalystX_Declarative_ActionClass} = $class;
160 method _check_for_available_import (Object $ctx, Str $name) {
162 if (my $code = $ctx->get_curstash_name->can($name)) {
169 method _handle_action_option (Object $ctx, HashRef $attrs) {
172 my $name = $ctx->strip_name
173 or croak "Anonymous actions not yet supported";
176 my $proto = $ctx->strip_proto || '';
177 $proto = join(', ', 'Object $self: Object $ctx', $proto || ());
179 $attrs->{Subname} = $name;
180 $attrs->{Signature} = $proto;
182 if (defined $CatalystX::Declarative::SCOPE::UNDER) {
183 $attrs->{Chained} ||= $CatalystX::Declarative::SCOPE::UNDER;
189 method _handle_final_option (Object $ctx, HashRef $attrs) {
191 return $self->_build_flag_populator($ctx, $attrs, 'final');
194 method _handle_is_option (Object $ctx, HashRef $attrs) {
196 my $what = $ctx->strip_name
197 or croak "Expected symbol token after is symbol, not " . substr($ctx->get_linestr, $ctx->offset);
199 return $self->_build_flag_populator($ctx, $attrs, $what);
202 method _build_flag_populator (Object $ctx, HashRef $attrs, Str $what) {
207 if ($what eq any qw( end endpoint final )) {
208 my $count = $self->_count_positional_arguments($method);
209 $attrs->{Args} = defined($count) ? $count : '';
211 elsif ($what eq 'private') {
212 $attrs->{Private} = 1;
217 method _handle_under_option (Object $ctx, HashRef $attrs) {
219 my $target = $self->_strip_actionpath($ctx);
222 if ($ctx->peek_next_char eq '{' and $self->identifier eq 'under') {
223 $ctx->inject_if_block(
224 sprintf '%s; local %s; BEGIN { %s = qq(%s) };',
225 $ctx->scope_injector_call,
233 $attrs->{Chained} = "'$target'";
238 my $count = $self->_count_positional_arguments($method);
239 $attrs->{CaptureArgs} = $count
244 method _handle_chains_option (Object $ctx, HashRef $attrs) {
247 $ctx->strip_name eq 'to'
248 or croak "Expected to token after chains symbol, not " . substr($ctx->get_linestr, $ctx->offset);
250 return $self->_handle_under_option($ctx, $attrs);
253 method _handle_as_option (Object $ctx, HashRef $attrs) {
257 my $path = $self->_strip_actionpath($ctx);
258 $attrs->{PathPart} = "'$path'";
263 method _count_positional_arguments (Object $method) {
264 my $signature = $method->_parsed_signature;
266 if ($signature->has_positional_params) {
267 my $count = @{ scalar($signature->positional_params) };
269 if ($count and ($signature->positional_params)[-1]->sigil eq '@') {
279 method _strip_actionpath (Object $ctx) {
282 my $linestr = $ctx->get_linestr;
283 my $rest = substr($linestr, $ctx->offset);
285 if ($rest =~ /^ ( [_a-z] [_a-z0-9]* ) \b/ix) {
286 substr($linestr, $ctx->offset, length($1)) = '';
287 $ctx->set_linestr($linestr);
290 elsif ($rest =~ /^ ' ( (?:[.:;,_a-z0-9]|\/)* ) ' /ix) {
291 substr($linestr, $ctx->offset, length($1) + 2) = '';
292 $ctx->set_linestr($linestr);
296 croak "Invalid syntax for action path: $rest";