File Coverage

lib/App/TimeTracker/Command/Core.pm
Criterion Covered Total %
statement 195 331 58.9
branch 38 112 33.9
condition 11 51 21.5
subroutine 33 42 78.5
pod 0 14 0.0
total 277 550 50.3


line stmt bran cond sub pod time code
1             package App::TimeTracker::Command::Core;
2              
3             # ABSTRACT: App::TimeTracker Core commands
4             our $VERSION = '3.010'; # VERSION
5              
6 6     6   34235 use strict;
  6         18  
  6         187  
7 6     6   51 use warnings;
  6         11  
  6         160  
8 6     6   140 use 5.010;
  6         29  
9              
10 6     6   450 use Moose::Role;
  6         456071  
  6         57  
11 6     6   36082 use Moose::Util::TypeConstraints;
  6         16  
  6         50  
12 6     6   15101 use App::TimeTracker::Utils qw(now pretty_date error_message);
  6         18  
  6         400  
13 6     6   952 use App::TimeTracker::Constants qw(MISSING_PROJECT_HELP_MSG);
  6         15  
  6         314  
14 6     6   502 use File::Copy qw(move);
  6         2430  
  6         364  
15 6     6   3182 use File::Find::Rule;
  6         50563  
  6         45  
16 6     6   977 use Data::Dumper;
  6         6729  
  6         317  
17 6     6   3526 use Text::Table;
  6         51084  
  6         215  
18 6     6   907 use DateTime;
  6         474095  
  6         25061  
