changed 'ls' to display first task in the group globally, instead of the first
[gtdo.git] / gtdo.pl
blobccfa9a480a2295b26b5296499f11af0fc651137c
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";
21 my %dispatch = (
22 add => \&add_task,
23 ls => \&list_tasks,
24 do => \&do_task,
25 contexts => \&list_contexts,
26 groups => \&list_groups,
28 my @tasks;
30 my $command = shift;
31 pod2usage()
32 unless ( (defined $command) && (exists $dispatch{$command}) );
34 read_tasks();
35 $dispatch{$command}->(@ARGV);
37 exit(0);
39 sub add_task
41 my $task = join (" ", @_);
42 quit("") unless $task;
44 # find out the group of the task
45 my (undef, $group) = ( $task =~ m{(^|\s)(/\S*)(\s|$)} );
46 $group |= "";
47 my ($parent, $number) = ( $group =~ /(.*?)\.(\d+)/ );
49 my $target_offset = @tasks;
51 # insert properly if its a subtask
52 if(defined $parent) {
54 # also remember to strip the number
55 $task =~ s/$parent\.$number/$parent/;
57 my @candidate_ids = grep_pos_in_tasks($parent, ());
58 my $length = @candidate_ids;
60 if( $number <= $length ) {
61 $target_offset = $candidate_ids[$number - 1] - 1;
65 splice @tasks, $target_offset, 0, ($task);
67 write_tasks();
70 sub list_tasks
72 my @patterns = @_;
73 @patterns = ("") unless @patterns;
74 my $is_a_group = 0;
76 my @candidates = ();
77 foreach my $pattern ( @patterns ) {
78 $is_a_group = 1 if( $pattern =~ m{^/} );
79 @candidates = grep_pos_in_tasks( $pattern, @candidates );
82 # list tasks only if they are the first task from their group.
83 my %out;
84 my $i = 0;
85 foreach my $id ( @candidates ) {
86 unless ($is_a_group) {
87 my (undef,$group) = ( $tasks[$id - 1] =~ m{(^|\s)(/\S*)(\s|$)} );
88 if(defined $group) {
89 my $first = (grep_pos_in_tasks( $group, () ))[0];
90 $out{$group} = $id if ($id == $first);
91 next;
94 $out{$i++} = $id;
97 foreach my $id ( sort { $a <=> $b } values %out ) {
98 print STDOUT "$id : $tasks[$id - 1]", "\n";
102 sub do_task
104 my $num = shift;
106 quit("invalid task number") unless ((defined $num) && ($num =~ /^\d+$/));
107 quit("") if ($num > @tasks);
109 my $task = splice @tasks, $num - 1, 1;
110 append_done($task);
111 write_tasks();
114 sub read_tasks
116 open TODO, "<$todo_file" or return;
118 while(<TODO>) {
119 chomp;
120 next unless $_;
121 push @tasks, $_;
124 close TODO;
127 sub write_tasks
129 open LOCK, ">$todo_file.lock" or quit("cannot open lock file : $!");
130 flock LOCK, LOCK_EX;
132 open TODO, ">$todo_file.$$" or quit("cannot open $todo_file.$$ : $!");
133 print TODO join( "\n", @tasks );
134 close TODO;
136 rename( "$todo_file.$$", $todo_file ) or quit("cannot rename : $!");
138 flock LOCK, LOCK_UN;
139 close LOCK;
142 sub grep_pos_in_tasks
144 my $pattern = shift;
145 my @positions = @_;
147 @positions = ( 1 .. scalar(@tasks) ) if( @positions < 1 );
149 my @ret = ();
150 foreach my $pos ( @positions ) {
151 push @ret, $pos if ( $tasks[$pos - 1] =~ /$pattern/ );
154 return @ret;
157 sub append_done
159 my $task = shift or quit("no task to append!");
160 my ($day, $mon, $year) = (localtime) [3 .. 5];
161 $mon ++;
162 $year += 1900;
164 open LOCK, ">$done_file.lock" or quit("cannot open lock file : $!");
165 flock LOCK, LOCK_EX;
167 open DONE, ">>$done_file" or quit("cannot open $done_file.$$ : $!");
168 print DONE "$year-$mon-$day : $task", "\n";
169 close DONE;
171 flock LOCK, LOCK_UN;
172 close LOCK;
175 sub quit
177 my $msg = shift;
179 die $msg if $msg;
182 sub extract_tokens
184 my $pattern = shift;
185 my %ret;
187 foreach ( @tasks ) {
188 $ret{$2}++ if (/(^|\s)($pattern.*?)(\s|$)/);
191 return keys %ret;
194 sub list_contexts
196 my @contexts = extract_tokens('@');
197 print join( "\n", @contexts, "" );
200 sub list_groups
202 my @groups = extract_tokens('/');
203 print join( "\n", @groups, "" );