root/misc/todo.pl

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

add Term::Encoding

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