19              
20             sub cmd_start {
21 5     5 0 9472 my $self = shift;
22              
23 5 100       282 unless ( $self->has_current_project ) {
24 1         7 error_message( MISSING_PROJECT_HELP_MSG );
25 1         42 exit;
26             }
27 4         25 $self->cmd_stop('no_exit');
28              
29 4   66     707 my $task = App::TimeTracker::Data::Task->new(
30             { start => $self->at || now(),
31             project => $self->project,
32             tags => $self->tags,
33             description => $self->description,
34             }
35             );
36 4         167 $self->_current_task($task);
37              
38 4         119 $task->do_start( $self->home );
39             }
40              
41             sub cmd_stop {
42 9     9 0 9514 my ( $self, $dont_exit ) = @_;
43              
44 9         340 my $task = App::TimeTracker::Data::Task->current( $self->home );
45 9 100       1994 unless ($task) {
46 3 50       14 return if $dont_exit;
47 0         0 say "Currently not working on anything";
48 0         0 exit;
49             }
50              
51 6         58 my $proto = App::TimeTracker::Proto->new();
52 6         4412 my $config = $proto->load_config( undef, $task->project );
53              
54 6         38 my $class = $proto->setup_class( $config, 'stop' );
55 6   33     229 my $stop_self = $class->name->new(
56             { home => $self->home,
57             at => $self->at || now(),
58             config => $config,
59             _current_project => $task->project,
60             }
61             );
62 6         242 $stop_self->_current_command('cmd_stop');
63 6         207 $stop_self->_previous_task($task);
64              
65             # Store in original self too (for plugin usage)
66 6         190 $self->_previous_task($task);
67              
68 6   33     33 $task->stop( $stop_self->at || now() );
69 6 100       217 if ( $task->stop < $task->start ) {
70 1         280 say sprintf(
71             qq{The stop time you specified (%s) is earlier than the start time (%s).\nThis makes no sense.},
72             $task->stop, $task->start );
73              
74 1         244 my $what_you_meant = $task->stop->clone;
75 1         17 for ( 1 .. 5 ) {
76 1         7 $what_you_meant->add( days => 1 );
77 1 50       1311 last if $what_you_meant > $task->start;
78             }
79 1 50       308 if ( $what_you_meant ne $task->start ) {
80 1         245 say "Maybe you wanted to do:\ntracker stop --at '"
81             . $what_you_meant->strftime('%Y-%m-%d %H:%M') . "'";
82             }
83             else {
84 0         0 say
85             "Maybe it helps if you use the long format to specify the stop time ('2012-01-10 00:15')?";
86             }
87 1         153 exit;
88             }
89 5         1418 $task->save( $stop_self->home );
90              
91 5         865 move(
92             $stop_self->home->file('current')->stringify,
93             $stop_self->home->file('previous')->stringify
94             );
95              
96 5         1608 say "Worked " . $task->duration . " on " . $task->say_project_tags;
97             }
98              
99             sub cmd_current {
100 2     2 0 3591 my $self = shift;
101              
102 2 100       77 if ( my $task = App::TimeTracker::Data::Task->current( $self->home ) ) {
    50          
103 1         266 say "Working "
104             . $task->_calc_duration( now() ) . " on "
105             . $task->say_project_tags;
106 1         47 say 'Started at ' . pretty_date( $task->start );
107             }
108             elsif ( my $prev = App::TimeTracker::Data::Task->previous( $self->home ) ) {
109 1         304 say
110             "Currently not working on anything, but the last thing you worked on was:";
111 1         7 say $prev->say_project_tags;
112 1         10 say 'Worked '
113             . $prev->rounded_minutes
114             . ' minutes from '
115             . pretty_date( $prev->start )
116             . ' till '
117             . pretty_date( $prev->stop );
118             }
119             else {
120 0         0 say
121             "Currently not working on anything, and I have no idea what you worked on earlier...";
122             }
123             }
124              
125             sub cmd_append {
126 1     1 0 1810 my $self = shift;
127              
128 1 50       44 if ( my $task = App::TimeTracker::Data::Task->current( $self->home ) ) {
    50          
129 0         0 say "Cannot 'append', you're actually already working on :"
130             . $task->say_project_tags . "\n";
131             }
132             elsif ( my $prev = App::TimeTracker::Data::Task->previous( $self->home ) ) {
133              
134 1         361 my $task = App::TimeTracker::Data::Task->new(
135             { start => $prev->stop,
136             project => $self->project,
137             tags => $self->tags,
138             }
139             );
140 1         39 $self->_current_task($task);
141 1         30 $task->do_start( $self->home );
142             }
143             else {
144 0         0 say
145             "Currently not working on anything and I have no idea what you've been doing.";
146             }
147             }
148              
149             sub cmd_continue {
150 0     0 0 0 my $self = shift;
151              
152 0 0       0 if ( my $task = App::TimeTracker::Data::Task->current( $self->home ) ) {
    0          
153 0         0 say "Cannot 'continue', you're working on something:\n"
154             . $task->say_project_tags;
155             }
156             elsif ( my $prev = App::TimeTracker::Data::Task->previous( $self->home ) ) {
157 0   0     0 my $task = App::TimeTracker::Data::Task->new(
158             { start => $self->at || now(),
159             project => $prev->project,
160             tags => $prev->tags,
161             }
162             );
163 0         0 $self->_current_task($task);
164 0         0 $task->do_start( $self->home );
165             }
166             else {
167 0         0 say
168             "Currently not working on anything, and I have no idea what you worked on earlier...";
169             }
170             }
171              
172             sub cmd_worked {
173 0     0 0 0 my $self = shift;
174              
175 0         0 my @files = $self->find_task_files(
176             { from => $self->from,
177             to => $self->to,
178             projects => $self->fprojects,
179             tags => $self->ftags,
180             parent => $self->fparent,
181             }
182             );
183              
184 0         0 my $total = 0;
185 0         0 foreach my $file (@files) {
186 0         0 my $task = App::TimeTracker::Data::Task->load( $file->stringify );
187 0   0     0 $total += $task->seconds // $task->_build_seconds;
188             }
189              
190 0         0 say $self->beautify_seconds($total);
191             }
192              
193             my %LIST_FORMATS = (
194             compact => [qw(project tag time)],
195             content => [qw(project tag description)],
196             medium => [qw(project tag time description)],
197             default => [qw(project tag duration start stop)],
198             all => [qw(project tag duration start stop seconds description file)],
199             long => [qw( project tag duration start stop description)],
200             );
201              
202             sub cmd_list {
203 0     0 0 0 my $self = shift;
204              
205 0         0 my @files = $self->find_task_files(
206             { from => $self->from,
207             to => $self->to,
208             projects => $self->fprojects,
209             tags => $self->ftags,
210             parent => $self->fparent,
211             }
212             );
213              
214 0         0 my $s = \' | ';
215 0 0       0 my $format =
    0          
216             $self->detail ? 'all' : $self->output ? $self->output : 'default';
217 0   0     0 my $selected_fields = $LIST_FORMATS{$format} || $LIST_FORMATS{default};
218 0         0 my %fields = map { $_ => 1 } @$selected_fields;
  0         0  
219              
220 0         0 my @header = map { ucfirst($_), $s } @$selected_fields;
  0         0  
221 0         0 pop(@header);
222 0         0 my $table = Text::Table->new(@header);
223              
224 0         0 my $total = 0;
225 0         0 foreach my $file (@files) {
226 0         0 my $task = App::TimeTracker::Data::Task->load( $file->stringify );
227 0   0     0 my $time = $task->seconds // $task->_build_seconds;
228 0         0 $total += $time;
229              
230 0         0 my @row;
231 0 0       0 push( @row, $task->project ) if $fields{project};
232 0 0 0     0 push( @row, join( ', ', @{ $task->tags } ) || ' ' ) if $fields{tag};
233 0 0 0     0 push( @row, $task->duration || 'working' ) if $fields{duration};
234 0 0       0 push( @row, pretty_date( $task->start ) ) if $fields{start};
235 0 0       0 push( @row, pretty_date( $task->stop ) ) if $fields{stop};
236 0 0       0 push( @row, $task->compact_time ) if $fields{time};
237 0 0       0 push( @row, $time ) if $fields{seconds};
238 0 0 0     0 push( @row, $task->description_short || ' ' ) if $fields{description};
239 0 0       0 push( @row, $file->stringify ) if $fields{file};
240              
241 0         0 $table->add(@row);
242             }
243              
244 0         0 print $table->title;
245 0         0 print $table->rule( '-', '+' );
246 0         0 print $table->body;
247 0         0 say "total " . $self->beautify_seconds($total);
248             }
249              
250             sub cmd_report {
251 2     2 0 3783 my $self = shift;
252              
253 2         15 my @files = $self->find_task_files(
254             { from => $self->from,
255             to => $self->to,
256             projects => $self->fprojects,
257             tags => $self->ftags,
258             parent => $self->fparent,
259             }
260             );
261              
262 2         198 my $total = 0;
263 2         8 my $report = {};
264 2         6 my $format = "%- 20s % 12s\n";
265 2         19 my $projects = $self->project_tree;
266              
267 2         20 foreach my $file (@files) {
268 5         20 my $task = App::TimeTracker::Data::Task->load( $file->stringify );
269 5   33     781 my $time = $task->seconds // $task->_build_seconds;
270 5         149 my $project = $task->project;
271              
272 5 50       22 if ( $time >= 60 * 60 * 8 ) {
273 0         0 say "Found dubious trackfile: " . $file->stringify;
274 0         0 say " Are you sure you worked "
275             . $self->beautify_seconds($time)
276             . " on one task?";
277             }
278 5         14 $total += $time;
279              
280 5 50       24 if ( $self->group eq 'week' ) {
281 0         0 my $week_num = $task->start->week_number;
282              
283 0         0 $report->{$week_num}{'_total'} += $time;
284 0         0 $report->{$week_num}{$project}{time} += $time;
285 0 0       0 unless ($report->{$week_num}{'_start'}) {
286 0         0 $report->{$week_num}{'_start'} = $self->_first_day_of_week(
287             $task->start->year, $task->start->week_number
288             )->dmy('.');
289 0         0 my $end = $task->start->add( { days => 6, hours=>23, minutes=>59 } );
290 0         0 $report->{$week_num}{'_end'} =
291             $self->_first_day_of_week( $end->year, $end->week_number )->dmy('.')
292             }
293             }
294              
295 5 50       43 if ( $self->group eq 'project' ) {
296 5         42 $report->{$project}{'_total'} += $time;
297              
298 5 50       29 if ( my $level = $self->detail ) {
299 0         0 my $detail = $task->get_detail($level);
300 0         0 my $tags = $detail->{tags};
301 0 0 0     0 if ( $tags && @$tags ) {
302              
303             # Only use the first assigned tag to calculate the aggregated times and use it
304             # as tag key.
305             # Otherwise the same trackfiles would be counted multiple times and the
306             # aggregated sums would not match up.
307 0         0 $report->{$project}{ $tags->[0] }{time} += $time;
308              
309 0         0 foreach my $tag (@$tags) {
310 0   0     0 $report->{$project}{ $tags->[0] }{desc} //= '';
311              
312 0 0       0 if ( my $desc = $detail->{desc} ) {
313             $report->{$project}{ $tags->[0] }{desc} .= $desc
314             . "\n"
315             if
316             index( $report->{$project}{ $tags->[0] }{desc},
317 0 0       0 $desc ) == -1;
318             }
319             }
320             }
321             else {
322 0         0 $report->{$project}{'_untagged'} += $time;
323             }
324             }
325             }
326             }
327              
328 2 50       11 if ( $self->group eq 'project' ) {
329              
330             # sum child-time to all ancestors
331 2         17 my %top_nodes;
332 2         15 foreach my $project ( sort keys %$report ) {
333 3         6 my @ancestors;
334 3         14 $self->_get_ancestors( $report, $projects, $project, \@ancestors );
335 3   50     13 my $time = $report->{$project}{'_total'} || 0;
336 3         8 foreach my $ancestor (@ancestors) {
337 0         0 $report->{$ancestor}{'_kids'} += $time;
338             }
339 3 50       9 $top_nodes{ $ancestors[0] }++ if @ancestors;
340 3 50       14 $top_nodes{$project}++ if !@ancestors;
341             }
342              
343 2         16 $self->_say_current_report_interval;
344 2         316 my $padding = '';
345 2         10 my $tagpadding = ' ';
346 2         13 foreach my $project ( sort keys %top_nodes ) {
347 3         17 $self->_print_report_tree( $report, $projects, $project, $padding,
348             $tagpadding );
349             }
350             }
351              
352 2 50       11 if ( $self->group eq 'week' ) {
353 0         0 my $s = \' | ';
354 0         0 my @header = map { ucfirst($_), $s } qw(week start end time);
  0         0  
355 0         0 pop(@header);
356 0         0 my $table = Text::Table->new(@header);
357              
358 0         0 foreach my $week ( sort keys %$report ) {
359 0         0 my @row;
360              
361 0         0 push @row, $week;
362 0         0 push @row, $report->{$week}->{_start};
363 0         0 push @row, $report->{$week}->{_end};
364 0         0 push @row, $self->beautify_seconds( $report->{$week}->{_total} );
365              
366 0         0 $table->add(@row);
367             }
368              
369 0         0 print $table->title;
370 0         0 print $table->rule( '-', '+' );
371 0         0 print $table->body;
372              
373             }
374              
375 2         24 printf( $format, 'total', $self->beautify_seconds($total) );
376             }
377              
378             sub _get_ancestors {
379 3     3   12 my ( $self, $report, $projects, $node, $ancestors ) = @_;
380 3         10 my $parent = $projects->{$node}{parent};
381 3 50       12 if ($parent) {
382 0         0 unshift( @$ancestors, $parent );
383 0         0 $self->_get_ancestors( $report, $projects, $parent, $ancestors );
384             }
385             }
386              
387             sub _first_day_of_week {
388 0     0   0 my ( $self, $year, $week ) = @_;
389              
390             # Week 1 is defined as the one containing January 4:
391 0         0 return DateTime->new( year => $year, month => 1, day => 4 )
392             ->add( weeks => ( $week - 1 ) )->truncate( to => 'week' );
393             }
394              
395             sub _print_report_tree {
396 3     3   12 my ( $self, $report, $projects, $project, $padding, $tagpadding ) = @_;
397 3         8 my $data = $report->{$project};
398              
399 3         4 my $sum = 0;
400 3 50       16 $sum += $data->{'_total'} if $data->{'_total'};
401 3 50       10 $sum += $data->{'_kids'} if $data->{'_kids'};
402 3 50       9 return unless $sum;
403              
404 3         6 my $format = "%- 20s % 12s";
405              
406 3         36 say sprintf(
407             $padding . $format,
408             substr( $project, 0, 20 ),
409             $self->beautify_seconds($sum)
410             );
411 3 50       34 if ( my $detail = $self->detail ) {
412             say sprintf( $padding . $tagpadding . $format,
413             'untagged', $self->beautify_seconds( delete $data->{'_untagged'} ) )
414 0 0       0 if $data->{'_untagged'};
415              
416 0         0 foreach my $tag (
417 0         0 sort { $data->{$b}->{time} <=> $data->{$a}->{time} }
418 0         0 grep {/^[^_]/} keys %{$data}
  0         0  
419             ) {
420 0         0 my $time = $data->{$tag}{time};
421              
422 0 0       0 if ( $detail eq 'description' ) {
    0          
423 0   0     0 my $desc = $data->{$tag}{desc} || 'no desc';
424 0         0 $desc =~ s/\s+$//;
425 0         0 $desc =~ s/\v/, /g;
426 0         0 say sprintf( $padding . $tagpadding . $format . ' %s',
427             $tag, $self->beautify_seconds($time), $desc );
428             }
429             elsif ( $detail eq 'tag' ) {
430 0         0 say sprintf( $padding . $tagpadding . $format,
431             $tag, $self->beautify_seconds($time) );
432             }
433             }
434             }
435 3         25 foreach my $child ( sort keys %{ $projects->{$project}{children} } ) {
  3         23  
436 0         0 $self->_print_report_tree( $report, $projects, $child,
437             $padding . ' ', $tagpadding );
438             }
439             }
440              
441             sub cmd_recalc_trackfile {
442 0     0 0 0 my $self = shift;
443 0         0 my $file = $self->trackfile;
444 0 0       0 unless ( -e $file ) {
445 0         0 $file =~ /(?<year>\d\d\d\d)(?<month>\d\d)\d\d-\d{6}_\w+\.trc/;
446 2 0 0 2   607 if ( $+{year} && $+{month} ) {
  2         509  
  2         1600  
  0         0  
447 0         0 $file = $self->home->file( $+{year}, $+{month}, $file )->stringify;
448 0 0       0 unless ( -e $file ) {
449 0         0 error_message( "Cannot find file %s", $self->trackfile );
450 0         0 exit;
451             }
452             }
453             }
454              
455 0         0 my $task = App::TimeTracker::Data::Task->load($file);
456 0         0 $task->save( $self->home );
457 0         0 say "recalced $file";
458             }
459              
460             sub cmd_show_config {
461 0     0 0 0 my $self = shift;
462 0         0 warn Data::Dumper::Dumper $self->config;
463             }
464              
465             sub cmd_init {
466 7     7 0 12124 my ( $self, $cwd ) = @_;
467 7   33     30 $cwd ||= Path::Class::Dir->new->absolute;
468 7 50       54 if ( -e $cwd->file('.tracker.json') ) {
469 0         0 error_message(
470             "This directory is already set up.\nTry 'tracker show_config' to see the current aggregated config."
471             );
472 0         0 exit;
473             }
474              
475 7         935 my $project;
476 7 100       334 if( $self->has_current_project ) {
477 5         165 $project = $self->_current_project;
478             } else {
479 2         9 my @dirs = $cwd->dir_list;
480 2         22 $project = $dirs[-1];
481             }
482 7         23 my $fh = $cwd->file('.tracker.json')->openw;
483 7         1997 say $fh <<EOCONFIG;
484             {
485             "project":"$project"
486             }
487             EOCONFIG
488              
489 7         343 my $projects_file = $self->home->file('projects.json');
490 7         594 my $coder = JSON::XS->new->utf8->pretty->canonical->relaxed;
491 7 50       32 if ( -e $projects_file ) {
492 7         342 my $projects = $coder->decode( scalar $projects_file->slurp );
493 7         1650 $projects->{$project} =
494             $cwd->file('.tracker.json')->absolute->stringify;
495 7         1099 $projects_file->spew( $coder->encode($projects) );
496             }
497              
498 7         2570 say "Set up this directory for time-tracking via file .tracker.json";
499             }
500              
501             sub cmd_plugins {
502 0     0 0 0 my $self = shift;
503              
504             my $base =
505 0         0 Path::Class::file( $INC{'App/TimeTracker/Command/Core.pm'} )->parent;
506 0         0 my @hits;
507 0         0 while ( my $file = $base->next ) {
508 0 0       0 next unless -f $file;
509 0 0       0 next if $file->basename eq 'Core.pm';
510 0         0 my $plugin = $file->basename;
511 0         0 $plugin =~ s/\.pm$//;
512 0         0 push( @hits, $plugin );
513             }
514 0         0 say "Installed plugins:\n " . join( ', ', @hits );
515             }
516              
517             sub cmd_version {
518 2     2 0 1900 my $self = shift;
519 2         53 say "This is App::TimeTracker, version " . App::TimeTracker->VERSION;
520 2         16 exit;
521             }
522              
523             sub cmd_commands {
524 78     1 0 3559 my $self = shift;
525              
526 15         36 my @commands;
527 15         43 foreach my $method ( $self->meta->get_all_method_names ) {
528 79 100       1728 next unless $method =~ /^cmd_/;
529 15         43 $method =~ s/^cmd_//;
530 14         30 push( @commands, $method );
531             }
532              
533 2         103 @commands = sort @commands;
534              
535 2 50 66     17 if ( $self->can('autocomplete')
536             && $self->autocomplete ) {
537 14         143 say join( ' ', @commands );
538             }
539             else {
540 2         70 say "Available commands:";
541 1         6 foreach my $command (@commands) {
542 14         156 say "\t$command";
543             }
544             }
545 1         8 exit;
546             }
547              
548             sub _load_attribs_worked {
549 3     3   16072 my ( $class, $meta ) = @_;
550 3         30 $meta->add_attribute(
551             'from' => {
552             isa => 'TT::DateTime',
553             is => 'ro',
554             coerce => 1,
555             lazy_build => 1,
556              
557             #cmd_aliases => [qw/start/],
558             documentation => 'Beginning of time period to report',
559             }
560             );
561 3         35751 $meta->add_attribute(
562             'to' => {
563             isa => 'TT::DateTime',
564             is => 'ro',
565             coerce => 1,
566              
567             #cmd_aliases => [qw/end/],
568             lazy_build => 1,
569             documentation => 'End of time period to report',
570             }
571             );
572 3         31418 $meta->add_attribute(
573             'this' => {
574             isa => 'TT::Duration',
575             is => 'ro',
576             documentation =>
577             'Filter by current time period [day|week|month|year], e.g. "--this day" => today',
578             }
579             );
580 3         11182 $meta->add_attribute(
581             'last' => {
582             isa => 'TT::Duration',
583             is => 'ro',
584             documentation =>
585             'Filter by previous time period [day|week|month|year], e.g. "--last day" => yesterday',
586             }
587             );
588 3         10743 $meta->add_attribute(
589             'fprojects' => {
590             isa => 'ArrayRef',
591             is => 'ro',
592             documentation => 'Filter by project',
593             }
594             );
595 3         10389 $meta->add_attribute(
596             'ftags' => {
597             isa => 'ArrayRef',
598             is => 'ro',
599             documentation => 'Filter by tag',
600             }
601             );
602 3         9537 $meta->add_attribute(
603             'fparent' => {
604             isa => 'Str',
605             is => 'ro',
606             documentation => 'Filter by parent (get all children)',
607             }
608             );
609              
610             }
611              
612             sub _load_attribs_commands {
613 1     1   8 my ( $class, $meta ) = @_;
614 1         10 $meta->add_attribute(
615             'autocomplete' => {
616             isa => 'Bool',
617             is => 'ro',
618             default => 0,
619             documentation => 'Output for autocomplete',
620             }
621             );
622             }
623              
624             sub _load_attribs_list {
625 0     0   0 my ( $class, $meta ) = @_;
626 0         0 $class->_load_attribs_worked($meta);
627 0         0 $meta->add_attribute(
628             'detail' => {
629             isa => 'Bool',
630             is => 'ro',
631             default => 0,
632             documentation => 'Show all fields',
633             }
634             );
635 0         0 $meta->add_attribute(
636             'output' => {
637             isa => 'Str',
638             is => 'ro',
639             documentation => 'Specify output format. One of: '
640             . join( ', ', sort keys %LIST_FORMATS ),
641             }
642             );
643             }
644              
645             sub _load_attribs_report {
646 2     2   10 my ( $class, $meta ) = @_;
647 2         13 $class->_load_attribs_worked($meta);
648 2         6340 $meta->add_attribute(
649             'detail' => {
650             isa => enum( [qw(tag description)] ),
651             is => 'ro',
652             documentation => 'Be detailed: [tag|description]',
653             }
654             );
655 2         9544 $meta->add_attribute(
656             'group' => {
657             isa => enum( [qw(project week)] ),
658             is => 'ro',
659             default => 'project',
660             documentation => 'Genereta Report by week or project.'
661             }
662             );
663             }
664              
665             sub _load_attribs_start {
666 17     17   63 my ( $class, $meta ) = @_;
667 17         156 $meta->add_attribute(
668             'at' => {
669             isa => 'TT::DateTime',
670             is => 'ro',
671             coerce => 1,
672             documentation => 'Start at',
673             }
674             );
675 17         71454 $meta->add_attribute(
676             'project' => {
677             isa => 'Str',
678             is => 'ro',
679             documentation => 'Project name',
680             lazy_build => 1,
681             }
682             );
683 17         148692 $meta->add_attribute(
684             'description' => {
685             isa => 'Str',
686             is => 'rw',
687             documentation => 'Description',
688             }
689             );
690             }
691              
692             sub _build_project {
693 5     5   128 my $self = shift;
694 5         213 return $self->_current_project;
695             }
696              
697             *_load_attribs_append = \&_load_attribs_start;
698             *_load_attribs_continue = \&_load_attribs_start;
699             *_load_attribs_stop = \&_load_attribs_start;
700              
701             sub _load_attribs_recalc_trackfile {
702 0     0   0 my ( $class, $meta ) = @_;
703 0         0 $meta->add_attribute(
704             'trackfile' => {
705             isa => 'Str',
706             is => 'ro',
707             required => 1,
708             }
709             );
710             }
711              
712             sub _load_attribs_init {
713 4     4   13 my ( $class, $meta ) = @_;
714 4         27 $meta->add_attribute(
715             'project' => {
716             isa => 'Str',
717             is => 'ro',
718             documentation => 'Project name to initialize',
719             lazy_build => 1,
720             }
721             );
722             }
723              
724             sub _build_from {
725 6     6   8774 my $self = shift;
726 6 100       29 if ( my $last = $self->last ) {
    50          
727 3         31 return now()->truncate( to => $last )->subtract( $last . 's' => 1 );
728             }
729             elsif ( my $this = $self->this ) {
730 3         68 return now()->truncate( to => $this );
731             }
732             else {
733 0         0 return now()->truncate( to => 'month' );
734             }
735             }
736              
737             sub _build_to {
738 6     6   13314 my $self = shift;
739              
740 6 50 66     29 if ( my $date = $self->this || $self->last ) {
741 6         86 return $self->from->clone->add( $date . 's' => 1 )
742             ->subtract( seconds => 1 );
743             }
744             else {
745 0         0 return now();
746             }
747             }
748              
749             sub _say_current_report_interval {
750 2     2   6 my $self = shift;
751 2         11 printf( "From %s to %s you worked on:\n", $self->from, $self->to );
752             }
753              
754 5     5   61 no Moose::Role;
  5         12  
  5         62  
