changed 'lsall' to print *all* the tasks if called without a pattern.
[gtdo.git] / gtdo.pl
blob69922175e61267e165308f3adad261bed8f03848
1 #!/usr/bin/perl -w
2 use strict;
4 #### Copyright 2008, Devendra Gera <gera@theoldmonk.net>,
5 #### All rights reserved.
6 ####
7 #### This is Free Software, released under the terms of the GNU General Public
8 #### License, version 2. A copy of the license can be obtained by emailing the
9 #### author, or from http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
10 ####
11 #### As noted in the License, this software does not come with any warranty,
12 #### explicit or implied. This program might, and would be buggy. Use it at
13 #### your own risk.
15 use Pod::Usage;
16 use Fcntl qw(:flock);
18 my $todo_file = "$ENV{HOME}/todo/todo.txt";
19 my $done_file = "$ENV{HOME}/todo/done.txt";
20 my $not_done_file = "$ENV{HOME}/todo/not_done.txt";
22 my %dispatch = (
23 add => \&add_task,
24 ls => \&list_tasks,
25 do => \&do_task,
26 contexts => \&list_contexts,
27 groups => \&list_groups,
28 lsall => \&listall,
29 del => \&delete_task,
30 change => \&change_task,
32 my @tasks;
34 my $command = shift;
35 pod2usage()
36 unless ( (defined $command) && (exists $dispatch{$command}) );
38 read_tasks();
39 $dispatch{$command}->(@ARGV);
41 exit(0);
43 sub add_task
45 my $task = join (" ", @_);
46 quit("") unless $task;
48 # find out the group of the task
49 my (undef, $group) = ( $task =~ m{(^|\s)(/\S*)(\s|$)} );
50 $group |= "";
51 my ($parent, $number) = ( $group =~ /(.*?)\.(\d+)/ );
53 my $target_offset = @tasks;
55 # insert properly if its a subtask
56 if(defined $parent) {
58 # also remember to strip the number
59 $task =~ s/$parent\.$number/$parent/;
61 my @candidate_ids = grep_pos_in_tasks($parent, ());
62 my $length = @candidate_ids;
64 if( $number <= $length ) {
65 $target_offset = $candidate_ids[$number - 1] - 1;
69 splice @tasks, $target_offset, 0, ($task);
71 write_tasks();
74 sub list_tasks
76 my @patterns = @_;
77 @patterns = ("") unless @patterns;
78 my $is_a_group = 0;
80 my @candidates = ();
81 foreach my $pattern ( @patterns ) {
82 $is_a_group = 1 if( $pattern =~ m{^/} );
83 @candidates = grep_pos_in_tasks( $pattern, @candidates );
86 # list tasks only if they are the first task from their group.
87 my %out;
88 my $i = 0;
89 foreach my $id ( @candidates ) {
90 unless ($is_a_group) {
91 my (undef,$group) = ( $tasks[$id - 1] =~ m{(^|\s)(/\S*)(\s|$)} );
92 if(defined $group) {
93 my $first = (grep_pos_in_tasks( $group, () ))[0];
94 $out{$group} = $id if ($id == $first);
95 next;
98 $out{$i++} = $id;
101 print_tasks( values %out );
104 sub listall
106 my @patterns = @_;
107 return print_tasks( (1 .. scalar(@tasks)) )
108 unless @patterns;
110 my @candidates = ();
111 foreach my $pattern ( @patterns ) {
112 @candidates = grep_pos_in_tasks( $pattern, @candidates );
115 # list only one task per group from these results.
116 my %out;
117 my $i = 0;
118 foreach my $id ( @candidates ) {
119 my (undef,$group) = ( $tasks[$id - 1] =~ m{(^|\s)(/\S*)(\s|$)} );
120 if(defined $group) {
121 $out{$group} = $id if (!exists $out{$group});
122 next;
124 $out{$i++} = $id;
127 print_tasks( values %out );
130 sub move_task
132 my $num = shift;
133 my $file = shift || quit("no file to move to!");
135 quit("invalid task number") unless ((defined $num) && ($num =~ /^\d+$/));
136 quit("") if ($num > @tasks);
138 my $task = splice @tasks, $num - 1, 1;
139 append_to_file($task, $file);
140 write_tasks();
143 sub do_task
145 my $num = shift;
146 move_task($num, $done_file);
149 sub delete_task
151 my $num = shift;
152 move_task($num, $not_done_file);
155 sub read_tasks
157 open TODO, "<$todo_file" or return;
159 while(<TODO>) {
160 chomp;
161 next unless $_;
162 push @tasks, $_;
165 close TODO;
168 sub write_tasks
170 open LOCK, ">$todo_file.lock" or quit("cannot open lock file : $!");
171 flock LOCK, LOCK_EX;
173 open TODO, ">$todo_file.$$" or quit("cannot open $todo_file.$$ : $!");
174 print TODO join( "\n", @tasks );
175 close TODO;
177 rename( "$todo_file.$$", $todo_file ) or quit("cannot rename : $!");
179 flock LOCK, LOCK_UN;
180 close LOCK;
183 sub grep_pos_in_tasks
185 my $pattern = shift;
186 my @positions = @_;
188 @positions = ( 1 .. scalar(@tasks) ) if( @positions < 1 );
190 my @ret = ();
191 foreach my $pos ( @positions ) {
192 push @ret, $pos if ( $tasks[$pos - 1] =~ /$pattern/ );
195 return @ret;
198 sub append_to_file
200 my $task = shift or quit("no task to append!");
201 my $file = shift or quit("no file to append to!");
202 my ($day, $mon, $year) = (localtime) [3 .. 5];
203 $mon ++;
204 $year += 1900;
206 open LOCK, ">$file.lock" or quit("cannot open lock file : $!");
207 flock LOCK, LOCK_EX;
209 open DONE, ">>$file" or quit("cannot open $file.$$ : $!");
210 print DONE "$year-$mon-$day : $task", "\n";
211 close DONE;
213 flock LOCK, LOCK_UN;
214 close LOCK;
217 sub quit
219 my $msg = shift;
221 die $msg if $msg;
224 sub extract_tokens
226 my $pattern = shift;
227 my %ret;
229 foreach ( @tasks ) {
230 $ret{$2}++ if (/(^|\s)($pattern.*?)(\s|$)/);
233 return keys %ret;
236 sub list_contexts
238 my @contexts = extract_tokens('@');
239 print join( "\n", @contexts, "" );
242 sub list_groups
244 my @groups = extract_tokens('/');
245 print join( "\n", @groups, "" );
248 sub change_task
250 my $num = shift;
251 my $new_task = join(" ", @_);
253 quit("not a valid task number") unless ((defined $num) && ($num =~ /^\d+$/));
254 quit("") if ($num > @tasks);
256 $tasks[$num - 1] = $new_task;
257 write_tasks();
260 sub print_tasks
262 my @ids = @_;
264 foreach my $id ( sort { $a <=> $b } @ids ) {
265 print "$id : $tasks[$id - 1]", "\n";