Annotation of todotxt/Text-Todo-REST-API/lib/Text/Todo/REST/API.pm, Revision 1.9
1.1 andrew 1: package Text::Todo::REST::API;
2:
1.9 ! andrew 3: # $AFresh1: API.pm,v 1.8 2010/01/19 03:18:34 andrew Exp $
1.1 andrew 4:
5: use warnings;
6: use strict;
7: use Carp;
8:
1.2 andrew 9: use Text::Todo;
1.1 andrew 10:
1.5 andrew 11: use Class::Std::Utils;
1.8 andrew 12: use Digest::MD5 qw/ md5_hex /;
13:
1.2 andrew 14: use version; our $VERSION = qv('0.0.1');
1.1 andrew 15:
1.2 andrew 16: {
1.5 andrew 17: my @attr_refs = \(
18: my %todo_of,
1.6 andrew 19:
1.5 andrew 20: my %suffix_of,
21: my %file_regex_of,
1.6 andrew 22:
1.8 andrew 23: my %format_of,
1.6 andrew 24:
1.5 andrew 25: my %action_handlers,
26: );
1.2 andrew 27:
1.5 andrew 28: sub new {
29: my ( $class, $options ) = @_;
30:
1.8 andrew 31: my $self = bless anon_scalar(), $class;
32: my $ident = ident($self);
33:
34: $format_of{$ident} = $options->{default_format};
1.2 andrew 35: if ( $options->{format} ) {
1.8 andrew 36: $format_of{$ident} = $options->{format};
1.2 andrew 37: }
38:
1.8 andrew 39: $suffix_of{$ident} = $options->{suffix} || '.txt';
1.2 andrew 40:
41: $file_regex_of{$ident} = $options->{file_regex} || qr{
42: .*
43: todo
44: .*
45: \Q$suffix_of{$ident}\E
46: $
47: }ixms;
48:
1.8 andrew 49: eval {
50: $todo_of{$ident} = Text::Todo->new(
51: { todo_dir => $options->{todo_dir},
52: todo_file => $options->{todo_file},
53: }
54: );
55: };
1.7 andrew 56: if ($@) {
1.8 andrew 57: $self->fail( 'Unable to create Text::Todo object' . $@ );
1.2 andrew 58: }
59:
1.5 andrew 60: return $self;
1.2 andrew 61: }
62:
1.9 ! andrew 63: sub _parse_options {
! 64: my ( $self, $method, @args ) = @_;
! 65:
! 66: my %options = (
! 67: method => lc $method,
! 68: list => '',
! 69: action => 'files',
! 70: args => [],
! 71: );
! 72:
! 73: if (@args) {
! 74: if ( !ref $args[0] ) {
! 75: $options{path} = shift @args;
! 76: }
! 77:
! 78: if ( ref $args[0] eq 'HASH' ) {
! 79: my $opts = shift @args;
! 80: foreach my $o ( keys %{$opts} ) {
! 81: $options{$o} = $opts->{$o};
! 82: }
! 83: }
! 84: }
1.2 andrew 85:
1.9 ! andrew 86: if ( exists $options{path} ) {
! 87: my %opts = $self->_split_path( $options{path} );
! 88: delete $options{path};
! 89:
! 90: foreach my $o ( keys %opts ) {
! 91: if ( defined $opts{$o} ) {
! 92: $options{$o} = $opts{$o};
! 93: }
! 94: }
1.2 andrew 95: }
96:
1.9 ! andrew 97: if ($options{action} eq 'entry' && @{ $options{args} }) {
! 98: $options{entry} = shift @{ $options{args} };
! 99: if (@{ $options{args} }) {
! 100: $options{action} = lc shift @{ $options{args} };
! 101: }
! 102: }
1.4 andrew 103:
1.9 ! andrew 104: push @{ $options{args} }, @args;
1.2 andrew 105:
1.9 ! andrew 106: $options{list} = defined $options{list} ? $options{list} : 'todo_file';
1.8 andrew 107:
1.9 ! andrew 108: if ( $options{format} ) {
! 109: $format_of{ ident $self } = $options{format};
! 110: delete $options{format};
! 111: }
1.8 andrew 112:
1.9 ! andrew 113: my @method;
! 114: foreach my $o qw( method action ) {
! 115: if ( $options{$o} ) {
! 116: push @method, $options{$o};
1.8 andrew 117: }
118: }
1.9 ! andrew 119: $method = join q{_}, @method;
1.8 andrew 120:
1.9 ! andrew 121: return $method, %options;
1.2 andrew 122: }
123:
1.9 ! andrew 124: sub _handle_action {
1.8 andrew 125: my ( $self, @args ) = @_;
1.9 ! andrew 126:
! 127: my ($method, %options) = $self->_parse_options(@args);
! 128:
! 129: my $todo = $self->_todo;
! 130: $todo->load( $options{list} );
! 131:
! 132: foreach my $class ( $self ) {
! 133: if ($class->can($method) ) {
! 134: return $class->$method( $todo, \%options );
! 135: }
! 136: }
! 137:
! 138: return $self->fail( 'Unable to handle [' . $method . ']' );
1.2 andrew 139: }
140:
1.9 ! andrew 141: sub _split_path {
! 142: my ( $self, $path ) = @_;
! 143:
! 144: my %options = (
! 145: list => undef,
! 146: action => undef,
! 147: args => [],
! 148: );
! 149:
! 150: $path = defined $path ? $path : q{};
! 151: $path =~ s{^/}{}xms;
! 152:
! 153: if ( $path =~ s/\.(\w+)$//xms ) {
! 154: $options{format} = $1;
! 155: }
! 156:
! 157: ( $options{list}, $options{action}, @{ $options{args} } ) = split '/',
! 158: $path;
! 159:
! 160: if ( $options{list} ) {
! 161: $options{action} ||= 'list';
! 162:
! 163: my $suffix = $self->_suffix;
! 164:
! 165: if (( lc $options{list} ) eq 'files') {
! 166: $options{action} = lc $options{list};
! 167: $options{list} = q{};
! 168: }
! 169: elsif ($self->_todo->file( $options{list} )) {
! 170: $options{list} = $self->_todo->file( $options{list} );
! 171: }
! 172: }
1.2 andrew 173:
1.9 ! andrew 174: if ( @{ $options{args} } && ( lc $options{args}[0] ) eq 'entry' ) {
! 175: $options{action} = lc shift @{ $options{args} };
1.2 andrew 176: }
177:
1.9 ! andrew 178: return %options;
1.3 andrew 179: }
180:
181: sub GET {
182: my ( $self, @args ) = @_;
183: return $self->_handle_action( 'GET', @args );
1.2 andrew 184: }
185:
186: sub get_entry {
1.9 ! andrew 187: my ( $self, $todo, $key ) = @_;
1.2 andrew 188:
189: if ( !$key ) {
190: return $self->fail("get_entry requires arguments");
191: }
192: elsif ( ref $key eq 'ARRAY' ) {
193: my @entries;
194: foreach ( @{$key} ) {
195: push @entries, $self->get_entry($_);
196: }
197: return @entries;
198: }
1.9 ! andrew 199: elsif ( ref $key eq 'HASH' ) {
! 200: if (exists $key->{entry}) {
! 201: $key = $key->{entry};
! 202: }
! 203: else {
! 204: return $self->fail('get_entry requires key [entry]');
! 205: }
! 206: }
1.2 andrew 207:
1.9 ! andrew 208: my @list = $self->get_list($todo);
1.2 andrew 209:
210: my $entry;
211: if ( $key =~ /^[[:xdigit:]]{32}$/xms ) {
212: my $search = lc $key;
213:
214: ENTRY: foreach my $e (@list) {
215: if ( $search eq $e->{md5} ) {
216: $entry = $e;
217: last ENTRY;
218: }
219: }
220: }
221: elsif ( $key =~ /^\d+$/xms ) {
222: $entry = $list[ $key - 1 ];
223: }
224:
225: if ( !$entry ) {
226: return $self->fail("Unable to find entry!");
227: }
228:
229: return $entry;
230: }
231:
232: sub get_list {
1.9 ! andrew 233: my ($self, $todo) = @_;
1.2 andrew 234:
235: my $line = 1;
236: return map ( {
237: line => $line++,
238: md5 => md5_hex( $_->text ),
239: text => $_->text,
240: },
1.9 ! andrew 241: $todo->list );
1.2 andrew 242: }
243:
244: sub get_files {
1.9 ! andrew 245: my ($self, $todo) = @_;
! 246: my $dir = $todo->file('todo_dir');
1.2 andrew 247:
248: if ( !$dir ) {
249: return $self->fail('Unable to find todo_dir');
250: }
251:
252: my $file_regex = $self->_file_regex;
253:
254: opendir my $dh, $dir or croak "Couldn't opendir: $!";
255: my @files = grep {m/$file_regex/xms} readdir $dh;
256: closedir $dh;
257:
258: return @files;
259: }
260:
261: sub get_tags {
1.9 ! andrew 262: my ( $self, $todo, $tag ) = @_;
! 263: return $todo->listtag($tag);
1.2 andrew 264: }
265:
1.5 andrew 266: sub POST {
1.3 andrew 267: my ( $self, @args ) = @_;
268: return $self->_handle_action( 'POST', @args );
269: }
270:
1.5 andrew 271: sub PUT {
1.3 andrew 272: my ( $self, @args ) = @_;
273: return $self->_handle_action( 'PUT', @args );
274: }
275:
276: sub DELETE {
277: my ( $self, @args ) = @_;
278: return $self->_handle_action( 'DELETE', @args );
279: }
1.2 andrew 280:
281: sub fail {
282: my ( $self, @message ) = @_;
283: croak(@message);
284: }
285:
286: sub _todo { my ($self) = @_; return $todo_of{ ident $self }; }
287: sub _suffix { my ($self) = @_; return $suffix_of{ ident $self}; }
288: sub _file_regex { my ($self) = @_; return $file_regex_of{ ident $self}; }
1.8 andrew 289: sub _format { my ($self) = @_; return $format_of{ ident $self}; }
1.1 andrew 290:
1.5 andrew 291: sub DESTROY {
292: my ($self) = @_;
293: my $ident = ident $self;
294: foreach my $attr_ref (@attr_refs) {
295: delete $attr_ref->{$ident};
296: }
297: }
1.2 andrew 298: }
299: 1; # Magic true value required at end of module
1.1 andrew 300: __END__
301:
302: =head1 NAME
303:
304: Text::Todo::REST::API - [One line description of module's purpose here]
305:
306:
307: =head1 VERSION
308:
309: This document describes Text::Todo::REST::API version 0.0.1
310:
311:
312: =head1 SYNOPSIS
313:
314: use Text::Todo::REST::API;
315:
316: =for author to fill in:
317: Brief code example(s) here showing commonest usage(s).
318: This section will be as far as many users bother reading
319: so make it as educational and exeplary as possible.
320:
321:
322: =head1 DESCRIPTION
323:
324: =for author to fill in:
325: Write a full description of the module and its features here.
326: Use subsections (=head2, =head3) as appropriate.
327:
328:
329: =head1 INTERFACE
330:
331: =for author to fill in:
332: Write a separate section listing the public components of the modules
333: interface. These normally consist of either subroutines that may be
334: exported, or methods that may be called on objects belonging to the
335: classes provided by the module.
336:
337:
338: =head1 DIAGNOSTICS
339:
340: =for author to fill in:
341: List every single error and warning message that the module can
342: generate (even the ones that will "never happen"), with a full
343: explanation of each problem, one or more likely causes, and any
344: suggested remedies.
345:
346: =over
347:
348: =item C<< Error message here, perhaps with %s placeholders >>
349:
350: [Description of error here]
351:
352: =item C<< Another error message here >>
353:
354: [Description of error here]
355:
356: [Et cetera, et cetera]
357:
358: =back
359:
360:
361: =head1 CONFIGURATION AND ENVIRONMENT
362:
363: =for author to fill in:
364: A full explanation of any configuration system(s) used by the
365: module, including the names and locations of any configuration
366: files, and the meaning of any environment variables or properties
367: that can be set. These descriptions must also include details of any
368: configuration language used.
369:
370: Text::Todo::REST::API requires no configuration files or environment variables.
371:
372:
373: =head1 DEPENDENCIES
374:
375: =for author to fill in:
376: A list of all the other modules that this module relies upon,
377: including any restrictions on versions, and an indication whether
378: the module is part of the standard Perl distribution, part of the
379: module's distribution, or must be installed separately. ]
380:
381: None.
382:
383:
384: =head1 INCOMPATIBILITIES
385:
386: =for author to fill in:
387: A list of any modules that this module cannot be used in conjunction
388: with. This may be due to name conflicts in the interface, or
389: competition for system or program resources, or due to internal
390: limitations of Perl (for example, many modules that use source code
391: filters are mutually incompatible).
392:
393: None reported.
394:
395:
396: =head1 BUGS AND LIMITATIONS
397:
398: =for author to fill in:
399: A list of known problems with the module, together with some
400: indication Whether they are likely to be fixed in an upcoming
401: release. Also a list of restrictions on the features the module
402: does provide: data types that cannot be handled, performance issues
403: and the circumstances in which they may arise, practical
404: limitations on the size of data sets, special cases that are not
405: (yet) handled, etc.
406:
407: No bugs have been reported.
408:
409: Please report any bugs or feature requests to
410: C<bug-text-todo-rest-api@rt.cpan.org>, or through the web interface at
411: L<http://rt.cpan.org>.
412:
413:
414: =head1 AUTHOR
415:
416: Andrew Fresh C<< <andrew@cpan.org> >>
417:
418:
419: =head1 LICENSE AND COPYRIGHT
420:
421: Copyright (c) 2010, Andrew Fresh C<< <andrew@cpan.org> >>. All rights reserved.
422:
423: This module is free software; you can redistribute it and/or
424: modify it under the same terms as Perl itself. See L<perlartistic>.
425:
426:
427: =head1 DISCLAIMER OF WARRANTY
428:
429: BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
430: FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
431: OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
432: PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
433: EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
434: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
435: ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
436: YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
437: NECESSARY SERVICING, REPAIR, OR CORRECTION.
438:
439: IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
440: WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
441: REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
442: LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
443: OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
444: THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
445: RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
446: FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
447: SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
448: SUCH DAMAGES.
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>