1
#!/usr/bin/perl
2
use strict;
3
use warnings;
4
use feature 'say';
5
use DateTime;
6
use GD;
7
use GD::Graph::bars;
8
use Getopt::Euclid;
9
10
sub calc_delta {
11
   my ($hour_start, $min_start) = split /:/, $_[0];
12
   my ($hour_end, $min_end) = split /:/, $_[1];
13
   my $start = DateTime->new(year => 2000, hour => $hour_start, minute => $min_start);
14
   my $end = DateTime->new(year => 2000, hour => $hour_end, minute => $min_end);
15
   my $duration = $end - $start;
16
   $duration->delta_minutes;
17
}
18
19
sub parse_timelog {
20
   my %tasks_by_date;
21
   open LOG, '<', $_[0] or die $!;
22
   while (<LOG>) {
23
      next if /^\s*$/; chomp;
24
      my ($date, $time, @task) = split /\s/;
25
      $time =~ s/:$//;
26
      my $duration = exists($tasks_by_date{$date}) ? calc_delta($tasks_by_date{$date}->[-1]{time}, $time) : 0;
27
      (my $group, @task) = split /:\s*/, "@task";
28
      push @{$tasks_by_date{$date}}, {group => $group, task => "@task", time => $time, minutes => $duration};
29
   }
30
   close LOG;
31
   return %tasks_by_date;
32
}
33
34
sub build_graph {
35
   my ($title, $x_label, $y_label) = @_;
36
   my $graph = GD::Graph::bars->new(700, 500);
37
   $graph->set(
38
      transparent => 0,
39
      x_label => $x_label,
40
      y_label => $y_label,
41
      title => $title,
42
      bar_spacing => 5,
43
      cycle_clrs => 1,
44
      overwrite => 1,
45
   ) or die $graph->error;
46
   return $graph;
47
}
48
sub plot_graph {
49
   my ($graph, $filename, @data) = @_;
50
   my $gd = $graph->plot(\@data) or die $graph->error;
51
   open IMG, '>', "$filename.png" or die $!;
52
   binmode IMG;
53
   print IMG $gd->png;
54
   close IMG;
55
   say "grafico salvo em: $filename.png";
56
}
57
58
sub default_graph {
59
   my ($date, %data) = @_;
60
   my %duration_by_group;
61
   foreach (@{$data{$date}}) {
62
      $duration_by_group{$_->{group}} += $_->{minutes};
63
   }
64
   my @data = (
65
      [ map { my $d = DateTime::Duration->new(minutes => $duration_by_group{$_}); sprintf("%s: %d:%02d", $_, $d->hours, $d->minutes) } keys %duration_by_group ],
66
      [ map { $duration_by_group{$_} / 60 } keys %duration_by_group ],
67
   );
68
   plot_graph(build_graph("Horas trabalhadas por grupo em $date", 'Grupo', 'Horas'), "$date", @data);
69
}
70
71
sub detailed_graph {
72
   my ($date, $group, %data) = @_;
73
   my @tasks = map { [$_->{task}, $_->{minutes}] } grep { $_->{group} eq $group } @{$data{$date}};
74
   my %duration_by_task;
75
   foreach (@tasks) {
76
      $duration_by_task{$_->[0]} += $_->[1];
77
   }
78
   my @data = (
79
      [ map { my $d = DateTime::Duration->new(minutes => $duration_by_task{$_}); sprintf("%d:%02d", $d->hours, $d->minutes) } keys %duration_by_task ],
80
      #[ map { $duration_by_task{$_} / 60 } keys %duration_by_task ],
81
   );
82
   foreach (keys %duration_by_task) {
83
      push @data, [ map { $duration_by_task{$_} / 60 } keys %duration_by_task ],
84
   }
85
   my $graph = build_graph("Detalhe de horas trabalhadas do grupo $group em $date", 'Tarefas', 'Horas');
86
   $graph->set_legend(keys %duration_by_task);
87
   plot_graph($graph, $date .'-'. lc($group), @data);
88
}
89
90
my $today = DateTime->now;
91
$today->add(days => -$ARGV{-d});
92
my %tasks_by_date = parse_timelog('/home/joenio/.gtimelog/timelog.txt');
93
if (exists $tasks_by_date{$today->ymd}) {
94
   if ($ARGV{-g}) {
95
      detailed_graph($today->ymd, $ARGV{-g}, %tasks_by_date);
96
   }
97
   else {
98
      default_graph($today->ymd, %tasks_by_date);
99
   }
100
}
101
else {
102
   warn "nao existem registros no dia: " . $today->ymd;
103
}
104
105
__END__
106
107
=head1 NAME
108
109
timelog-graph - Cria gráfico de barras a partir dos registros do gtimelog
110
111
=head1 VERSION
112
113
0.1
114
115
=head1 USAGE
116
117
    timelog-graph [options]
118
119
=head1 OPTIONS
120
121
=over
122
123
=item -d <d>
124
125
Gerar grafico de <d> dias atras.
126
127
=for Euclid:
128
    d.type: int >= 0
129
    d.default: 0
130
131
=item -g <g>
132
133
Gerar grafico detalhado do grupo <g>.
134
135
=item --version
136
137
=item --usage
138
139
=item --help
140
141
=item --man
142
143
Print the usual program information
144
145
=back
146
147
=head1 DEPENDENCIES
148
149
=over
150
151
=item L<DateTime>
152
153
=item L<GD::Graph>
154
155
=item L<Getopt::Euclid>
156
157
=back
158
159
=head1 TODO
160
161
=over
162
163
=item Calcular total de horas trabalho e nao trabalho
164
165
=item Corrigir data atual sendo gerada como dia seguinte
166
167
=item Gerar grafico de pizza com 24H
168
169
=item Corrigir problemas de codificação
170
171
=item Acessar arquivo timelog.txt usando uma intreface SQL
172
173
http://search.cpan.org/~jzucker/DBD-AnyData-0.09/AnyData.pm
174
http://search.cpan.org/~jzucker/DBD-CSV-0.22/lib/DBD/CSV.pm
175
http://search.cpan.org/~timb/DBI-1.607/lib/DBD/File.pm
176
177
=back
178
179
=head1 AUTHOR
180
181
Joenio Costa
182
183
=head1 COPYRIGHT
184
185
Copyright (c) 2009, Joenio Costa. All Rights Reserved.
186
This module is free software. It may be used, redistributed
187
and/or modified under the terms of the Perl Artistic License
188
(see http://www.perl.com/perl/misc/Artistic.html)