File Coverage

blib/lib/Gentoo/ChangeLog/Parser/Eventual.pm
Criterion Covered Total %
statement 11 13 84.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 16 18 88.8


line stmt bran cond sub pod time code
1 1     1   33070 use strict;
  1         2  
  1         45  
2 1     1   5 use warnings;
  1         3  
  1         58  
3              
4             package Gentoo::ChangeLog::Parser::Eventual;
5             BEGIN {
6 1     1   31 $Gentoo::ChangeLog::Parser::Eventual::AUTHORITY = 'cpan:KENTNL';
7             }
8             {
9             $Gentoo::ChangeLog::Parser::Eventual::VERSION = '0.1.2';
10             }
11              
12             # ABSTRACT: Rudimentary Event-Based ChangeLog format parser, inspired by Pod::Eventual.
13              
14              
15              
16             {
17 1     1   6 use Carp qw( croak );
  1         2  
  1         77  
18 1     1   1733 use Moose;
  0            
  0            
19             use namespace::clean -except => 'meta';
20              
21             has _context => ( isa => 'Str', is => 'rw', default => 'pre-parse', );
22              
23             has _event_register => ( isa => 'ArrayRef[ CodeRef ]', is => 'rw', lazy_build => 1, );
24              
25              
26             has _callback => (
27             isa => 'CodeRef',
28             init_arg => 'callback',
29             is => 'rw',
30             required => 1,
31              
32             # lazy_build => 1,
33             traits => ['Code'],
34             'handles' => { handle_event => 'execute_method', },
35             );
36              
37             sub _event_data {
38             my ( $self, %other ) = @_;
39             return { content => $self->{WORKLINE}, %other, %{ $self->{PASSTHROUGH} } };
40             }
41              
42              
43             sub handle_line {
44             my ( $self, $line, $passthrough ) = @_;
45              
46             $passthrough ||= {};
47              
48             ## no critic ( ProhibitLocalVars )
49             local $self->{WORKLINE} = $line;
50             local $self->{PASSTHROUGH} = $passthrough;
51              
52             RULE: for my $event ( @{ $self->_event_register } ) {
53             local $_ = $self;
54              
55             my $result = $event->( $self, $line );
56              
57             next RULE if $result eq 'next';
58             return if $result eq 'fail';
59             return 1 if $result eq 'return';
60             croak(qq{Bad return $result});
61             }
62             return;
63             }
64              
65             sub _build__event_register {
66             return [
67             \&_event_start, \&_event_blank, \&_event_header_comment,
68             \&_event_header_end, \&_event_release_line, \&_event_change_header,
69             \&_event_begin_change_header, \&_event_continue_change_header, \&_event_end_change_header,
70             \&_event_change_body, \&_event_unknown
71             ];
72             }
73              
74             sub _build_callback {
75             croak(q{Not implementeted!. For now you MUST specify callback yourself});
76             }
77              
78              
79             my %EVENT_LIST = map { $_ => 1 } qw(
80             start
81             blank
82             header
83             header_comment
84             header_end
85              
86             release_line
87              
88             change_header
89             change_body
90             end_change_body
91              
92             begin_change_header
93             continue_change_header
94             end_change_header
95              
96             UNKNOWN
97             );
98              
99             before handle_event => sub {
100             croak("BAD EVENT $_[1]") if not exists $EVENT_LIST{ $_[1] };
101             };
102              
103             sub _event_start {
104             return 'next' if $_->_context ne 'pre-parse';
105             $_->handle_event( 'start' => $_->_event_data() );
106             $_->_context('document');
107             return 'next';
108             }
109              
110             sub _event_blank {
111             return 'next' if $_->{WORKLINE} !~ /^\s*$/;
112             $_->handle_event( 'blank' => $_->_event_data() );
113             return 'return';
114             }
115              
116             sub _event_header_comment {
117             return 'next' if $_->{WORKLINE} !~ /^#\s*/;
118             if ( $_->_context eq 'document' ) {
119             $_->handle_event( 'header' => $_->_event_data() );
120             $_->_context('header');
121             }
122             $_->handle_event( 'header_comment' => $_->_event_data() );
123             return 'return';
124             }
125              
126             sub _event_header_end {
127             return 'next'
128             if ( $_->_context() ne 'pre-parse' )
129             and ( $_->_context() ne 'header' );
130              
131             $_->handle_event( 'header_end' => $_->_event_data() );
132             $_->_context('body');
133             return 'next';
134             }
135              
136             sub _event_release_line {
137             return 'next'
138             if ( $_->_context() ne 'body' )
139             and ( $_->_context() ne 'changebody' );
140             return 'next' if $_->{WORKLINE} !~ /^\*/;
141             if ( $_->_context eq 'changebody' ) {
142             $_->handle_event( 'end_change_body' => $_->_event_data() );
143             $_->_context('body');
144             }
145              
146             $_->handle_event( 'release_line' => $_->_event_data() );
147             return 'return';
148             }
149              
150             sub _event_change_header {
151             return 'next' if ( $_->_context() ne 'body' ) and ( $_->_context() ne 'changebody' );
152             return 'next' if ( $_->{WORKLINE} !~ /^[ ]{2}\d\d?[ ][A-Z][a-z]+[ ]\d\d+;.*:\s*$/ );
153             if ( $_->_context eq 'changebody' ) {
154             $_->handle_event( 'end_change_body' => $_->_event_data() );
155             }
156             $_->handle_event( 'change_header' => $_->_event_data() );
157             $_->_context('changebody');
158             return 'return';
159             }
160              
161             sub _event_begin_change_header {
162             return 'next'
163             unless ( $_->_context() eq 'body' )
164             or ( $_->_context() eq 'changebody' );
165             return 'next' if ( $_->{WORKLINE} !~ /^[ ]{2}\d\d?[ ][A-Z][a-z]+[ ]\d\d+;.*$/ );
166             if ( $_->_context eq 'changebody' ) {
167             $_->handle_event( 'end_change_body' => $_->_event_data() );
168             }
169              
170             $_->handle_event( 'begin_change_header' => $_->_event_data() );
171             $_->_context('changeheader');
172             return 'return';
173             }
174              
175             sub _event_continue_change_header {
176             return 'next' unless $_->_context eq 'changeheader';
177             return 'next' if $_->{WORKLINE} =~ /:\s*$/;
178             $_->handle_event( 'continue_change_header' => $_->_event_data() );
179             return 'return';
180             }
181              
182             sub _event_end_change_header {
183             return 'next' unless $_->_context eq 'changeheader';
184             return 'next' unless $_->{WORKLINE} =~ /:\s*$/;
185             $_->handle_event( 'end_change_header' => $_->_event_data() );
186             $_->_context('changebody');
187             return 'return';
188             }
189              
190             sub _event_change_body {
191             return 'next' unless $_->_context eq 'changebody';
192             return 'next' unless $_->{WORKLINE} =~ /^[ ]{2}/;
193             $_->handle_event( 'change_body' => $_->_event_data() );
194             return 'return';
195             }
196              
197             sub _event_unknown {
198             $_->handle_event( 'UNKNOWN' => $_->_event_data() );
199             return 'return';
200             }
201             __PACKAGE__->meta->make_immutable;
202              
203             no Moose;
204             }
205              
206             1;
207              
208             __END__
209              
210             =pod
211              
212             =head1 NAME
213              
214             Gentoo::ChangeLog::Parser::Eventual - Rudimentary Event-Based ChangeLog format parser, inspired by Pod::Eventual.
215              
216             =head1 VERSION
217              
218             version 0.1.2
219              
220             =head1 SYNOPSIS
221              
222             use Gentoo::ChangeLog::Parser::Eventual
223             my $parser = Gentoo::ChangeLog::Parser::Eventual->new(
224             callback => sub {
225             my ( $parser, $event, $opts ) = @_ ;
226             },
227             );
228              
229             $parser->handle_line( "This is a line", { key => 'value', line => 1 });
230              
231             =head1 DESCRIPTION
232              
233             In the proceeds of making a ChangeLog parser, I kept getting stuck on various parts with writing it cleanly.
234              
235             This design, inspired by L<< RJBS' Great C<Pod::Eventual>|Pod::Eventual >>, greatly simplifies the process by using very rudimentary and loose
236             data validation.
237              
238             Lines are fed in manually, because we didn't want to implement all the File IO our self and didn't want to
239             limit the interface by forcing passing a file handle.
240              
241             You can do the IO quite simply anyway.
242              
243             while( my $line = <$fh> ){
244             chomp $line;
245             $parser->handle_line( $line , { line => $. } );
246             }
247              
248             A parser instance has a bit of state persistence, so you should use only 1 parser per input file.
249              
250             Currently, it can only detect a few basic things.
251              
252             =over 4
253              
254             =item 1. Header blocks.
255              
256             We go naive and classify that entire "# ChangeLog for " section at the top of a ChangeLog as a "Header".
257              
258             The header itself is not validated or parsed in any way beyond the notion that its a series of comments.
259              
260             =item 2. Release statements.
261              
262             Raises an event when it sees
263              
264             *perl-5.12.2 (10 Jun 2010)
265              
266             =item 3. Change Headers.
267              
268             This is the part on the top of each ChangeLog entry as follows:
269              
270             10 Jun 2010; Bob Smith <asd>:
271              
272             There are multiple ways this can be done however, so there are 3 events for this.
273              
274             =item 4. Change bodies.
275              
276             This is the part after the header.
277              
278             =item 5. Blank Lines.
279              
280             =back
281              
282             =head1 METHODS
283              
284             =head2 handle_line
285              
286             handle_line is the only public method on this object. It takes one line, processes
287             its own state a bit, works out what event(s) need to be thrown, and call the passed callback.
288              
289             =head3 Specification: $object->handle_line( Str $line, HashRef $opts )
290              
291             =head3 Parameter: $line : Mandatory, Str
292              
293             This must be a string, and this is the string that represents a singular line from the ChangeLog to be parsed.
294             This code is written under the assumption that you have also pre-chomped all your lines, but doesn't enforce it.
295             However, its not guaranteed to work, and is not tested for, and may in a future revision be enforced.
296              
297             =head3 Parameter: $opts : Mandatory, HashRef
298              
299             This is a HashRef of data to be sent through to the event handler.
300              
301             This is a good place to specify the source line number of the line you are currently parsing if you want that.
302              
303             $object->handle_line("this line", { line => 4 } );
304              
305             and then in the callback:
306              
307             my( $parser, $event, $opts ) = @_ ;
308             print $opts->{line} = 4;
309              
310             =head1 ATTRIBUTES
311              
312             =head2 _callback
313              
314             Outside construction and providing this (required) attribute, no public methods exist for
315             working with it.
316              
317             =head3 Specification: CodeRef, rw, required, init_arg => callback
318              
319             =head3 Construction.
320              
321             my $object = ::Elemental->new( callback => sub {
322             my( $parser, $event, $opts ) = @_ ;
323             .... event handler code here ....
324             });
325              
326             =head3 Parameter: $event : Str
327              
328             This is the name of the event that has been triggered. See L</EVENTS>.
329              
330             =head3 Parameter: $opts : HashRef
331              
332             This is a Hash Reference of data about the event. Mostly, it contains whatever data was passed
333             from L</handle_line>, but it injects its own 'content' key containing a copy of the string that was parsed.
334              
335             =head3 Executing.
336              
337             You can manually execute the CodeRef as if it were called internally, but there is little point to this.
338              
339             $object->handle_event( 'an-event-name' => { } );
340              
341             Note, that the event-names list is baked into this class, and manually calling this method and passing
342             an unsupported event name will result in casualties.
343              
344             =head1 EVENTS
345              
346             =head2 start
347              
348             Fires when the first line is parsed.
349              
350             =head2 blank
351              
352             Fires on blank ( i.e.: all white space ) lines.
353              
354             =head2 header
355              
356             Fires on the first header line.
357              
358             =head2 header_comment
359              
360             Fires on all comments that are deemed "part of the header"
361              
362             =head2 header_end
363              
364             Fires on the first line that is obviously not part of the header, terminating the header.
365              
366             =head2 release_line
367              
368             Fires on C<*perl-5.12.2> lines.
369              
370             =head2 change_header
371              
372             Fires on Single-line change headers.
373              
374             =head2 change_body
375              
376             Fires on each line that looks like it was a child of the previous change header.
377              
378             =head2 end_change_body
379              
380             Fires when the first line is seen that indicates the change body is complete.
381              
382             =head2 begin_change_header
383              
384             Fires on the first line of a multi-line change header.
385              
386             =head2 continue_change_header
387              
388             Fires on all non-blank lines in a multi-line change header other than the first and last.
389              
390             =head2 end_change_header
391              
392             Fires on the last line of a multi-line change header
393              
394             =head2 UNKNOWN
395              
396             Fires in the event no processing rules indicated a success state.
397              
398             =head1 AUTHOR
399              
400             Kent Fredric <kentnl@cpan.org>
401              
402             =head1 COPYRIGHT AND LICENSE
403              
404             This software is copyright (c) 2013 by Kent Fredric <kentnl@cpan.org>.
405              
406             This is free software; you can redistribute it and/or modify it under
407             the same terms as the Perl 5 programming language system itself.
408              
409             =cut