File Coverage

blib/lib/Test/Class/Sugar/Context.pm
Criterion Covered Total %
statement 6 125 4.8
branch 0 52 0.0
condition 0 5 0.0
subroutine 2 20 10.0
pod 5 17 29.4
total 13 219 5.9


line stmt bran cond sub pod time code
1             package Test::Class::Sugar::Context;
2             our $VERSION = '0.0300';
3 2     2   11 use base qw/Devel::Declare::Context::Simple/;
  2         4  
  2         192  
4              
5              
6 2     2   10 use Carp qw/croak/;
  2         3  
  2         3199  
7              
8             sub strip_test_desc_string {
9 0     0 0   my $self = shift;
10 0           $self->skipspace;
11              
12 0 0         return unless $self->looking_at(q{'});
13              
14 0           my $linestr = $self->get_linestr();
15 0           my $length = Devel::Declare::toke_scan_str($self->offset);
16 0           my $desc = Devel::Declare::get_lex_stuff();
17 0           Devel::Declare::clear_lex_stuff();
18 0 0         if ( $length < 0 ) {
19 0           $linestr .= $self->get_linestr();
20 0           $length = rindex($linestr, q{'}) - $self->offset + 1;
21             }
22             else {
23 0           $linestr = $self->get_linestr();
24             }
25              
26 0           substr($linestr, $self->offset, $length) = '';
27 0           $self->set_linestr($linestr);
28              
29 0           $desc =~ s/^\s+|\s$//g;
30 0           $desc =~ s/\s+/_/g;
31 0           $desc =~ s/\W+//g;
32 0           $desc =~ s/^(\d)/_$1/;
33              
34 0           return $desc
35             }
36              
37             sub strip_names {
38 0     0 0   my $self = shift;
39              
40 0           $self->skipspace;
41 0           my $declarator = $self->declarator;
42 0           my $name = $declarator;
43              
44 0 0         unless($self->looking_at('>>')) {
45 0           while (! $self->looking_at(qr/(?:{|>>)/,1) ) {
46 0 0         $self->looking_at(qr{\w}) or croak("I don't understand ", $self->peek_next_char);
47 0           $name .= ('_' . $self->strip_name);
48 0 0         croak "Expecting a simple name; try quoting it" unless defined $name;
49 0           $self->skipspace;
50             }
51             }
52 0 0         return if $name eq 'test';
53 0 0         if ($name eq $declarator) {
54 0           $name .= $self->get_curstash_name;
55 0           $name =~ s/::/_/g;
56             }
57 0           return $name;
58             }
59              
60             sub strip_test_name {
61 0     0 0   my $self = shift;
62 0           $self->skipspace;
63              
64 0   0       my $name = $self->strip_test_desc_string
65             || $self->strip_names
66             || return;
67              
68 0           return lc($name)
69             }
70              
71             sub looking_at {
72 0     0 1   my($self, $expected, $len) = @_;
73 0 0         unless (defined $len) {
74 0 0         $len = ref($expected) ? undef : length($expected);
75             }
76              
77 0 0         $expected = quotemeta($expected) unless ref($expected);
78              
79 0           my $buffer = $self->get_buffer;
80 0   0       while ($len && $len > length($buffer)) {
81 0           $buffer = $self->extend_buffer;
82             }
83              
84 0           $buffer =~ /^$expected/;
85             }
86              
87             sub peek_next_char {
88 0     0 0   my $self = shift;
89 0           my $buffer = $self->get_buffer;
90 0           return substr($buffer, 0, 1);
91             }
92              
93             sub strip_plan {
94 0     0 0   my $self = shift;
95 0           $self->skipspace;
96 0 0         return unless $self->strip_string('>>');
97              
98 0           $self->skipspace;
99              
100 0           my($plan) = $self->looking_at(qr/(\+?\d+|no_plan)/);
101 0           $self->strip_string($plan);
102 0           return $plan;
103             }
104              
105             sub strip_testclass_name {
106 0     0 0   my $self = shift;
107 0           $self->skipspace;
108              
109 0 0         ! $self->looking_at(qr/^(?:uses|ex(?:tends|ercises))/, 9)
110             && $self->strip_name;
111             }
112              
113             sub strip_options {
114 0     0 0   my $self = shift;
115 0           $self->skipspace;
116              
117 0           my %ret;
118              
119 0           while (!$self->looking_at(qr/[{"]/)) {
120 0 0         defined $self->strip_base_classes(\%ret) ? ()
    0          
    0          
121             : defined $self->strip_helper_classes(\%ret) ? ()
122             : defined $self->strip_class_under_test(\%ret) ? ()
123             : croak 'Expected option name';
124 0           $self->skipspace;
125             }
126              
127 0           return \%ret;
128             }
129              
130              
131             sub strip_class_under_test {
132 0     0 0   my($self, $opts) = @_;
133 0 0         return unless $self->strip_string('exercises');
134              
135 0 0         croak "testclass can only exercise one class" if $opts->{class_under_test};
136              
137 0           my $name = $self->strip_name;
138 0 0         croak "Expected a class name" unless defined $name;
139 0           $opts->{class_under_test} = $name;
140 0           return 1;
141             }
142              
143              
144             sub strip_helper_classes {
145 0     0 0   my($self, $opts) = @_;
146 0 0         return unless $self->strip_string('uses');
147              
148 0 0         $opts->{helpers} = [] unless defined $opts->{helpers};
149              
150 0           while (1) {
151 0           $self->skipspace;
152 0           my $helper = '';
153 0 0         if ($self->strip_string('-')) {
154 0           $helper .= 'Test::';
155             }
156              
157 0           my $name = $self->strip_name;
158 0           $helper .= $name;
159 0           push @{$opts->{helpers}}, $helper;
  0            
160 0 0         return 1 unless $self->strip_comma;
161             }
162             }
163              
164             sub strip_base_classes {
165 0     0 0   my($self, $ret) = @_;
166 0 0         return unless $self->strip_string('extends');
167              
168 0           while (1) {
169 0           $self->skipspace;
170              
171 0           my $baseclass = $self->strip_name;
172 0 0         croak 'expecting a base class' unless defined $baseclass;
173 0           $ret->{base} .= "$baseclass ";
174 0 0         return 1 unless $self->strip_comma;
175             }
176             }
177              
178             sub strip_comma {
179 0     0 0   my $self = shift;
180 0           $self->skipspace;
181 0           $self->strip_string(',');
182             }
183              
184             sub strip_string {
185 0     0 0   my($self, $expected) = @_;
186              
187 0 0         return unless $self->looking_at($expected);
188              
189 0     0     $self->alter_buffer(sub { s/^\Q$expected\E// });
  0            
190 0           return 1;
191             }
192              
193             sub alter_buffer {
194 0     0 1   my($self, $sub) = @_;
195              
196 0           local $_ = $self->get_buffer;
197 0           $sub->();
198 0           $self->set_buffer($_);
199             }
200              
201             sub get_buffer {
202 0     0 1   my $self = shift;
203 0           my $linestr = $self->get_linestr;
204 0           substr($linestr, $self->offset)
205             }
206              
207             sub set_buffer {
208 0     0 1   my($self, $new) = @_;
209              
210 0           my $linestr = $self->get_linestr;
211 0           substr($linestr, $self->offset) = $new;
212 0           $self->set_linestr($linestr);
213 0           return $new;
214             }
215              
216             sub extend_buffer {
217 0     0 1   my $self = shift;
218 0           my $buffer = $self->get_buffer;
219 0           $self->set_buffer('');
220 0           $self->skipspace;
221 0           $buffer .= $self->get_buffer;
222 0           $self->set_buffer($buffer);
223             }
224              
225             1;
226             __END__
227              
228             =head1 NAME
229              
230             Test::Class::Sugar::Context - Pay no attention to the class behind the curtain
231              
232             =head1 DESCRIPTION
233              
234             Test::Class::Sugar::Context does most of the heavy lifting for
235             Test::Class::Sugar's parser. No user serviceable parts inside and all that.
236              
237             However, if you're writing your own module using L<Devel::Declare> and, like I
238             was, you're looking at other D::D client modules to lift ideas from, then you
239             probably want to take a look at the following selected methods:
240              
241             =over
242              
243             =item B<looking_at($expected, $len)>
244              
245             Look at the unparsed buffer and returns true if it
246             matches C<$expected>. Given a C<$len> argument, looking_at first makes sure
247             that there are at least $len characters in the buffer.
248              
249             =item B<get_buffer>, B<set_buffer>
250              
251             Getters and setters. Like B<get_linestr> and B<set_linestr> but, rather than
252             return the whole C<linestr>, they only return the unparsed bit of it. If you
253             too are sick of writing C<< substr($ctx->get_linestr, $ctx->offset) >>, then
254             these are the methods for you.
255              
256             =item B<alter_buffer(CODE)>
257              
258             It works like this:
259              
260             $ctx->alter_buffer(sub { s/bibble// }
261              
262             Obvious no?
263              
264             B<alter_buffer> temporarily copies the buffer into C<$_>, then calls the
265             coderef you pass in, then writes the new value of C<$_> back into the
266             buffer. It's not quite the same as having a fully mutable buffer, but it'll
267             just have to serve.
268              
269             =item B<extend_buffer>
270              
271             Grabs the next linestr and appends it to the buffer.
272              
273             =back
274              
275             =head1 DIAGNOSTICS
276              
277             Only kidding. Right now the diagnostics suck harder than a thing that sucks
278             very hard indeed. One of these days I'll work out how to have a parser fail
279             gracefully with meaningful diagnostics, but today is not that day.
280              
281             =head1 BUGS AND LIMITATIONS
282              
283             There's bound to be some. Patches welcome.
284              
285             Please report any bugs or feature requests to me. It's unlikely you'll get any
286             response if you use L<http://rt.cpan.org> though. Your best course of action
287             is to fork the project L<http://www.github.com/pdcawley/test-class-sugar>,
288             write at least one failing test (Write something in C<testclass> form that
289             should work, but doesn't. If you can arrange for it to fail gracefully, then
290             please do, but if all you do is write something that blows up spectacularly,
291             that's good too. Failing/exploding tests are like manna to a maintenance
292             programmer.
293              
294             =head1 AUTHOR
295              
296             Piers Cawley C<< <pdcawley@bofh.org.uk> >>
297              
298             =head1 LICENCE AND COPYRIGHT
299              
300             Copyright (c) 2009, Piers Cawley C<< <pdcawley@bofh.org.uk> >>. All rights reserved.
301              
302             This module is free software; you can redistribute it and/or
303             modify it under the same terms as Perl itself. See L<perlartistic>.
304              
305              
306             =head1 DISCLAIMER OF WARRANTY
307              
308             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
309             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
310             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
311             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
312             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
313             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
314             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
315             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
316             NECESSARY SERVICING, REPAIR, OR CORRECTION.
317              
318             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
319             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
320             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
321             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
322             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
323             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
324             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
325             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
326             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
327             SUCH DAMAGES.