Annotation of todotxt/Text-Todo/lib/Text/Todo.pm, Revision 1.6
1.1 andrew 1: package Text::Todo;
2:
1.6 ! andrew 3: # $RedRiver: Todo.pm,v 1.5 2010/01/09 05:00:21 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 ) {
1.6 ! andrew 136: croak q{todo file can't be found};
1.5 andrew 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 ) {
1.6 ! andrew 163: croak q{todo file can't be found};
1.5 andrew 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);
1.6 ! andrew 179:
1.2 andrew 180: return if !$list_of{$ident};
1.6 ! andrew 181: return wantarray ? @{ $list_of{$ident} } : $list_of{$ident};
1.5 andrew 182: }
183:
184: sub listpri {
185: my ($self) = @_;
186:
187: my @list = grep { $_->priority } $self->list;
188:
189: return wantarray ? @list : \@list;
1.2 andrew 190: }
1.1 andrew 191:
1.3 andrew 192: sub add {
193: my ( $self, $entry ) = @_;
194: my $ident = ident($self);
195:
1.5 andrew 196: if ( !ref $entry ) {
197: $entry = Text::Todo::Entry->new($entry);
198: }
199: elsif ( ref $entry ne 'Text::Todo::Entry' ) {
200: croak(
201: 'entry is a ' . ref($entry) . ' not a Text::Todo::Entry!' );
202: }
203:
204: push @{ $list_of{$ident} }, $entry;
205:
206: return $entry;
207: }
208:
1.6 ! andrew 209: sub del {
1.5 andrew 210: my ( $self, $src ) = @_;
211: my $ident = ident($self);
212:
1.6 ! andrew 213: my $id = $self->_find_entry_id($src);
1.5 andrew 214:
215: my @list = $self->list;
1.6 ! andrew 216: my $entry = splice @list, $id, 1;
1.5 andrew 217: $list_of{$ident} = \@list;
218:
219: return $entry;
220: }
221:
222: sub move {
223: my ( $self, $entry, $dst ) = @_;
224: my $ident = ident($self);
225:
226: my $src = $self->_find_entry_id($entry);
227: my @list = $self->list;
228:
1.6 ! andrew 229: splice @list, $dst, 0, splice @list, $src, 1;
1.5 andrew 230:
231: $list_of{$ident} = \@list;
232:
233: return 1;
234: }
235:
1.6 ! andrew 236: sub listproj {
1.5 andrew 237: my ( $self, $entry, $dst ) = @_;
238: my $ident = ident($self);
239:
240: my %available_projects;
1.6 ! andrew 241: foreach my $e ( $self->list ) {
1.5 andrew 242: foreach my $p ( $e->projects ) {
243: $available_projects{$p} = 1;
244: }
245: }
246:
247: my @projects = sort keys %available_projects;
248:
249: return wantarray ? @projects : \@projects;
250: }
251:
1.6 ! andrew 252: sub archive { carp "unsupported\n"; return }
! 253: sub addto { carp "unsupported\n"; return }
! 254: sub listfile { carp "unsupported\n"; return }
1.5 andrew 255:
256: sub _find_entry_id {
257: my ( $self, $entry ) = @_;
258: my $ident = ident($self);
259:
1.3 andrew 260: if ( ref $entry ) {
261: if ( ref $entry ne 'Text::Todo::Entry' ) {
262: croak( 'entry is a '
263: . ref($entry)
264: . ' not a Text::Todo::Entry!' );
265: }
1.5 andrew 266:
267: my @list = $self->list;
268: foreach my $id ( 0 .. $#list ) {
269: if ( $list[$id] eq $entry ) {
270: return $id;
271: }
272: }
1.3 andrew 273: }
1.5 andrew 274: elsif ( $entry =~ /^\d+$/xms ) {
275: return $entry;
1.3 andrew 276: }
277:
1.5 andrew 278: croak "Invalid entry [$entry]!";
1.3 andrew 279: }
1.2 andrew 280: }
1.1 andrew 281:
1.2 andrew 282: 1; # Magic true value required at end of module
1.1 andrew 283: __END__
284:
285: =head1 NAME
286:
1.4 andrew 287: Text::Todo - Perl interface to todo_txt files
1.1 andrew 288:
1.6 ! andrew 289: =head1 VERSION
! 290:
! 291: I will have to figure out how to include $VERSION in this somehow.
! 292:
! 293: Perhaps RCS Id is good enough?
! 294:
! 295: $Id$
1.1 andrew 296:
297: =head1 SYNOPSIS
298:
299: use Text::Todo;
300:
301: =head1 DESCRIPTION
302:
1.4 andrew 303: For more information see L<http://todotxt.com>
1.1 andrew 304:
305: =head1 INTERFACE
306:
1.2 andrew 307: =head2 new
308:
309: =head2 load
310:
311: =head2 save
312:
1.3 andrew 313: =head2 file
314:
1.2 andrew 315: =head2 list
1.3 andrew 316:
317: =head2 add
1.1 andrew 318:
319: =head1 DIAGNOSTICS
320:
321: =for author to fill in:
322: List every single error and warning message that the module can
323: generate (even the ones that will "never happen"), with a full
324: explanation of each problem, one or more likely causes, and any
325: suggested remedies.
326:
327: =over
328:
329: =item C<< Error message here, perhaps with %s placeholders >>
330:
331: [Description of error here]
332:
333: =item C<< Another error message here >>
334:
335: [Description of error here]
336:
337: [Et cetera, et cetera]
338:
339: =back
340:
341:
342: =head1 CONFIGURATION AND ENVIRONMENT
343:
344: Text::Todo requires no configuration files or environment variables.
345:
1.4 andrew 346: Someday it should be able to read and use the todo.sh config file.
347:
1.1 andrew 348:
349: =head1 DEPENDENCIES
350:
351: =for author to fill in:
352: A list of all the other modules that this module relies upon,
353: including any restrictions on versions, and an indication whether
354: the module is part of the standard Perl distribution, part of the
355: module's distribution, or must be installed separately. ]
356:
357: None.
358:
359:
360: =head1 INCOMPATIBILITIES
361:
362: None reported.
363:
364:
365: =head1 BUGS AND LIMITATIONS
366:
367: No bugs have been reported.
368:
369: Please report any bugs or feature requests to
370: C<bug-text-todo@rt.cpan.org>, or through the web interface at
371: L<http://rt.cpan.org>.
372:
373:
374: =head1 AUTHOR
375:
376: Andrew Fresh C<< <andrew@cpan.org> >>
377:
378:
379: =head1 LICENSE AND COPYRIGHT
380:
381: Copyright (c) 2009, Andrew Fresh C<< <andrew@cpan.org> >>. All rights reserved.
382:
383: This module is free software; you can redistribute it and/or
384: modify it under the same terms as Perl itself. See L<perlartistic>.
385:
386:
387: =head1 DISCLAIMER OF WARRANTY
388:
389: BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
390: FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
391: OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
392: PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
393: EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
394: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
395: ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
396: YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
397: NECESSARY SERVICING, REPAIR, OR CORRECTION.
398:
399: IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
400: WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
401: REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
402: LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
403: OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
404: THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
405: RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
406: FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
407: SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
408: SUCH DAMAGES.
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>