Adjust color list
[infodrom.org/service.infodrom.org] / src / InfoCon / stempel / stempel
1 #! /usr/bin/perl
2
3 #  InfoCon Time Tracker
4 #  Copyright (c) 2007,8,13,14,15  Martin Schulze <joey@infodrom.org>
5 #
6 #  This program is free software; you can redistribute it and/or modify
7 #  it under the terms of the GNU General Public License as published by
8 #  the Free Software Foundation; either version 2 of the License, or
9 #  (at your option) any later version.
10 #
11 #  This program is distributed in the hope that it will be useful,
12 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #  GNU General Public License for more details.
15 #
16 #  You should have received a copy of the GNU General Public License
17 #  along with this program; if not, write to the Free Software
18 #  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
19
20 use strict;
21 use warnings;
22
23 use DBI;
24 use Getopt::Long;
25 use Term::ReadLine;
26 use Term::ANSIColor;
27
28 my $table = "stempel";
29 my $engine = "dbi:Pg:dbname=infocon";
30
31 my $dbh = DBI->connect($engine);
32 die "Access to database denied!\n" unless $dbh;
33 $dbh->do("SET DateStyle = 'ISO'");
34
35 sub min2hour
36 {
37     my $minutes = shift;
38
39     return sprintf('%02d:%02d', $minutes/60, $minutes%60);
40 }
41
42 sub is_open
43 {
44     my $query = q{SELECT count(*) AS count FROM stempel WHERE stop IS NULL};
45     my $sth = $dbh->prepare ($query);
46     if ($sth && $sth->execute > 0) {
47         my $row = $sth->fetchrow_hashref;
48         return $row->{count} > 0 ? 1 : 0;
49     }
50     return 0;
51 }
52
53 sub customerlist
54 {
55     my @res;
56
57     my $query = q{SELECT DISTINCT customer FROM stempel WHERE start > now() - interval '1 year' ORDER BY customer};
58     my $sth = $dbh->prepare ($query);
59     if ($sth && $sth->execute > 0) {
60         while ((my $row = $sth->fetchrow_hashref)) {
61             push @res, $row->{customer};
62         }
63     }
64     return \@res;
65 }
66
67 sub tasklist
68 {
69     my $customer = shift;
70     my @res;
71
72     my $query = q{SELECT DISTINCT task FROM stempel WHERE start > now() - interval '1 month' AND customer = ? ORDER BY task};
73     my $sth = $dbh->prepare($query);
74     if ($sth && $sth->execute($customer) > 0) {
75         while ((my $row = $sth->fetchrow_hashref)) {
76             push @res, $row->{task};
77         }
78     }
79     return \@res;
80 }
81
82 sub complete_customer
83 {
84     my ($customer, $text, $line, $start) = @_;
85
86     # return () unless exists $answers->{category} && length $answers->{category};
87
88     my $sql = sprintf("SELECT DISTINCT task FROM stempel WHERE start > now() - interval '1 month' AND customer = '%s' AND task LIKE '%s%%' ORDER BY task",
89                       $customer,
90                       $line);
91
92     my $sth = $dbh->prepare($sql);
93     $sth->execute;
94     my @complete;
95     while (my $row = $sth->fetchrow_hashref) {
96         $row->{task} = substr $row->{task}, $start if $start;
97         push @complete, $row->{task};
98     }
99
100     return @complete;
101 }
102
103 sub open_task
104 {
105     my $term = new Term::ReadLine '';
106     my $customers = customerlist;
107
108     my $attribs = $term->Attribs;
109     $attribs->{completion_entry_function} = $attribs->{list_completion_function};
110     $attribs->{completion_word} = $customers;
111
112     printf "[%s]\n", join (', ', @$customers);
113
114     my $customer = $term->readline ('Kunde: ');
115
116     if (defined $customer && length($customer) > 0) {
117         $customer =~ s/\s*$//;
118
119         my $tasks = tasklist $customer;
120
121         $term->addhistory($_) foreach (@$tasks);
122         $attribs->{completion_word} = $tasks;
123         $attribs->{completion_entry_function} = undef;
124         $term->{completion_function} = sub {return complete_customer $customer, @_};
125         while (1) {
126             my $task = $term->readline ('Aufgabe: ');
127             return unless length $task;
128             return if $task eq 'q';
129
130             if ($task eq '?') {
131                 printf "  %s\n", $_ foreach (@$tasks);
132                 next;
133             }
134
135             $task =~ s/\s*$//;
136             my $query = q{INSERT INTO stempel (start,customer,task) VALUES('now()',?,?)};
137             my $sth = $dbh->prepare($query);
138             $sth->execute($customer, $task);
139             last;
140         }
141     }
142 }
143
144 sub quarter
145 {
146     my $min = shift;
147
148     return 15 if $min > 0 && $min < 15;
149
150     return $min - $min%15 if ($min%15 < 3);
151
152     return $min + 15-$min%15;
153 }
154
155 sub hdiff
156 {
157     my ($sh, $sm, $eh, $em) = @_;
158
159     if ($em >= $sm) {
160         return ($eh-$sh)*60 + $em-$sm;
161     } else {
162         return ($eh-$sh)*60 - ($sm-$em);
163     }
164 }
165
166 sub close_task
167 {
168     my $really = shift;
169     my ($d_sec,$d_min,$d_hour,$d_mday,$d_mon,$d_year,$d_wday,$d_isdst) = localtime();
170     my $today = 1;
171
172     my $query = q{SELECT oid,start FROM stempel WHERE stop IS NULL};
173     my $sth = $dbh->prepare ($query);
174     if ($sth && $sth->execute > 0) {
175         while ((my $row = $sth->fetchrow_hashref)) {
176             my @arr = split(/ /, $row->{start});
177             my @d = split(/-/, $arr[0]);
178
179             if ($d[0] != $d_year+1900 || $d[1] != $d_mon+1 || $d[2] != $d_mday) {
180                 if ($really) {
181                     printf "Task not started today.\n";
182                     $today = 0;
183                 } else {
184                     printf "Task not started today, aborting, use -D if this is intentional.\n";
185                     next;
186                 }
187             }
188
189             my @t = split(/:/, $arr[1]);
190             my $int = quarter(hdiff($t[0], $t[1], $d_hour, $d_min));
191             $int += 24 * 60 unless $today;
192
193             $query = sprintf('UPDATE stempel SET stop=now(),time=%d WHERE oid = %d',
194                              $int, $row->{oid});
195             $dbh->do ($query);
196             printf "Wrote %s.\n", min2hour($int);
197         }
198     }
199     exit(0);
200 }
201
202 sub delete_task
203 {
204     my $query = q{DELETE FROM stempel WHERE stop IS NULL};
205     $dbh->do ($query);
206
207     exit(0);
208 }
209
210 sub reopen_task
211 {
212     my $sql = q{SELECT max(id) AS max_id FROM stempel};
213     my $sth = $dbh->prepare($sql);
214     if ($sth && $sth->execute > 0) {
215         my $row = $sth->fetchrow_hashref;
216         $sql = sprintf("UPDATE stempel SET stop = NULL, time = NULL WHERE id = %d", $row->{max_id});
217         $dbh->do($sql);
218     }
219
220     exit(0);
221 }
222
223 sub list_open
224 {
225     my $exit = shift;
226     my ($d_sec,$d_min,$d_hour,$d_mday,$d_mon,$d_year,$d_wday,$d_isdst) = localtime();
227     my $query = q{SELECT customer,start,task FROM stempel WHERE time IS NULL ORDER BY start,customer};
228     my $today = 1;
229
230     my $sth = $dbh->prepare ($query);
231     if ($sth && $sth->execute > 0) {
232         while ((my $row = $sth->fetchrow_hashref)) {
233             my @arr = split(/ /, $row->{start});
234             my $day = $arr[0];
235             my @d = split(/-/, $day);
236
237             if ($d[0] != $d_year+1900 || $d[1] != $d_mon+1 || $d[2] != $d_mday) {
238                 printf "Task not started today.\n";
239                 $today = 0;
240                 next if defined $exit && $exit eq 1;
241             }
242
243             my @t = split(/:/, $arr[1]);
244             my $time = sprintf('%02d:%02d', $t[0], $t[1]);
245             my $int = quarter(hdiff($t[0], $t[1], $d_hour, $d_min));
246             $int += 24 * 60 unless $today;
247
248             printf "%-15s  %s %s (%s)  %s\n", $row->{customer}, $day, $time, min2hour($int), $row->{task};
249         }
250     }
251     exit 0 if $exit;
252 }
253
254 sub list
255 {
256     my $month = shift;
257     my ($d_sec,$d_min,$d_hour,$d_mday,$d_mon,$d_year,$d_wday,$d_isdst) = localtime();
258     my $query = q{SELECT start,customer,time,task FROM stempel WHERE time IS NOT NULL };
259     
260     if ($month =~ /^(\d{4})-?(\d\d?)$/) {
261         my $pivot = $1 . '-' . $2 . '-01';
262         $query .= "AND start >= '$pivot' AND start <= '$pivot'::date + interval '1 month' ";
263     } elsif ($month =~ /^(\d{4})$/) {
264         my $pivot = $1 . '-01-01';
265         $query .= "AND start >= '$pivot' AND start <= '$pivot'::date + interval '1 year' ";
266     } elsif ($month =~ /^(\d\d?)$/) {
267         my $pivot = $d_year+1900 . '-' . $1 . '-01';
268         $query .= "AND start >= '$pivot' AND start <= '$pivot'::date + interval '1 month' ";
269     } elsif ($month !~ /^all$/) {
270         my $pivot = sprintf('%04d-%02d-01', $d_year+1900, $d_mon+1);
271         $query .= "AND start >= '$pivot' AND start <= '$pivot'::date + interval '1 month' ";
272     }
273     $query .= "ORDER BY customer,start";
274
275     my $sth = $dbh->prepare ($query);
276
277     my $customer = '';
278     my @colors = qw/green yellow red cyan magenta blue/;
279     my $color = $#colors;
280     if ($sth && $sth->execute > 0) {
281         while ((my $row = $sth->fetchrow_hashref)) {
282             if (defined $row->{time}) {
283                 if ($customer ne $row->{customer}) {
284                     $customer = $row->{customer};
285                     if ($color == $#colors) {
286                         $color = 0;
287                     } else {
288                         $color++;
289                     }
290                 }
291                 my $day = (split(/ /, $row->{start}))[0];
292                 my $time = min2hour $row->{time};
293                 print color($colors[$color]);
294                 printf "%-15s  %s  %s  %s", $row->{customer}, $day, $time, $row->{task};
295                 print color('reset'), "\n";
296             }
297         }
298     }
299     exit 0;
300 }
301
302 sub toggle_task
303 {
304     if (is_open) {
305         list_open 1;
306     } else {
307         open_task;
308     }
309 }
310
311 sub late_close_task
312 {
313     my $delta = shift;
314     my ($d_sec,$d_min,$d_hour,$d_mday,$d_mon,$d_year,$d_wday,$d_isdst) = localtime();
315
316     return unless is_open;
317
318     my $query = sprintf("UPDATE stempel SET stop=start + interval '%d minutes',time=%d WHERE stop IS NULL",
319                         $delta, quarter($delta));
320     $dbh->do($query);
321 }
322
323 sub move_task
324 {
325     my $minutes = shift;
326
327     return unless is_open;
328
329     my $query = sprintf("UPDATE stempel SET start = start - interval '%d minutes' WHERE stop IS NULL", $minutes);
330     $dbh->do($query);
331 }
332
333 sub alter_task
334 {
335     return unless is_open;
336
337     list_open;
338
339     my $term = new Term::ReadLine '';
340
341     my $sql = q{SELECT customer,task FROM stempel WHERE stop IS NULL};
342     my $sth = $dbh->prepare($sql);
343     $sth->execute;
344     my $row = $sth->fetchrow_hashref;
345
346     my $tasks = tasklist $row->{'customer'};
347     $term->addhistory($_) foreach (@$tasks);
348
349     my $attribs = $term->Attribs;
350     $attribs->{completion_word} = $tasks;
351     $term->{completion_function} = sub {return complete_customer $row->{'customer'}, @_};
352
353     my $task = $term->readline ('Aufgabe: ');
354
355     if (length($task) > 0) {
356         my $query = q{UPDATE stempel SET task=? WHERE stop IS NULL};
357         my $sth = $dbh->prepare($query);
358         $sth->execute($task);
359     }
360     exit 0;
361 }
362
363 sub help
364 {
365     print <<"END";
366 stempel  Copyright (c) 2007,8  Martin Schulze <joey\@infodrom.org>
367     --list [month] list month [default=current|all]
368     --back n       move start of current task back by n minutes
369     --open         list open
370     --close time   close open task
371     --help         this text
372     --end|-d       terminate task
373     -D             terminate task not started today
374     --delete       delete open task
375     --reopen       re-open last task
376     --task         alter task content
377 END
378     exit;
379 }
380
381 my $opt_close = undef;
382 my $opt_list = undef;
383 my $opt_back = undef;
384 my %options = ('list:s' => \$opt_list,
385                'back=i' => \$opt_back,
386                'open' => \&list_open,
387                'help' => \&help,
388                'close=i' => \$opt_close,
389                'delete' => \&delete_task,
390                'reopen' => \&reopen_task,
391                'task|t' => \&alter_task,
392                'terminate|end|d' => \&close_task,
393                'D' => sub {close_task(1);},
394                );
395 GetOptions %options;
396
397 if ($opt_close) {
398     late_close_task $opt_close;
399 } elsif (defined $opt_list) {
400     list $opt_list;
401 } elsif (defined $opt_back) {
402     move_task $opt_back;
403 } else {
404     toggle_task;
405 }