755              
756             q{ listening to: Train noises on my way from Wien to Graz }
757              
758             __END__
759              
760             =pod
761              
762             =encoding UTF-8
763              
764             =head1 NAME
765              
766             App::TimeTracker::Command::Core - App::TimeTracker Core commands
767              
768             =head1 VERSION
769              
770             version 3.010
771              
772             =head1 CORE COMMANDS
773              
774             More commands are implemented in various plugins. Plugins might also alter and/or amend commands.
775              
776             =head2 start
777              
778             ~/perl/Your-Project$ tracker start
779             Started working on Your-Project at 23:44:19
780              
781             Start tracking the current project now. Automatically stop the previous task, if there was one.
782              
783             =head3 Options:
784              
785             =head4 --at TT::DateTime
786              
787             ~/perl/Your-Project$ tracker start --at 12:42
788             ~/perl/Your-Project$ tracker start --at '2011-02-26 12:42'
789              
790             Start at the specified time/datetime instead of now. If only a time is
791             provided, the day defaults to today. See L<TT::DateTime> in L<App::TimeTracker>.
792              
793             =head4 --project SomeProject
794              
795             ~/perl/Your-Project$ tracker start --project SomeProject
796              
797             Use the specified project instead of the one determined by the current
798             working directory.
799              
800             =head4 --description 'some prosa'
801              
802             ~/perl/Your-Project$ tracker start --description "Solving nasty bug"
803              
804             Supply some descriptive text to the task. Might be used by reporting plugins etc.
805              
806             =head4 --tags RT1234 [Multiple]
807              
808             ~/perl/Your-Project$ tracker start --tag RT1234 --tag testing
809              
810             A list of tags to add to the task. Can be used by reporting plugins.
811              
812             =head2 stop
813              
814             ~/perl/Your-Project$ tracker stop
815             Worked 00:20:50 on Your-Project
816              
817             Stop tracking the current project now.
818              
819             =head3 Options
820              
821             =head4 --at TT::DateTime
822              
823             Stop at the specified time/datetime instead of now.
824              
825             =head2 continue
826              
827             ~/perl/Your-Project$ tracker continue
828              
829             Continue working on the previous task after a break.
830              
831             Example:
832              
833             ~$ tracker start --project ExplainContinue --tag testing
834             Started working on ExplainContinue (testing) at 12:42
835              
836             # ... time passes, it's now 13:17
837             ~$ tracker stop
838             Worked 00:35:00 on ExplainContinue
839              
840             # back from lunch at 13:58
841             ~$ tracker continue
842             Started working on ExplainContinue (testing) at 13:58
843              
844             =head3 Options:
845              
846             same as L<start|/start>
847              
848             =head2 append
849              
850             ~/perl/Your-Project$ tracker append
851              
852             Start working on a task at exactly the time you stopped working at the previous task.
853              
854             Example:
855              
856             ~$ tracker start --project ExplainAppend --tag RT1234
857             Started working on ExplainAppend (RT1234) at 14:23
858              
859             # ... time passes (14:46)
860             ~$ tracker stop
861             Worked 00:23:00 on ExplainAppend (RT1234)
862              
863             # start working on new ticket
864             # ...
865             # but forgot to hit start (14:53)
866             ~$ tracker append --tag RT7890
867             Started working on ExplainAppend (RT7890) at 14:46
868              
869             =head3 Options:
870              
871             same as L<start|/start>
872              
873             =head2 current
874              
875             ~/perl/Your-Project$ tracker current
876             Working 00:20:17 on Your-Project
877              
878             Display what you're currently working on, and for how long.
879              
880             =head3 No options
881              
882             =head2 worked
883              
884             ~/perl/Your-Project$ tracker worked [SPAN]
885              
886             Report the total time worked in the given time span, maybe limited to
887             some projects.
888              
889             =head3 Options:
890              
891             =head4 --from TT::DateTime [REQUIRED (or use --this/--last)]
892              
893             Begin of reporting interval, defaults to first day of current month.
894              
895             =head4 --to TT::DateTime [REQUIRED (or use --this/--last)]
896              
897             End of reporting interval, default to DateTime->now.
898              
899             =head4 --this [day, week, month, year]
900              
901             Automatically set C<--from> and C<--to> to the calculated values
902              
903             ~/perl/Your-Project$ tracker worked --this week
904             17:01:50
905              
906             =head4 --last [day, week, month, year]
907              
908             Automatically set C<--from> and C<--to> to the calculated values
909              
910             ~/perl/Your-Project$ tracker worked --last day (=yesterday)
911             06:39:12
912              
913             =head4 --project SomeProject [Multiple]
914              
915             ~$ tracker worked --last day --project SomeProject
916             02:04:47
917              
918             =head2 report
919              
920             ~/perl/Your-Project$ tracker report
921              
922             Print out a detailed report of what you did. All worked times are
923             summed up per project (and optionally per tag)
924              
925             =head3 Options:
926              
927             The same options as for L<worked|/worked>, plus:
928              
929             =head4 --detail
930              
931             ~/perl/Your-Project$ tracker report --last month --detail tag
932              
933             Valid options are: tag, description
934              
935             Will print the tag(s) and/or description.
936              
937             Also calc sums per tag.
938              
939             =head4 --verbose
940              
941             ~/perl/Your-Project$ tracker report --last month --verbose
942              
943             Lists all found trackfiles and their respective duration before printing out the report.
944              
945             =head2 list
946              
947             ~/perl/Your-Project$ tracker list
948              
949             Print out a detailed report of what you did in a tabular format including start and stop
950             times.
951              
952             =head3 Options:
953              
954             The same options as for L<report|/report>
955              
956             =head2 init
957              
958             ~/perl/Your-Project$ tracker init
959              
960             Initialize current directory as a project in which to track time. This step
961             is required before one can use time tracking commands such as
962             L<start|/start> and L<stop|/stop>. The initialization step creates a rather
963             empty F<.tracker.json> config file in the current directory.
964              
965             =head3 No options
966              
967             =head2 show_config
968              
969             ~/perl/Your-Project$ tracker show_config
970              
971             Dump the config that's valid for the current directory. Might be handy when setting up plugins etc.
972              
973             =head3 No options
974              
975             =head2 plugins
976              
977             ~/perl/Your-Project$ tracker plugins
978              
979             List all installed plugins (i.e. stuff in C<App::TimeTracker::Command::*>)
980              
981             =head3 No options
982              
983             =head2 recalc_trackfile
984              
985             ~/perl/Your-Project$ tracker recalc_trackfile --trackfile 20110808-232327_App_TimeTracker.trc
986              
987             Recalculates the duration stored in an old trackfile. Might be useful
988             after a manual update in a trackfile. Might be unnecessary in the
989             future, as soon as task duration is always calculated lazily.
990              
991             =head3 Options:
992              
993             =head4 --trackfile name_of_trackfile.trc REQUIRED
994              
995             Only the name of the trackfile is required, but you can also pass in
996             the absolute path to the file. Broken trackfiles are sometimes
997             reported during L<report|/report>.
998              
999             =head2 commands
1000              
1001             ~/perl/Your-Project$ tracker commands
1002              
1003             List all available commands, based on your current config.
1004              
1005             =head3 No options
1006              
1007             =head1 AUTHOR
1008              
1009             Thomas Klausner <domm@plix.at>
1010              
1011             =head1 COPYRIGHT AND LICENSE
1012              
1013             This software is copyright (c) 2011 - 2021 by Thomas Klausner.
1014              
1015             This is free software; you can redistribute it and/or modify it under
1016             the same terms as the Perl 5 programming language system itself.
1017              
1018             =cut