File Coverage

lib/App/TimeTracker/Command/Core.pm
Criterion Covered Total %
statement 130 274 47.4
branch 22 82 26.8
condition 9 46 19.5
subroutine 25 38 65.7
pod 0 14 0.0
total 186 454 40.9


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