first rudimentary implementation
[catagits/CatalystX-Declare.git] / lib / CatalystX / Declarative / Keyword / Action.pm
CommitLineData
918fb36e 1use MooseX::Declare;
2
3class CatalystX::Declarative::Keyword::Action
4 with MooseX::Declare::Syntax::KeywordHandling {
5
6
7 use Carp qw( croak );
8 use Perl6::Junction qw( any );
9 use Data::Dump qw( pp );
10
11 use aliased 'MooseX::Method::Signatures::Meta::Method';
12 use aliased 'MooseX::MethodAttributes::Role::Meta::Method', 'AttributeRole';
13
14
15 method parse (Object $ctx) {
16
17 # somewhere to put the attributes
18 my %attributes;
19 my @populators;
20 my $skipped_declarator;
21
22 # parse declarations
23 until (do { $ctx->skipspace; $ctx->peek_next_char } eq any qw( ; { } )) {
24 warn "LINESTR[" . pp($ctx->get_linestr) . "]\n";
25
26 $ctx->skipspace;
27
28 # optional commas
29 if ($ctx->peek_next_char eq ',') {
30
31 my $linestr = $ctx->get_linestr;
32 substr($linestr, $ctx->offset, 1) = '';
33 $ctx->set_linestr($linestr);
34
35 next;
36 }
37
38 # next thing should be an option name
39 my $option = (
40 $skipped_declarator
41 ? $ctx->strip_name
42 : do {
43 $ctx->skip_declarator;
44 $skipped_declarator++;
45 $ctx->declarator;
46 })
47 or croak "Expected option token, not " . substr($ctx->get_linestr, $ctx->offset);
48
49 # we need to be able to handle the rest
50 my $handler = $self->can("_handle_${option}_option")
51 or croak "Unknown action option: $option";
52
53 # call the handler
54 push @populators, $self->$handler($ctx, \%attributes);
55 }
56
57 croak "Need an action specification"
58 unless exists $attributes{Signature};
59
60 my $name = $attributes{Subname};
61 my $method = Method->wrap(
62 signature => qq{($attributes{Signature})},
63 package_name => $ctx->get_curstash_name,
64 name => $name,
65 );
66
67 $_->($method)
68 for @populators;
69
70 $attributes{PathPart} ||= "'$name'";
71
72 delete $attributes{CaptureArgs}
73 if exists $attributes{Args};
74
75 $attributes{CaptureArgs} = 0
76 unless exists $attributes{Args}
77 or exists $attributes{CaptureArgs};
78
79 if ($ctx->peek_next_char eq '{') {
80 $ctx->inject_if_block($ctx->scope_injector_call . $method->injectable_code);
81 }
82 else {
83 $ctx->inject_code_parts_here(
84 sprintf '{ %s%s }',
85 $ctx->scope_injector_call,
86 $method->injectable_code,
87 );
88 }
89
90 pp \%attributes;
91
92 AttributeRole->meta->apply($method);
93
94 my @attributes = map {
95 join('',
96 $_,
97 sprintf('(%s)', $attributes{ $_ }),
98 );
99 } keys %attributes;
100
101 return $ctx->shadow(sub (&) {
102 my $class = caller;
103
104 $method->_set_actual_body(shift);
105 $method->{attributes} = \@attributes;
106
107 $class->meta->add_method($name, $method);
108 $class->meta->register_method_attributes($class->can($method->name), \%attributes);
109 });
110 }
111
112 method _handle_action_option (Object $ctx, HashRef $attrs) {
113
114 # action name
115 my $name = $ctx->strip_name
116 or croak "Anonymous actions not yet supported";
117
118 # signature
119 my $proto = $ctx->strip_proto || '';
120 $proto = join(', ', 'Object $self: Object $ctx', $proto || ());
121
122 $attrs->{Subname} = $name;
123 $attrs->{Signature} = $proto;
124
125 return;
126 }
127
128 method _handle_is_option (Object $ctx, HashRef $attrs) {
129
130 my $what = $ctx->strip_name
131 or croak "Expected symbol token after is symbol, not " . substr($ctx->get_linestr, $ctx->offset);
132
133 return sub {
134 my $method = shift;
135
136 if ($what eq any qw( end endpoint final )) {
137 my $count = $self->_count_positional_arguments($method);
138 $attrs->{Args} = defined($count) ? $count : '';
139 }
140 elsif ($what eq 'private') {
141 $attrs->{Private} = 1;
142 }
143 };
144 }
145
146 method _handle_under_option (Object $ctx, HashRef $attrs) {
147
148 my $target = $self->_strip_actionpath($ctx);
149 $attrs->{Chained} = "'$target'";
150
151 return sub {
152 my $method = shift;
153
154 my $count = $self->_count_positional_arguments($method);
155 $attrs->{CaptureArgs} = $count
156 if defined $count;
157 };
158 }
159
160 method _handle_chains_option (Object $ctx, HashRef $attrs) {
161
162 $ctx->skipspace;
163 $ctx->strip_name eq 'to'
164 or croak "Expected to token after chains symbol, not " . substr($ctx->get_linestr, $ctx->offset);
165
166 return $self->_handle_under_option($ctx, $attrs);
167 }
168
169 method _handle_as_option (Object $ctx, HashRef $attrs) {
170
171 $ctx->skipspace;
172
173 my $path = $self->_strip_actionpath($ctx);
174 $attrs->{PathPart} = "'$path'";
175
176 return;
177 }
178
179 method _count_positional_arguments (Object $method) {
180 my $signature = $method->_parsed_signature;
181
182 if ($signature->has_positional_params) {
183 my $count = @{ scalar($signature->positional_params) };
184
185 if ($count and ($signature->positional_params)[-1]->sigil eq '@') {
186 return undef;
187 }
188
189 return $count - 1;
190 }
191
192 return 0;
193 }
194
195 method _strip_actionpath (Object $ctx) {
196
197 $ctx->skipspace;
198 my $linestr = $ctx->get_linestr;
199 my $rest = substr($linestr, $ctx->offset);
200
201 if ($rest =~ /^ ( [_a-z] [_a-z0-9]* ) \b/ix) {
202 substr($linestr, $ctx->offset, length($1)) = '';
203 $ctx->set_linestr($linestr);
204 return $1;
205 }
206 elsif ($rest =~ /^ ' ( (?:[a-z0-9]|\/)* ) ' /ix) {
207 substr($linestr, $ctx->offset, length($1) + 2) = '';
208 $ctx->set_linestr($linestr);
209 return $1;
210 }
211 else {
212 croak "Invalid syntax for action path: $rest";
213 }
214 }
215}
216
217
218