root/misc/todo.pl

Revision 1941 (checked in by miyagawa, 14 years ago)

added todo.pl

  • Property svn:mime-type set to text/script
  • Property svn:executable set to *
Line 
1 #!/usr/bin/env perl
2 use strict;
3 use warnings;
4
5 =head1 DESCRIPTION
6
7 This is a simple command-line interface to Hiveminder that loosely
8 emulates the interface of Lifehacker.com's todo.sh script.
9
10 =cut
11
12 use YAML ();
13 use XML::Simple;
14 use LWP::UserAgent;
15 use Number::RecordLocator;
16 use Getopt::Long;
17 use Pod::Usage;
18 use Fcntl qw(:mode);
19
20 our $CONFFILE = "$ENV{HOME}/.hiveminder";
21 our %config = ();
22 our $ua = LWP::UserAgent->new;
23 our $locator = Number::RecordLocator->new();
24 our $default_query = "not/complete/owner/me/starts/before/tomorrow/accepted/but_first/nothing";
25 our %args;
26
27 $ua->cookie_jar({});
28
29 main();
30
31 sub main {
32     setup_config();
33
34     GetOptions(\%args, "tags=s", "tag=s@", "group=s") or pod2usage(2);
35    
36     push @{$args{tag}}, split /\s+/, $args{tags} if $args{tags};
37
38     do_login() or die("Bad username/password -- edit $CONFFILE and try again.");
39
40     my %commands = (
41         list    => \&list_tasks,
42         add     => \&add_task,
43         do      => \&do_task,
44         done    => \&do_task,
45         del     => \&del_task,
46         rm      => \&del_task,
47        );
48    
49     my $command = shift @ARGV || "list";
50     $commands{$command} or pod2usage(-message => "Unknown command: $command", -exitval => 2);
51
52     $commands{$command}->();
53 }
54
55
56 =head1 CONFIG FILE
57
58 These methods deal with loading the config file, and populating it
59 with selections read from the terminal on our first run.
60
61 =cut
62
63 sub setup_config {
64     check_config_perms() unless($^O eq 'win32');
65     load_config();
66     check_config();
67
68 }
69
70 sub check_config_perms {
71     return unless -e $CONFFILE;
72     my @stat = stat($CONFFILE);
73     my $mode = $stat[2];
74     if($mode & S_IRGRP || $mode & S_IROTH) {
75         warn("Config file $CONFFILE is readable by someone other than you, fixing.");
76         chmod 0600, $CONFFILE;
77     }
78 }
79
80 sub load_config {
81     return unless(-e $CONFFILE);
82     %config = %{YAML::LoadFile($CONFFILE)};
83 }
84
85 sub check_config {
86     new_config() unless $config{email};
87 }
88
89 sub new_config {
90     print <<"END_WELCOME";
91 Welcome to todo.pl! before we get started, please enter your
92 hiveminder username and password so we can access your tasklist.
93
94 This information will be stored in $CONFFILE, should you ever need to
95 change it.
96
97 END_WELCOME
98
99     $config{site} ||= 'http://hiveminder.com';
100
101     while (1) {
102         print "First, what's your email address? ";
103         $config{email} = <stdin>;
104         chomp($config{email});
105
106         use Term::ReadKey;
107         print "And your password? ";
108         ReadMode('noecho');
109         $config{password} = <stdin>;
110         chomp($config{password});
111         ReadMode('restore');
112
113         last if do_login();
114         print "That combination doesn't seem to be correct. Try again?\n";
115     }
116
117     YAML::DumpFile($CONFFILE, \%config);
118     chmod 0600, $CONFFILE;
119 }
120
121 =head1 TASKS
122
123 methods related to manipulating tasks -- the meat of the script.
124
125 =cut
126
127 sub list_tasks {
128     my $query = $default_query;
129
130     my $tag;
131     $query .= "/tag/$tag" while $tag = shift @{$args{tag}};
132     $query .= "/group/" . $args{group} if $args{group};
133
134     my $tasks = download_tasks($query);
135    
136     for my $t (@$tasks) {
137         printf "%4s :", $locator->encode($t->{id});
138         print '(' . chr(ord('A') + 5 - $t->{priority}) . ') ';
139         print $t->{summary};
140         if($t->{tags}) {
141             print ' [' . $t->{tags} . ']';
142         }
143
144         if($t->{group}) {
145             print ' (' . $t->{group} . ')';
146         }
147        
148         print "\n";
149     }
150 }
151
152 sub do_task {
153     my $task = shift @ARGV or pod2usage(-message => 'Need a task-id!');
154     my $id = $locator->decode($task) or die("Invalid task ID: $task");
155     my $result = call(UpdateTask =>
156                       id         => $id,
157                       complete   => 1);
158     if($result->{result}{success} == 1) {
159         print "Task $task completed.\n";
160     } else {
161         die(YAML::Dump($result));
162     }
163 }
164
165 sub add_task {
166     my $summary = join(" ",@ARGV) or pod2usage(-message => "Must specify a task description");
167     my %task;
168     $task{tags} = join(" ", map {'"' . $_ . '"'} @{$args{tag}}) if $args{tag};
169     $task{group_id} = $args{group} if $args{group};
170     $task{summary} = $summary;
171     $task{owner_id} = $config{email};
172
173     my $result = call(CreateTask => %task);
174     if($result->{result}{success} == 1) {
175         print "Task created.\n";
176     } else {
177         die(YAML::Dump($result));
178     }
179 }
180
181 sub del_task {
182     my $task = shift @ARGV or pod2usage(-message => 'Need a task-id!');
183     my $id = $locator->decode($task) or die("Invalid task ID: $task");
184     my $result = call(DeleteTask => id => $id);
185     if($result->{result}{success} == 1) {
186         print "Deleted task.\n";
187     } else {
188         die YAML::Dump($result);
189     }
190                      
191 }
192
193
194 =head1 BTDT API
195
196 These functions deal with calling the BTDT/Jifty api to communicate
197 with the server.
198
199 =cut
200
201 sub do_login {
202     my $result = call(Login =>
203                       address  => $config{email},
204                       password => $config{password});
205     return $result->{result}{success} == 1;
206 }
207
208 sub download_tasks {
209     my $query = shift || $default_query;
210
211     my $result = call(DownloadTasks =>
212                       query  => $query,
213                       format => 'yaml');
214     return YAML::Load($result->{result}{content}{result});
215 }
216
217 sub call ($@) {
218     my $class   = shift;
219     my %args    = (@_);
220     my $moniker = 'fnord';
221
222     my $res = $ua->post(
223         $config{site} . "/__jifty/webservices/xml",
224         {   "J:A-$moniker" => $class,
225             map { ( "J:A:F-$_-$moniker" => $args{$_} ) } keys %args
226         }
227     );
228
229     if ( $res->is_success ) {
230         return XML::Simple::XMLin($res->content);
231     } else {
232         die $res->status_line;
233     }
234 }
235
236 __END__
237
238 =head1 NAME
239
240 todo.pl - a command-line interface to Hiveminder
241
242 =cut
243
244 =head1 SYNOPSIS
245
246   todo.pl [options] list
247   todo.pl [options] add <summary>
248   todo.pl done <task-id>
249   todo.pl del|rm <task-id>
250
251     Options:
252        --group                          Operate on tasks in a group
253        --tag                            Operate on tasks with a given tag
254
255   todo.pl list
256         List all tasks in your todo list.
257
258   todo.pl --tag home --tag othertag --group personal list
259         List all personl tasks (not in a group with tags 'home' and 'othertag'.
260
261  
262
263
264 =head1 OPTIONS
265
266 =over
267
268 =back
269
270 =cut
271
272 =cut
273
Note: See TracBrowser for help on using the browser.