| version 1.14, 2010/01/10 01:41:40 |
version 1.30, 2010/02/16 01:13:12 |
|
|
| package Text::Todo::Entry; |
package Text::Todo::Entry; |
| |
|
| # $RedRiver: Entry.pm,v 1.13 2010/01/10 01:03:02 andrew Exp $ |
# $AFresh1: Entry.pm,v 1.29 2010/02/14 06:08:07 andrew Exp $ |
| |
|
| use warnings; |
use warnings; |
| use strict; |
use strict; |
| use Carp; |
use Carp; |
| |
|
| use Class::Std::Utils; |
use Class::Std::Utils; |
| use List::Util qw/ first /; |
|
| |
|
| use version; our $VERSION = qv('0.0.1'); |
use version; our $VERSION = qv('0.2.0'); |
| |
|
| { |
{ |
| my %text_of; |
|
| |
|
| my %tags_of; |
my @attr_refs = \( |
| my %priority_of; |
my %text_of, |
| my %completion_status_of; |
|
| my %known_tags_of; |
|
| |
|
| |
my %tags_of, |
| |
my %priority_of, |
| |
my %completion_status_of, |
| |
my %known_tags_of, |
| |
); |
| |
|
| # XXX Should the completion (x) be case sensitive? |
# XXX Should the completion (x) be case sensitive? |
| my $priority_completion_regex = qr{ |
my $priority_completion_regex = qr{ |
| ^ \s* |
^ \s* |
| (?i: (x) \s+)? |
(?i:(x \s* [\d-]* ) \s*)? |
| (?i:\( ([A-Z]) \) \s+)? |
(?i:\( ([A-Z]) \) \s*)? |
| }xms; |
}xms; |
| |
|
| sub new { |
sub new { |
|
|
| my $self = bless anon_scalar(), $class; |
my $self = bless anon_scalar(), $class; |
| my $ident = ident($self); |
my $ident = ident($self); |
| |
|
| |
$text_of{$ident} = q{}; |
| |
|
| if ( !ref $options ) { |
if ( !ref $options ) { |
| $options = { text => $options }; |
$options = { text => $options }; |
| } |
} |
|
|
| croak 'Invalid parameter passed!'; |
croak 'Invalid parameter passed!'; |
| } |
} |
| |
|
| $known_tags_of{$ident} = { |
my %tags = ( |
| context => q{@}, |
context => q{@}, |
| project => q{+}, |
project => q{+}, |
| }; |
); |
| |
|
| if ( exists $options->{tags} && ref $options->{tags} eq 'HASH' ) { |
if ( exists $options->{tags} && ref $options->{tags} eq 'HASH' ) { |
| foreach my $k ( keys %{ $options->{tags} } ) { |
%tags = ( %tags, %{ $options->{tags} } ); |
| $known_tags_of{$ident}{$k} = $options->{tags}->{$k}; |
|
| } |
|
| } |
} |
| |
|
| for my $tag ( keys %{ $known_tags_of{$ident} } ) { |
for my $tag ( keys %tags ) { |
| ## no critic strict |
$self->learn_tag( $tag, $tags{$tag} ); |
| no strict |
|
| 'refs'; # Violates use strict, but allows code generation |
|
| ## use critic |
|
| |
|
| if ( !$self->can( $tag . 's' ) ) { |
|
| *{ $tag . 's' } = sub { |
|
| my ($self) = @_; |
|
| return $self->_tags($tag); |
|
| }; |
|
| } |
|
| |
|
| if ( !$self->can( 'in_' . $tag ) ) { |
|
| *{ 'in_' . $tag } = sub { |
|
| my ( $self, $item ) = @_; |
|
| return $self->_is_in( $tag . 's', $item ); |
|
| }; |
|
| } |
|
| } |
} |
| |
|
| $self->replace( $options->{text} ); |
$self->replace( $options->{text} ); |
|
|
| return $self; |
return $self; |
| } |
} |
| |
|
| |
sub _parse_entry { |
| |
my ($self) = @_; |
| |
my $ident = ident($self); |
| |
|
| |
delete $tags_of{$ident}; |
| |
delete $completion_status_of{$ident}; |
| |
delete $priority_of{$ident}; |
| |
|
| |
my $text = $self->text || q{}; |
| |
my $known_tags = $self->known_tags || {}; |
| |
|
| |
foreach my $tag ( keys %{$known_tags} ) { |
| |
next if !defined $known_tags->{$tag}; |
| |
next if !length $known_tags->{$tag}; |
| |
|
| |
my $sigal = quotemeta $known_tags->{$tag}; |
| |
$tags_of{$ident}{$tag} |
| |
= { map { $_ => q{} } $text =~ / (?:^|\s) $sigal (\S*)/gxms }; |
| |
} |
| |
|
| |
my ( $completed, $priority ) |
| |
= $text =~ / $priority_completion_regex /xms; |
| |
|
| |
$completion_status_of{$ident} = _clean_completed($completed); |
| |
$priority_of{$ident} = $priority; |
| |
|
| |
return 1; |
| |
} |
| |
|
| |
sub _clean_completed { |
| |
my ($completed) = @_; |
| |
|
| |
$completed ||= q{}; |
| |
$completed =~ s/^\s+|\s+$//gxms; |
| |
|
| |
if ( !$completed ) { |
| |
return; |
| |
} |
| |
|
| |
if ( $completed =~ s/(x)\s*//ixms ) { |
| |
my $status = $1; |
| |
if ($completed) { |
| |
return $completed; |
| |
} |
| |
else { |
| |
return $status; |
| |
} |
| |
} |
| |
|
| |
return; |
| |
} |
| |
|
| sub replace { |
sub replace { |
| my ( $self, $text ) = @_; |
my ( $self, $text ) = @_; |
| my $ident = ident($self); |
my $ident = ident($self); |
|
|
| |
|
| $text_of{$ident} = $text; |
$text_of{$ident} = $text; |
| |
|
| foreach my $tag ( keys %{ $known_tags_of{$ident} } ) { |
return $self->_parse_entry; |
| my $symbol = quotemeta $known_tags_of{$ident}{$tag}; |
} |
| $tags_of{$ident}{$tag} = { map { $_ => q{} } |
|
| $text =~ / (?:^|\s) $symbol (\S+)/gxms }; |
sub learn_tag { |
| |
my ( $self, $tag, $sigal ) = @_; |
| |
$known_tags_of{ ident $self}{$tag} = $sigal; |
| |
|
| |
## no critic strict |
| |
no strict 'refs'; # Violates use strict, but allows code generation |
| |
## use critic |
| |
|
| |
if ( !$self->can( $tag . 's' ) ) { |
| |
*{ $tag . 's' } = sub { |
| |
my ($self) = @_; |
| |
return $self->_tags($tag); |
| |
}; |
| } |
} |
| ( $completion_status_of{$ident}, $priority_of{$ident} ) |
|
| = $text =~ / $priority_completion_regex /xms; |
|
| |
|
| return 1; |
if ( !$self->can( 'in_' . $tag ) ) { |
| |
*{ 'in_' . $tag } = sub { |
| |
my ( $self, $item ) = @_; |
| |
return $self->_is_in( $tag . 's', $item ); |
| |
}; |
| |
} |
| |
|
| |
return $self->_parse_entry; |
| } |
} |
| |
|
| sub _tags { |
sub _tags { |
| my ( $self, $tag ) = @_; |
my ( $self, $tag ) = @_; |
| my $ident = ident($self); |
my $ident = ident($self); |
| |
|
| my @tags = sort keys %{ $tags_of{$ident}{$tag} }; |
my @tags; |
| |
if ( defined $tags_of{$ident}{$tag} ) { |
| |
@tags = sort keys %{ $tags_of{$ident}{$tag} }; |
| |
} |
| return wantarray ? @tags : \@tags; |
return wantarray ? @tags : \@tags; |
| } |
} |
| |
|
| sub _is_in { |
sub _is_in { |
| my ( $self, $tags, $item ) = @_; |
my ( $self, $tags, $item ) = @_; |
| return defined first { $_ eq $item } $self->$tags; |
return if !defined $item; |
| |
foreach ( $self->$tags ) { |
| |
return 1 if $_ eq $item; |
| |
} |
| |
return 0; |
| } |
} |
| |
|
| sub text { |
|
| my ($self) = @_; |
|
| my $ident = ident($self); |
|
| |
|
| return $text_of{$ident}; |
|
| } |
|
| |
|
| sub depri { my ($self) = @_; return $self->pri(q{}) } |
|
| |
|
| sub pri { |
sub pri { |
| my ( $self, $new_pri ) = @_; |
my ( $self, $new_pri ) = @_; |
| my $ident = ident($self); |
my $ident = ident($self); |
|
|
| return $self->prepend(); |
return $self->prepend(); |
| } |
} |
| |
|
| sub priority { |
|
| my ( $self, $new_pri ) = @_; |
|
| my $ident = ident($self); |
|
| |
|
| return $priority_of{$ident}; |
|
| } |
|
| |
|
| sub prepend { |
sub prepend { |
| my ( $self, $addition ) = @_; |
my ( $self, $addition ) = @_; |
| |
|
|
|
| $new =~ s/$priority_completion_regex//xms; |
$new =~ s/$priority_completion_regex//xms; |
| |
|
| if ( $self->done ) { |
if ( $self->done ) { |
| |
if ( $self->done !~ /^x/ixms ) { |
| |
push @new, 'x'; |
| |
} |
| push @new, $self->done; |
push @new, $self->done; |
| } |
} |
| |
|
|
|
| return 1; |
return 1; |
| } |
} |
| |
|
| $completion_status_of{$ident} = 'x'; |
$completion_status_of{$ident} = sprintf "%04d-%02d-%02d", |
| |
( (localtime)[5] + 1900 ), |
| |
( (localtime)[4] + 1 ), |
| |
( (localtime)[3] ); |
| |
|
| return $self->prepend(); |
return $self->prepend(); |
| } |
} |
| |
|
| sub done { |
sub done { |
| my ($self) = @_; |
my ($self) = @_; |
| my $ident = ident($self); |
return $completion_status_of{ ident($self) }; |
| |
|
| return $completion_status_of{$ident}; |
|
| } |
} |
| |
sub known_tags { my ($self) = @_; return $known_tags_of{ ident($self) }; } |
| |
sub priority { my ($self) = @_; return $priority_of{ ident($self) }; } |
| |
sub text { my ($self) = @_; return $text_of{ ident($self) }; } |
| |
sub depri { my ($self) = @_; return $self->pri(q{}) } |
| |
|
| |
sub DESTROY { |
| |
my ($self) = @_; |
| |
my $ident = ident $self; |
| |
foreach my $attr_ref (@attr_refs) { |
| |
delete $attr_ref->{$ident}; |
| |
} |
| |
} |
| } |
} |
| 1; # Magic true value required at end of module |
1; # Magic true value required at end of module |
| __END__ |
__END__ |
|
|
| |
|
| Each tag type generates two accessor functions {tag}s and in_{tag}. |
Each tag type generates two accessor functions {tag}s and in_{tag}. |
| |
|
| Current tags are context (@) and project (+). |
Default tags are context (@) and project (+). |
| |
|
| When creating a new object you can pass in new tags to recognize. |
When creating a new object you can pass in new tags to recognize. |
| |
|
|
|
| |
|
| my @due_dates = $entry->due_dates; |
my @due_dates = $entry->due_dates; |
| |
|
| then @due_dates eq ( '2011-01-01' ); |
then @due_dates is ( '2011-01-01' ); |
| |
|
| and you could also: |
and you could also: |
| |
|
|
|
| |
|
| =back |
=back |
| |
|
| |
=head2 learn_tag($tag, $sigal) |
| |
|
| |
$entry->learn_tag('due_date', 'DUE:'); |
| |
|
| |
Teaches the entry about an additional tag, same as passing a tags argument to |
| |
new(). See tags() |
| |
|
| |
You can simulate forgetting a tag by setting the sigal to undef or an empty |
| |
string. |
| |
|
| |
=head2 known_tags |
| |
|
| |
$known_tags = $entry->known_tags; |
| |
|
| |
$known_tags by default would be: |
| |
|
| |
{ context => '@', |
| |
project => '+', |
| |
} |
| |
|
| |
|
| =head3 context |
=head3 context |
| |
|
| These are matched as a word beginning with @. |
These are matched as a word beginning with @. |
|
|
| |
|
| $entry->do; |
$entry->do; |
| |
|
| Does this by prepending an 'x' to the beginning of the entry. |
Does this by prepending "x `date '%Y-%m-%d'`" to the beginning of the entry. |
| |
|
| =head2 done |
=head2 done |
| |
|
| Returns true if an entry is marked complete and false if not. |
Returns true if an entry is marked complete and false if not. |
| |
|
| if (!$entry->done) { |
if (!my $status = $entry->done) { |
| # remind me to do it |
# remind me to do it |
| } |
} |
| |
|
| |
If the entry starts as 'x date', for example 'x 2010-01-01', $status is now |
| |
'2010-01-01'. |
| |
If the entry just starts with 'x', then $status will be 'x'. |
| |
|
| =head1 DIAGNOSTICS |
=head1 DIAGNOSTICS |
| |
|