File: | lib/App/TimeTracker.pm |
Coverage: | 91.2% |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package App::TimeTracker; | ||||||
2 | 8 8 8 | 528750 25 188 | use strict; | ||||
3 | 8 8 8 | 41 17 247 | use warnings; | ||||
4 | 8 8 8 8 8 8 | 446 462 246 2457 74 103 | use 5.010; | ||||
5 | |||||||
6 | our $VERSION = "2.005"; | ||||||
7 | # ABSTRACT: Track time spend on projects from the commandline | ||||||
8 | |||||||
9 | 8 8 8 | 1051 23 174 | use App::TimeTracker::Data::Task; | ||||
10 | |||||||
11 | 8 8 8 | 1521 226313 161 | use DateTime; | ||||
12 | 8 8 8 | 43 30 84 | use Moose; | ||||
13 | 8 8 8 | 37718 39 114 | use Moose::Util::TypeConstraints; | ||||
14 | 8 8 8 | 18246 62469 93 | use Path::Class::Iterator; | ||||
15 | 8 8 8 | 1071 445701 1924 | use MooseX::Storage::Format::JSONpm; | ||||
16 | |||||||
17 | with qw( | ||||||
18 | MooseX::Getopt | ||||||
19 | ); | ||||||
20 | |||||||
21 | subtype 'TT::DateTime' => as class_type('DateTime'); | ||||||
22 | subtype 'TT::RT' => as 'Int'; | ||||||
23 | |||||||
24 | coerce 'TT::RT' | ||||||
25 | => from 'Str' | ||||||
26 | => via { | ||||||
27 | my $raw = $_; | ||||||
28 | $raw=~s/\D//g; | ||||||
29 | return $raw; | ||||||
30 | }; | ||||||
31 | |||||||
32 | coerce 'TT::DateTime' | ||||||
33 | => from 'Str' | ||||||
34 | => via { | ||||||
35 | my $raw = $_; | ||||||
36 | my $dt = DateTime->now; | ||||||
37 | $dt->set_time_zone('local'); | ||||||
38 | |||||||
39 | given (length($raw)) { | ||||||
40 | when (5) { # "13:42" | ||||||
41 | $dt = DateTime->today; | ||||||
42 | my ($h,$m)=split(/:/,$raw); | ||||||
43 | $dt->set(hour=>$h, minute=>$m); | ||||||
44 | return $dt; | ||||||
45 | } | ||||||
46 | when (10) { # "2010-02-26" | ||||||
47 | require DateTime::Format::Strptime; | ||||||
48 | my $dp = DateTime::Format::Strptime->new(pattern=>'%Y-%m-%d'); | ||||||
49 | $dt = $dp->parse_datetime($raw); | ||||||
50 | } | ||||||
51 | when (16) { # "2010-02-26 23:42" | ||||||
52 | require DateTime::Format::Strptime; | ||||||
53 | my $dp = DateTime::Format::Strptime->new(pattern=>'%Y-%m-%d %H:%M'); | ||||||
54 | $dt = $dp->parse_datetime($raw); | ||||||
55 | } | ||||||
56 | } | ||||||
57 | return $dt; | ||||||
58 | }; | ||||||
59 | |||||||
60 | MooseX::Getopt::OptionTypeMap->add_option_type_to_map( | ||||||
61 | 'TT::DateTime' => '=s', | ||||||
62 | ); | ||||||
63 | MooseX::Getopt::OptionTypeMap->add_option_type_to_map( | ||||||
64 | 'TT::RT' => '=i', | ||||||
65 | ); | ||||||
66 | |||||||
67 | 8 8 8 | 47 19 107 | no Moose::Util::TypeConstraints; | ||||
68 | |||||||
69 | has 'home' => ( | ||||||
70 | is=>'ro', | ||||||
71 | isa=>'Path::Class::Dir', | ||||||
72 | traits => [ 'NoGetopt' ], | ||||||
73 | required=>1, | ||||||
74 | ); | ||||||
75 | has 'config' => ( | ||||||
76 | is=>'ro', | ||||||
77 | isa=>'HashRef', | ||||||
78 | required=>1, | ||||||
79 | traits => [ 'NoGetopt' ], | ||||||
80 | ); | ||||||
81 | has '_currentproject' => ( | ||||||
82 | is=>'ro', | ||||||
83 | isa=>'Str', | ||||||
84 | traits => [ 'NoGetopt' ], | ||||||
85 | ); | ||||||
86 | |||||||
87 | has 'tags' => ( | ||||||
88 | isa=>'ArrayRef', | ||||||
89 | is=>'ro', | ||||||
90 | traits => ['Array'], | ||||||
91 | default=>sub {[]}, | ||||||
92 | handles => { | ||||||
93 | insert_tag => 'unshift', | ||||||
94 | 2 1 | 3289 7 | add_tag => 'push', | ||||
95 | } | ||||||
96 | ); | ||||||
97 | |||||||
98 | has '_current_command' => ( | ||||||
99 | isa=>'Str', | ||||||
100 | 2 1 | 5 4 | is=>'rw', | ||||
101 | traits => [ 'NoGetopt' ], | ||||||
102 | ); | ||||||
103 | |||||||
104 | has '_current_task' => ( | ||||||
105 | isa=>'App::TimeTracker::Data::Task', | ||||||
106 | 2 2 1 1 | 5 6 3 3 | is=>'rw', | ||||
107 | traits => [ 'NoGetopt' ], | ||||||
108 | 2 2 1 1 | 4 12 3 5 | ); | ||||
109 | has '_previous_task' => ( | ||||||
110 | isa=>'App::TimeTracker::Data::Task', | ||||||
111 | is=>'rw', | ||||||
112 | traits => [ 'NoGetopt' ], | ||||||
113 | ); | ||||||
114 | |||||||
115 | sub run { | ||||||
116 | 1 | 0 | 855 | my $self = shift; | |||
117 | 1 | 10 | my $command = 'cmd_'.($self->extra_argv->[0] || 'missing'); | ||||
118 | |||||||
119 | 1 | 22 | $self->cmd_commands unless $self->can($command); | ||||
120 | 0 | 0 | $self->_current_command($command); | ||||
121 | 0 | 0 | $self->$command; | ||||
122 | } | ||||||
123 | |||||||
124 | sub now { | ||||||
125 | 2 | 0 | 271865 | my $dt = DateTime->now(); | |||
126 | 2 | 670 | $dt->set_time_zone('local'); | ||||
127 | 2 | 15939 | return $dt; | ||||
128 | } | ||||||
129 | |||||||
130 | sub beautify_seconds { | ||||||
131 | 10 | 0 | 6789 | my ( $self, $s ) = @_; | |||
132 | 10 | 41 | return '0' unless $s; | ||||
133 | 8 | 18 | my ( $m, $h )= (0, 0); | ||||
134 | |||||||
135 | 8 | 27 | if ( $s >= 60 ) { | ||||
136 | 7 | 17 | $m = int( $s / 60 ); | ||||
137 | 7 | 17 | $s = $s - ( $m * 60 ); | ||||
138 | } | ||||||
139 | 8 | 44 | if ( $m && $m >= 60 ) { | ||||
140 | 4 | 10 | $h = int( $m / 60 ); | ||||
141 | 4 | 8 | $m = $m - ( $h * 60 ); | ||||
142 | } | ||||||
143 | 8 | 66 | return sprintf("%02d:%02d:%02d",$h,$m,$s); | ||||
144 | } | ||||||
145 | |||||||
146 | sub find_task_files { | ||||||
147 | 3 | 0 | 9217 | my ($self, $args) = @_; | |||
148 | |||||||
149 | 3 | 10 | my ($cmp_from, $cmp_to); | ||||
150 | |||||||
151 | 3 | 20 | if (my $from = $args->{from}) { | ||||
152 | 2 | 89 | my $to = $args->{to} || $self->now; | ||||
153 | 2 | 80 | $to->set(hour=>23,minute=>59,second=>59) unless $to->hour; | ||||
154 | 2 | 355 | $cmp_from = $from->strftime("%Y%m%d%H%M%S"); | ||||
155 | 2 | 155 | $cmp_to = $to->strftime("%Y%m%d%H%M%S"); | ||||
156 | } | ||||||
157 | 3 | 115 | my $projects; | ||||
158 | 3 | 14 | if ($args->{projects}) { | ||||
159 | 2 2 | 6 8 | $projects = join('|',@{$args->{projects}}); | ||||
160 | } | ||||||
161 | |||||||
162 | 3 | 9 | my @found; | ||||
163 | 3 | 26 | my $iterator = Path::Class::Iterator->new( | ||||
164 | root => $self->home, | ||||||
165 | ); | ||||||
166 | 3 | 7612 | until ($iterator->done) { | ||||
167 | 75 | 1596 | my $file = $iterator->next; | ||||
168 | 75 | 78683 | next unless -f $file; | ||||
169 | 69 | 3654 | my $name = $file->basename; | ||||
170 | 69 | 552 | next unless $name =~/\.trc$/; | ||||
171 | |||||||
172 | 69 | 183 | if ($cmp_from) { | ||||
173 | 46 | 241 | $file =~ /(\d{8})-(\d{6})/; | ||||
174 | 46 | 2186 | my $time = $1 . $2; | ||||
175 | 46 | 219 | next if $time < $cmp_from; | ||||
176 | 35 | 129 | next if $time > $cmp_to; | ||||
177 | } | ||||||
178 | |||||||
179 | 52 | 592 | next if $projects && ! ($name ~~ /$projects/); | ||||
180 | |||||||
181 | 29 | 247 | push(@found,$file); | ||||
182 | } | ||||||
183 | 3 | 68 | return sort @found; | ||||
184 | } | ||||||
185 | |||||||
186 | 1; |