Annotation of todotxt/Text-Todo/lib/Text/Todo.pm, Revision 1.9
1.1 andrew 1: package Text::Todo;
2:
1.7 andrew 3: # $RedRiver: Todo.pm,v 1.6 2010/01/09 05:15:20 andrew Exp $
1.1 andrew 4:
5: use warnings;
6: use strict;
7: use Carp;
8:
1.2 andrew 9: use Class::Std::Utils;
10: use Text::Todo::Entry;
1.5 andrew 11: use File::Spec;
12:
13: use Data::Dumper;
1.2 andrew 14:
1.1 andrew 15: use version; our $VERSION = qv('0.0.1');
16:
1.2 andrew 17: {
18:
1.5 andrew 19: my %path_of;
1.2 andrew 20: my %list_of;
1.8 andrew 21: my %loaded_of;
1.1 andrew 22:
1.2 andrew 23: sub new {
1.5 andrew 24: my ( $class, $options ) = @_;
1.1 andrew 25:
1.2 andrew 26: my $self = bless anon_scalar(), $class;
27: my $ident = ident($self);
28:
1.5 andrew 29: $path_of{$ident} = {
30: todo_dir => undef,
31: todo_file => 'todo.txt',
32: done_file => undef,
33: report_file => undef,
34: };
35:
36: if ($options) {
37: if ( ref $options eq 'HASH' ) {
38: foreach my $opt ( keys %{$options} ) {
39: if ( exists $path_of{$ident}{$opt} ) {
40: $self->_path_to( $opt, $options->{$opt} );
41: }
42: else {
43: carp "Invalid option [$opt]";
44: }
45: }
46: }
47: else {
48: if ( -d $options ) {
49: $self->_path_to( 'todo_dir', $options );
50: }
51: elsif ( $options =~ /\.txt$/ixms ) {
52: $self->_path_to( 'todo_file', $options );
53: }
54: else {
55: carp "Unknown options [$options]";
56: }
57: }
58: }
59:
60: my $file = $self->_path_to('todo_file');
61: if ( defined $file && -e $file ) {
62: $self->load();
63: }
1.2 andrew 64:
65: return $self;
66: }
67:
1.5 andrew 68: sub _path_to {
69: my ( $self, $type, $path ) = @_;
70: my $ident = ident($self);
71:
72: if ( $type eq 'todo_dir' ) {
73: if ($path) {
74: $path_of{$ident}{$type} = $path;
75: }
76: return $path_of{$ident}{$type};
77: }
78:
79: if ($path) {
80: my ( $volume, $directories, $file )
81: = File::Spec->splitpath($path);
82: $path_of{$ident}{$type} = $file;
83:
84: if ($volume) {
85: $directories = File::Spec->catdir( $volume, $directories );
86: }
87:
88: # XXX Should we save complete paths to each file, mebbe only if
89: # the dirs are different?
90: if ($directories) {
91: $path_of{$ident}{todo_dir} = $directories;
92: }
93: }
94:
95: if ( $type =~ /(todo|done|report)_file/xms ) {
96: if ( my ( $pre, $post )
97: = $path_of{$ident}{$type} =~ /^(.*)$1(.*)\.txt$/ixms )
98: {
99: foreach my $f qw( todo done report ) {
100: if ( !defined $path_of{$ident}{ $f . '_file' } ) {
101: $path_of{$ident}{ $f . '_file' }
102: = $pre . $f . $post . '.txt';
103: }
104: }
105: }
106: }
107:
108: if ( defined $path_of{$ident}{todo_dir} ) {
109: return File::Spec->catfile( $path_of{$ident}{todo_dir},
110: $path_of{$ident}{$type} );
111: }
112:
113: return;
114: }
115:
1.3 andrew 116: sub file {
1.2 andrew 117: my ( $self, $file ) = @_;
118: my $ident = ident($self);
119:
1.5 andrew 120: if ( defined $file && exists $path_of{$ident}{$file} ) {
121: $file = $self->_path_to($file);
122: }
123: else {
124: $file = $self->_path_to( 'todo_file', $file );
1.2 andrew 125: }
126:
1.5 andrew 127: return $file;
1.3 andrew 128: }
129:
130: sub load {
131: my ( $self, $file ) = @_;
132: my $ident = ident($self);
133:
1.8 andrew 134: $loaded_of{$ident} = undef;
135:
1.9 ! andrew 136: $file = $self->file($file);
! 137:
1.8 andrew 138: if ( $list_of{$ident} = $self->listfile($file) ) {
139: $loaded_of{$ident} = $file;
140: return 1;
141: }
142:
143: return;
144: }
145:
146: sub listfile {
147: my ( $self, $file ) = @_;
148:
1.5 andrew 149: $file = $self->file($file);
150:
151: if ( !defined $file ) {
1.8 andrew 152: carp q{file can't be found};
153: return;
1.5 andrew 154: }
155:
156: if ( !-e $file ) {
1.8 andrew 157: carp "file [$file] does not exist";
1.5 andrew 158: return;
159: }
1.2 andrew 160:
161: my @list;
162: open my $fh, '<', $file or croak "Couldn't open [$file]: $!";
163: while (<$fh>) {
164: s/\r?\n$//xms;
1.8 andrew 165: next if !length $_;
1.2 andrew 166: push @list, Text::Todo::Entry->new($_);
167: }
168: close $fh or croak "Couldn't close [$file]: $!";
169:
1.8 andrew 170: return wantarray ? @list : \@list;
1.2 andrew 171: }
172:
173: sub save {
174: my ( $self, $file ) = @_;
175: my $ident = ident($self);
176:
1.5 andrew 177: $file = $self->file($file);
178: if ( !defined $file ) {
1.6 andrew 179: croak q{todo file can't be found};
1.5 andrew 180: }
1.2 andrew 181:
182: open my $fh, '>', $file or croak "Couldn't open [$file]: $!";
183: foreach my $e ( @{ $list_of{$ident} } ) {
1.3 andrew 184: print {$fh} $e->text . "\n"
185: or croak "Couldn't print to [$file]: $!";
1.2 andrew 186: }
187: close $fh or croak "Couldn't close [$file]: $!";
188:
1.9 ! andrew 189: $loaded_of{$ident} = $file;
! 190:
1.2 andrew 191: return 1;
192: }
193:
194: sub list {
1.3 andrew 195: my ($self) = @_;
1.2 andrew 196: my $ident = ident($self);
1.6 andrew 197:
1.2 andrew 198: return if !$list_of{$ident};
1.6 andrew 199: return wantarray ? @{ $list_of{$ident} } : $list_of{$ident};
1.5 andrew 200: }
201:
202: sub listpri {
203: my ($self) = @_;
204:
205: my @list = grep { $_->priority } $self->list;
206:
207: return wantarray ? @list : \@list;
1.2 andrew 208: }
1.1 andrew 209:
1.3 andrew 210: sub add {
211: my ( $self, $entry ) = @_;
212: my $ident = ident($self);
213:
1.5 andrew 214: if ( !ref $entry ) {
215: $entry = Text::Todo::Entry->new($entry);
216: }
217: elsif ( ref $entry ne 'Text::Todo::Entry' ) {
218: croak(
219: 'entry is a ' . ref($entry) . ' not a Text::Todo::Entry!' );
220: }
221:
222: push @{ $list_of{$ident} }, $entry;
223:
224: return $entry;
225: }
226:
1.6 andrew 227: sub del {
1.5 andrew 228: my ( $self, $src ) = @_;
229: my $ident = ident($self);
230:
1.6 andrew 231: my $id = $self->_find_entry_id($src);
1.5 andrew 232:
233: my @list = $self->list;
1.6 andrew 234: my $entry = splice @list, $id, 1;
1.5 andrew 235: $list_of{$ident} = \@list;
236:
237: return $entry;
238: }
239:
240: sub move {
241: my ( $self, $entry, $dst ) = @_;
242: my $ident = ident($self);
243:
244: my $src = $self->_find_entry_id($entry);
245: my @list = $self->list;
246:
1.6 andrew 247: splice @list, $dst, 0, splice @list, $src, 1;
1.5 andrew 248:
249: $list_of{$ident} = \@list;
250:
251: return 1;
252: }
253:
1.6 andrew 254: sub listproj {
1.5 andrew 255: my ( $self, $entry, $dst ) = @_;
256: my $ident = ident($self);
257:
258: my %available_projects;
1.6 andrew 259: foreach my $e ( $self->list ) {
1.5 andrew 260: foreach my $p ( $e->projects ) {
261: $available_projects{$p} = 1;
262: }
263: }
264:
265: my @projects = sort keys %available_projects;
266:
267: return wantarray ? @projects : \@projects;
268: }
269:
1.9 ! andrew 270: sub archive {
! 271: my ($self) = @_;
! 272: my $ident = ident($self);
! 273:
! 274: if ( !defined $loaded_of{$ident}
! 275: || $loaded_of{$ident} ne $self->file('todo_file') )
! 276: {
! 277: carp 'todo_file not loaded';
! 278: return;
! 279: }
! 280:
! 281: my $archived = 0;
! 282: ENTRY: foreach my $e ( $self->list ) {
! 283: if ( $e->done ) {
! 284: if ( $self->addto( 'done_file', $e ) && $self->del($e) ) {
! 285: $archived++;
! 286: }
! 287: else {
! 288: carp q{Couldn't archive entry [} . $e->text . ']';
! 289: last ENTRY;
! 290: }
! 291: }
! 292: }
! 293:
! 294: if ($archived) {
! 295: $self->save;
! 296: }
! 297:
! 298: return $archived;
! 299: }
1.8 andrew 300:
301: sub addto {
302: my ( $self, $file, $entry ) = @_;
303: my $ident = ident($self);
304:
305: $file = $self->file($file);
306: if ( !defined $file ) {
307: croak q{file can't be found};
308: }
309:
1.9 ! andrew 310: if ( ref $entry ) {
! 311: if ( ref $entry eq 'Text::Todo::Entry' ) {
! 312: $entry = $entry->text;
! 313: }
! 314: else {
! 315: carp 'Unknown ref [' . ref($entry) . ']';
! 316: return;
! 317: }
! 318: }
! 319:
1.8 andrew 320: open my $fh, '>>', $file or croak "Couldn't open [$file]: $!";
321: print {$fh} $entry, "\n"
322: or croak "Couldn't print to [$file]: $!";
323: close $fh or croak "Couldn't close [$file]: $!";
324:
325: if ( defined $loaded_of{$ident} && $file eq $loaded_of{$ident} ) {
326: return $self->load($file);
327: }
328:
329: return 1;
330: }
1.5 andrew 331:
332: sub _find_entry_id {
333: my ( $self, $entry ) = @_;
334: my $ident = ident($self);
335:
1.3 andrew 336: if ( ref $entry ) {
337: if ( ref $entry ne 'Text::Todo::Entry' ) {
338: croak( 'entry is a '
339: . ref($entry)
340: . ' not a Text::Todo::Entry!' );
341: }
1.5 andrew 342:
343: my @list = $self->list;
344: foreach my $id ( 0 .. $#list ) {
345: if ( $list[$id] eq $entry ) {
346: return $id;
347: }
348: }
1.3 andrew 349: }
1.5 andrew 350: elsif ( $entry =~ /^\d+$/xms ) {
351: return $entry;
1.3 andrew 352: }
353:
1.5 andrew 354: croak "Invalid entry [$entry]!";
1.3 andrew 355: }
1.2 andrew 356: }
1.1 andrew 357:
1.2 andrew 358: 1; # Magic true value required at end of module
1.1 andrew 359: __END__
360:
361: =head1 NAME
362:
1.4 andrew 363: Text::Todo - Perl interface to todo_txt files
1.1 andrew 364:
1.6 andrew 365: =head1 VERSION
366:
367: I will have to figure out how to include $VERSION in this somehow.
368:
369: Perhaps RCS Id is good enough?
370:
1.7 andrew 371: $Id: Todo.pm,v 1.6 2010/01/09 05:15:20 andrew Exp $
1.1 andrew 372:
373: =head1 SYNOPSIS
374:
375: use Text::Todo;
376:
377: =head1 DESCRIPTION
378:
1.4 andrew 379: For more information see L<http://todotxt.com>
1.1 andrew 380:
381: =head1 INTERFACE
382:
1.2 andrew 383: =head2 new
384:
1.9 ! andrew 385: =head2 file
! 386:
1.2 andrew 387: =head2 load
388:
389: =head2 save
390:
1.9 ! andrew 391: =head2 list
! 392:
! 393: =head2 listpri
1.3 andrew 394:
1.9 ! andrew 395: =head2 listproj
1.3 andrew 396:
397: =head2 add
1.9 ! andrew 398:
! 399: =head2 del
! 400:
! 401: =head2 move
! 402:
! 403: =head2 archive
! 404:
! 405: =head2 addto
! 406:
! 407: =head2 listfile
1.1 andrew 408:
409: =head1 DIAGNOSTICS
410:
411: =for author to fill in:
412: List every single error and warning message that the module can
413: generate (even the ones that will "never happen"), with a full
414: explanation of each problem, one or more likely causes, and any
415: suggested remedies.
416:
417: =over
418:
419: =item C<< Error message here, perhaps with %s placeholders >>
420:
421: [Description of error here]
422:
423: =item C<< Another error message here >>
424:
425: [Description of error here]
426:
427: [Et cetera, et cetera]
428:
429: =back
430:
431:
432: =head1 CONFIGURATION AND ENVIRONMENT
433:
434: Text::Todo requires no configuration files or environment variables.
435:
1.4 andrew 436: Someday it should be able to read and use the todo.sh config file.
437:
1.1 andrew 438:
439: =head1 DEPENDENCIES
440:
441: =for author to fill in:
442: A list of all the other modules that this module relies upon,
443: including any restrictions on versions, and an indication whether
444: the module is part of the standard Perl distribution, part of the
445: module's distribution, or must be installed separately. ]
446:
447: None.
448:
449:
450: =head1 INCOMPATIBILITIES
451:
452: None reported.
453:
454:
455: =head1 BUGS AND LIMITATIONS
456:
457: No bugs have been reported.
458:
459: Please report any bugs or feature requests to
460: C<bug-text-todo@rt.cpan.org>, or through the web interface at
461: L<http://rt.cpan.org>.
462:
463:
464: =head1 AUTHOR
465:
466: Andrew Fresh C<< <andrew@cpan.org> >>
467:
468:
469: =head1 LICENSE AND COPYRIGHT
470:
471: Copyright (c) 2009, Andrew Fresh C<< <andrew@cpan.org> >>. All rights reserved.
472:
473: This module is free software; you can redistribute it and/or
474: modify it under the same terms as Perl itself. See L<perlartistic>.
475:
476:
477: =head1 DISCLAIMER OF WARRANTY
478:
479: BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
480: FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
481: OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
482: PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
483: EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
484: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
485: ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
486: YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
487: NECESSARY SERVICING, REPAIR, OR CORRECTION.
488:
489: IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
490: WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
491: REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
492: LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
493: OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
494: THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
495: RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
496: FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
497: SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
498: SUCH DAMAGES.
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>