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