File Coverage

lib/App/TimeTracker/Command/Core.pm
Criterion Covered Total %
statement 182 294 61.9
branch 32 100 32.0
condition 11 51 21.5
subroutine 31 39 79.4
pod 0 14 0.0
total 256 498 51.4


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