[BACK]Return to Todo.pm CVS log [TXT][DIR] Up to [local] / todotxt / Text-Todo / lib / Text

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>