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

Diff for /todotxt/Text-Todo/lib/Text/Todo.pm between version 1.3 and 1.23

version 1.3, 2010/01/06 19:54:56 version 1.23, 2010/01/22 01:30:45
Line 1 
Line 1 
 package Text::Todo;  package Text::Todo;
   
 # $RedRiver: Todo.pm,v 1.2 2009/07/10 22:26:14 andrew Exp $  # $AFresh1: Todo.pm,v 1.22 2010/01/19 18:53:36 andrew Exp $
   
 use warnings;  use warnings;
 use strict;  use strict;
Line 8 
Line 8 
   
 use Class::Std::Utils;  use Class::Std::Utils;
 use Text::Todo::Entry;  use Text::Todo::Entry;
   use File::Spec;
   
 use version; our $VERSION = qv('0.0.1');  use version; our $VERSION = qv('0.1.1');
   
 {  {
   
     my %file_of;      my @attr_refs = \(
     my %list_of;          my %path_of,
   
           my %list_of,
           my %loaded_of,
       );
   
     sub new {      sub new {
         my ( $class, $file ) = @_;          my ( $class, $options ) = @_;
   
         my $self = bless anon_scalar(), $class;          my $self = bless anon_scalar(), $class;
         my $ident = ident($self);          my $ident = ident($self);
   
         if ($file) { $self->load($file); }          $path_of{$ident} = {
               todo_dir  => undef,
               todo_file => 'todo.txt',
               done_file => undef,
           };
   
           if ($options) {
               if ( ref $options eq 'HASH' ) {
                   foreach my $opt ( keys %{$options} ) {
                       if ( exists $path_of{$ident}{$opt} ) {
                           $self->_path_to( $opt, $options->{$opt} );
                       }
                       else {
   
                           #carp "Invalid option [$opt]";
                       }
                   }
               }
               else {
                   if ( -d $options ) {
                       $self->_path_to( 'todo_dir', $options );
                   }
                   elsif ( $options =~ /\.txt$/ixms ) {
                       $self->_path_to( 'todo_file', $options );
                   }
                   else {
                       carp "Unknown options [$options]";
                   }
               }
           }
   
           my $file = $self->_path_to('todo_file');
           if ( defined $file && -e $file ) {
               $self->load();
           }
   
         return $self;          return $self;
     }      }
   
       sub _path_to {
           my ( $self, $type, $path ) = @_;
           my $ident = ident($self);
   
           if ( $type eq 'todo_dir' ) {
               if ($path) {
                   $path_of{$ident}{$type} = $path;
               }
               return $path_of{$ident}{$type};
           }
   
           if ($path) {
               my ( $volume, $directories, $file )
                   = File::Spec->splitpath($path);
               $path_of{$ident}{$type} = $file;
   
               if ($volume) {
                   $directories = File::Spec->catdir( $volume, $directories );
               }
   
               # XXX Should we save complete paths to each file, mebbe only if
               # the dirs are different?
               if ($directories) {
                   $path_of{$ident}{todo_dir} = $directories;
               }
           }
   
           if ( $type =~ /(todo|done|report)_file/xms ) {
               if ( my ( $pre, $post )
                   = $path_of{$ident}{$type} =~ /^(.*)$1(.*)\.txt$/ixms )
               {
                   foreach my $f qw( todo done report ) {
                       if ( !defined $path_of{$ident}{ $f . '_file' } ) {
                           $path_of{$ident}{ $f . '_file' }
                               = $pre . $f . $post . '.txt';
                       }
                   }
               }
           }
   
           if ( defined $path_of{$ident}{todo_dir} ) {
               return File::Spec->catfile( $path_of{$ident}{todo_dir},
                   $path_of{$ident}{$type} );
           }
   
           return;
       }
   
     sub file {      sub file {
         my ( $self, $file ) = @_;          my ( $self, $file ) = @_;
         my $ident = ident($self);          my $ident = ident($self);
   
         if ($file) {          if ( defined $file && exists $path_of{$ident}{$file} ) {
             $file_of{$ident} = $file;              $file = $self->_path_to($file);
         }          }
           else {
               $file = $self->_path_to( 'todo_file', $file );
           }
   
         return $file_of{$ident};          return $file;
     }      }
   
     sub load {      sub load {
         my ( $self, $file ) = @_;          my ( $self, $file ) = @_;
         my $ident = ident($self);          my $ident = ident($self);
   
         $file = $self->file($file) || croak 'load requires a filename';          $loaded_of{$ident} = undef;
   
           $file = $self->file($file);
   
           if ( $list_of{$ident} = $self->listfile($file) ) {
               $loaded_of{$ident} = $file;
               return 1;
           }
   
           return;
       }
   
       sub listfile {
           my ( $self, $file ) = @_;
   
           $file = $self->file($file);
   
           if ( !defined $file ) {
               carp q{file can't be found};
               return;
           }
   
           if ( !-e $file ) {
               carp "file [$file] does not exist";
               return;
           }
   
         my @list;          my @list;
         open my $fh, '<', $file or croak "Couldn't open [$file]: $!";          open my $fh, '<', $file or croak "Couldn't open [$file]: $!";
         while (<$fh>) {          while (<$fh>) {
Line 51 
Line 166 
             push @list, Text::Todo::Entry->new($_);              push @list, Text::Todo::Entry->new($_);
         }          }
         close $fh or croak "Couldn't close [$file]: $!";          close $fh or croak "Couldn't close [$file]: $!";
         $list_of{$ident} = \@list;  
   
         return 1;          return wantarray ? @list : \@list;
     }      }
   
     sub save {      sub save {
         my ( $self, $file ) = @_;          my ( $self, $file ) = @_;
         my $ident = ident($self);          my $ident = ident($self);
   
         $file = $self->file($file) || croak 'save requires a filename';          $file = $self->file($file);
           if ( !defined $file ) {
               croak q{todo file can't be found};
           }
   
         open my $fh, '>', $file or croak "Couldn't open [$file]: $!";          open my $fh, '>', $file or croak "Couldn't open [$file]: $!";
         foreach my $e ( @{ $list_of{$ident} } ) {          foreach my $e ( @{ $list_of{$ident} } ) {
Line 69 
Line 186 
         }          }
         close $fh or croak "Couldn't close [$file]: $!";          close $fh or croak "Couldn't close [$file]: $!";
   
           $loaded_of{$ident} = $file;
   
         return 1;          return 1;
     }      }
   
     sub list {      sub list {
         my ($self) = @_;          my ($self) = @_;
         my $ident = ident($self);          my $ident = ident($self);
   
         return if !$list_of{$ident};          return if !$list_of{$ident};
           return wantarray ? @{ $list_of{$ident} } : $list_of{$ident};
       }
   
         return $list_of{$ident};      sub listpri {
           my ( $self, $pri ) = @_;
   
         #my $id = 1;          my @list;
         #my @l;          if ($pri) {
         #foreach my $e ( @{ $list_of{$ident} } ) {              $pri = uc $pri;
         #    push @l, $e; #{ %{$e}, id => $id };              if ( $pri !~ /^[A-Z]$/xms ) {
         #    $id++;                  croak 'PRIORITY must a single letter from A to Z.';
         #}              }
         #              @list = grep { defined $_->priority && $_->priority eq $pri }
         #my @list = sort { $a->priority cmp $b->priority }                  $self->list;
         #    grep { defined $_->priority } @l;          }
         #          else {
         #push @list, grep { !defined $_->priority } @l;              @list = grep { $_->priority } $self->list;
         #          }
         #return \@list;  
           return wantarray ? @list : \@list;
     }      }
   
     sub add {      sub add {
         my ( $self, $entry ) = @_;          my ( $self, $entry ) = @_;
         my $ident = ident($self);          my $ident = ident($self);
   
           if ( !ref $entry ) {
               $entry = Text::Todo::Entry->new($entry);
           }
           elsif ( ref $entry ne 'Text::Todo::Entry' ) {
               croak(
                   'entry is a ' . ref($entry) . ' not a Text::Todo::Entry!' );
           }
   
           push @{ $list_of{$ident} }, $entry;
   
           return $entry;
       }
   
       sub del {
           my ( $self, $src ) = @_;
           my $ident = ident($self);
   
           my $id = $self->_find_entry_id($src);
   
           my @list = $self->list;
           my $entry = splice @list, $id, 1;
           $list_of{$ident} = \@list;
   
           return $entry;
       }
   
       sub move {
           my ( $self, $entry, $dst ) = @_;
           my $ident = ident($self);
   
           my $src  = $self->_find_entry_id($entry);
           my @list = $self->list;
   
           splice @list, $dst, 0, splice @list, $src, 1;
   
           $list_of{$ident} = \@list;
   
           return 1;
       }
   
       sub listproj {
           my ($self) = @_;
           return $self->listtag('project');
       }
   
       sub listcon {
           my ($self) = @_;
           return $self->listtag('context');
       }
   
       sub listtag {
           my ( $self, $tag ) = @_;
           my $ident = ident($self);
   
           my $accessor = $tag . 's';
   
           my %available;
           foreach my $e ( $self->list ) {
               foreach my $p ( $e->$accessor ) {
                   $available{$p} = 1;
               }
           }
   
           my @tags = sort keys %available;
   
           return wantarray ? @tags : \@tags;
       }
   
       sub archive {
           my ($self) = @_;
           my $ident = ident($self);
   
           if ( !defined $loaded_of{$ident}
               || $loaded_of{$ident} ne $self->file('todo_file') )
           {
               carp 'todo_file not loaded';
               return;
           }
   
           my $changed = 0;
       ENTRY: foreach my $e ( $self->list ) {
               if ( $e->done ) {
                   if ( $self->addto( 'done_file', $e ) && $self->del($e) ) {
                       $changed++;
                   }
                   else {
                       carp q{Couldn't archive entry [} . $e->text . ']';
                       last ENTRY;
                   }
               }
               elsif ( $e->text eq q{} ) {
                   if ( $self->del($e) ) {
                       $changed++;
                   }
                   else {
                       carp q{Couldn't delete blank entry};
                       last ENTRY;
                   }
               }
           }
   
           if ($changed) {
               $self->save;
           }
   
           return $changed;
       }
   
       sub addto {
           my ( $self, $file, $entry ) = @_;
           my $ident = ident($self);
   
           $file = $self->file($file);
           if ( !defined $file ) {
               croak q{file can't be found};
           }
   
         if ( ref $entry ) {          if ( ref $entry ) {
               if ( ref $entry eq 'Text::Todo::Entry' ) {
                   $entry = $entry->text;
               }
               else {
                   carp 'Unknown ref [' . ref($entry) . ']';
                   return;
               }
           }
   
           open my $fh, '>>', $file or croak "Couldn't open [$file]: $!";
           print {$fh} $entry, "\n"
               or croak "Couldn't print to [$file]: $!";
           close $fh or croak "Couldn't close [$file]: $!";
   
           if ( defined $loaded_of{$ident} && $file eq $loaded_of{$ident} ) {
               return $self->load($file);
           }
   
           return 1;
       }
   
       sub _find_entry_id {
           my ( $self, $entry ) = @_;
           my $ident = ident($self);
   
           if ( ref $entry ) {
             if ( ref $entry ne 'Text::Todo::Entry' ) {              if ( ref $entry ne 'Text::Todo::Entry' ) {
                 croak(    'entry is a '                  croak(    'entry is a '
                         . ref($entry)                          . ref($entry)
                         . ' not a Text::Todo::Entry!' );                          . ' not a Text::Todo::Entry!' );
             }              }
   
               my @list = $self->list;
               foreach my $id ( 0 .. $#list ) {
                   if ( $list[$id] eq $entry ) {
                       return $id;
                   }
               }
         }          }
         else {          elsif ( $entry =~ /^\d+$/xms ) {
             $entry = Text::Todo::Entry->new($entry);              return $entry;
         }          }
   
         push @{ $list_of{$ident} }, $entry;          croak "Invalid entry [$entry]!";
       }
   
         return $entry;      sub DESTROY {
           my ($self) = @_;
           my $ident = ident $self;
   
           foreach my $attr_ref (@attr_refs) {
               delete $attr_ref->{$ident};
           }
   
           return;
     }      }
 }  }
   
Line 120 
Line 403 
   
 =head1 NAME  =head1 NAME
   
 Text::Todo - [One line description of module's purpose here]  Text::Todo - Perl interface to todo_txt files
   
   
 =head1 VERSION  =head1 VERSION
   
 This document describes Text::Todo version 0.0.1  Since the $VERSION can't be automatically included,
   here is the RCS Id instead, you'll have to look up $VERSION.
   
       $Id$
   
 =head1 SYNOPSIS  =head1 SYNOPSIS
   
     use Text::Todo;      use Text::Todo;
   
       my $todo = Text::Todo->new('todo/todo.txt');
   
 =for author to fill in:      foreach my $e (sort { lc($_->text) cmp lc($e->text)} $todo->list) {
     Brief code example(s) here showing commonest usage(s).          print $e->text, "\n";
     This section will be as far as many users bother reading      }
     so make it as educational and exeplary as possible.  
   
   
 =head1 DESCRIPTION  =head1 DESCRIPTION
   
 =for author to fill in:  This module is a basic interface to the todo.txt files as described by
     Write a full description of the module and its features here.  Lifehacker and extended by members of their community.
     Use subsections (=head2, =head3) as appropriate.  
   
   For more information see L<http://todotxt.com>
   
 =head1 INTERFACE  This module supports the 3 axes of an effective todo list.
   Priority, Project and Context.
   
 =for author to fill in:  It does not support other notations or many of the more advanced features of
     Write a separate section listing the public components of the modules  the todo.sh like plugins.
     interface. These normally consist of either subroutines that may be  
     exported, or methods that may be called on objects belonging to the  
     classes provided by the module.  
   
   It should be extensible, but and hopefully will be before a 1.0 release.
   
   
   =head1 INTERFACE
   
 =head2 new  =head2 new
   
       new({
           [ todo_dir    => 'directory', ]
           [ todo_file   => 'filename in todo_dir', ]
           [ done_file   => 'filename in todo_dir', ]
           [ report_file => 'filename in todo_dir', ]
           });
   
   Allows you to set each item individually.  todo_file defaults to todo.txt.
   
       new('path/to/todo.txt');
   
   Automatically sets todo_dir to 'path/to', todo_file to 'todo.txt'
   
       new('path/to')
   
   If you pass an existing directory to new, it will set todo_dir.
   
   
   If you what you set matches (.*)todo(.*).txt it will automatically set
   done_file to $1done$2.txt
   and
   report_file to $1report$2.txt.
   
   For example, new('todo/todo.shopping.txt') will set
   todo_dir to 'todo',
   todo_file to 'todo.shopping.txt',
   done_file to 'done.shopping.txt',
   and
   report_file to 'report.shopping.txt'.
   
   =head2 file
   
   Allows you to read the paths to the files in use.
   If as in the SYNOPSIS above you used $todo = new('todo/todo.txt').
   
       $todo_file = $todo->file('todo_file');
   
   then, $todo_file eq 'todo/todo.txt'
   
 =head2 load  =head2 load
   - Reads a list from a file into the current object.
   
   Allows you to load a different file into the object.
   
       $todo->load('done_file');
   
   This effects the other functions that act on the list.
   
 =head2 save  =head2 save
   - Writes the list to disk.
   
 =head2 file      $todo->save(['new/path/to/todo']);
   
   Either writes the current working file or the passed in argument
   that can be recognized by file().
   
   If you specify a filename it will save to that file and update the paths.
   Additional changes to the object work on that file.
   
 =head2 list  =head2 list
   - get the curently loaded list
   
       my @todo_list = $todo->list;
   
   In list context returns a list, it scalar context returns an array reference to the list.
   
   =head2 listpri
   - get the list items that are marked priority
   
   Like list, but only returns entries that have priority set.
   
       my @priority_list = $todo->listpri;
   
   Since this is so easy to write as:
   
       my @priority_list = grep { $_->priority } $todo->list;
   
   I think it may become depreciated unless there is demand.
   
   =head2 listtag
   
   Returns tags found in the list sorted by name.
   
   If there were projects +GarageSale and +Shopping then
   
       my @projects = $todo->listtag('project');
   
   is the same as
   
       @projects = ( 'GarageSale', 'Shopping' );
   
   =head2 listcon
   - Shortcut to listtag('context')
   
   =head2 listproj
   - Shortcut to listtag('project')
   
 =head2 add  =head2 add
   
 =head1 DIAGNOSTICS  Adds a new entry to the list.
   Can either be a Text::Todo::Entry object or plain text.
   
 =for author to fill in:      $todo->add('new todo entry');
     List every single error and warning message that the module can  
     generate (even the ones that will "never happen"), with a full  
     explanation of each problem, one or more likely causes, and any  
     suggested remedies.  
   
 =over  It then becomes $todo->list->[-1];
   
 =item C<< Error message here, perhaps with %s placeholders >>  =head2 del
   
 [Description of error here]  Remove an entry from the list, either the reference or by number.
   
 =item C<< Another error message here >>      $removed_entry = $todo->del($entry);
   
 [Description of error here]  $entry can either be an Text::Todo::Entry in the list or the index of the
   entry to delete.
   
 [Et cetera, et cetera]  Note that entries are 0 indexed (as expected in perl) not starting at line 1.
   
 =back  =head2 move
   
       $todo->move($entry, $new_pos);
   
   $entry can either be the number of the entry or the actual entry.
   $new_pos is the new position to put it.
   
   Note that entries are 0 indexed (as expected in perl) not starting at line 1.
   
   =head2 archive
   
       $todo->archive
   
   Iterates over the list and for each done entry,
   addto('done_file')
   and
   del($entry).
   If any were archived it will then
   save()
   and
   load().
   
   =head2 addto
   
       $todo->addto($file, $entry);
   
   Appends text to the file.
   $file can be anyting recognized by file().
   $entry can either be a Text::Todo::Entry or plain text.
   
   =head2 listfile
   
       @list = $todo->listfile($file);
   
   Read a file and returns a list like $todo->list but does not update the
   internal list that is being worked with.
   $file can be anyting recognized by file().
   
   
   =head1 DIAGNOSTICS
   
   Most methods return undef on failure.
   
   Some more important methods are fatal.
   
   
 =head1 CONFIGURATION AND ENVIRONMENT  =head1 CONFIGURATION AND ENVIRONMENT
   
 =for author to fill in:  
     A full explanation of any configuration system(s) used by the  
     module, including the names and locations of any configuration  
     files, and the meaning of any environment variables or properties  
     that can be set. These descriptions must also include details of any  
     configuration language used.  
   
 Text::Todo requires no configuration files or environment variables.  Text::Todo requires no configuration files or environment variables.
   
   Someday it should be able to read and use the todo.sh config file.  This may
   possibly be better done in a client that would use this module.
   
   
 =head1 DEPENDENCIES  =head1 DEPENDENCIES
   
 =for author to fill in:  Class::Std::Utils
     A list of all the other modules that this module relies upon,  File::Spec
     including any restrictions on versions, and an indication whether  version
     the module is part of the standard Perl distribution, part of the  
     module's distribution, or must be installed separately. ]  
   
 None.  
   
   
 =head1 INCOMPATIBILITIES  =head1 INCOMPATIBILITIES
   
 =for author to fill in:  
     A list of any modules that this module cannot be used in conjunction  
     with. This may be due to name conflicts in the interface, or  
     competition for system or program resources, or due to internal  
     limitations of Perl (for example, many modules that use source code  
     filters are mutually incompatible).  
   
 None reported.  None reported.
   
   
 =head1 BUGS AND LIMITATIONS  =head1 BUGS AND LIMITATIONS
   
 =for author to fill in:  
     A list of known problems with the module, together with some  
     indication Whether they are likely to be fixed in an upcoming  
     release. Also a list of restrictions on the features the module  
     does provide: data types that cannot be handled, performance issues  
     and the circumstances in which they may arise, practical  
     limitations on the size of data sets, special cases that are not  
     (yet) handled, etc.  
   
 No bugs have been reported.  No bugs have been reported.
   
   Limitations:
   
   Currently there isn't an easy way to print out line numbers with the entry.
   
 Please report any bugs or feature requests to  Please report any bugs or feature requests to
 C<bug-text-todo@rt.cpan.org>, or through the web interface at  C<bug-text-todo@rt.cpan.org>, or through the web interface at

Legend:
Removed from v.1.3  
changed lines
  Added in v.1.23

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>