File Coverage

lib/App/TimeTracker/Proto.pm
Criterion Covered Total %
statement 134 157 85.3
branch 25 36 69.4
condition 15 29 51.7
subroutine 27 30 90.0
pod 0 5 0.0
total 201 257 78.2


line stmt bran cond sub pod time code
1             package App::TimeTracker::Proto;
2              
3             # ABSTRACT: App::TimeTracker Proto Class
4             our $VERSION = '3.008'; # VERSION
5              
6 6     6   1139875 use strict;
  6         40  
  6         184  
7 6     6   90 use warnings;
  6         16  
  6         170  
8 6     6   146 use 5.010;
  6         20  
9              
10 6     6   2121 use App::TimeTracker::Utils qw(error_message);
  6         13  
  6         350  
11 6     6   3069 use Moose;
  6         2389143  
  6         46  
12 6     6   47645 use MooseX::Types::Path::Class;
  6         2130423  
  6         60  
13 6     6   8010 use File::HomeDir ();
  6         16156  
  6         151  
14 6     6   38 use Path::Class;
  6         13  
  6         502  
15 6     6   3489 use Hash::Merge qw(merge);
  6         48169  
  6         391  
16 6     6   3624 use JSON::XS;
  6         25514  
  6         350  
17 6     6   52 use Carp;
  6         13  
  6         313  
18 6     6   74 use Try::Tiny;
  6         33  
  6         301  
19 6     6   2607 use App::TimeTracker::Data::Task;
  6         33  
  6         302  
20 6     6   3127 use App::TimeTracker::Constants qw(MISSING_PROJECT_HELP_MSG);
  6         19  
  6         10260  
21              
22             has 'home' => (
23             is => 'ro',
24             isa => 'Path::Class::Dir',
25             lazy_build => 1,
26             );
27              
28             sub _build_home {
29 7     7   2836 my ( $self, $home ) = @_;
30              
31             $home ||=
32 7   33     132 Path::Class::Dir->new( $ENV{TRACKER_HOME} || (File::HomeDir->my_home, '.TimeTracker' ));
      66        
33 7 50       379 unless (-d $home) {
34 0         0 $home->mkpath;
35 0         0 $self->_write_config_file_locations( {} );
36 0         0 my $fh = $self->global_config_file->openw;
37 0         0 print $fh $self->json_decoder->encode( {} );
38 0         0 close $fh;
39             }
40 7         517 return $home;
41             }
42              
43             has 'global_config_file' => (
44             is => 'ro',
45             isa => 'Path::Class::File',
46             lazy_build => 1,
47             );
48              
49             sub _build_global_config_file {
50 14     14   39 my $self = shift;
51 14         377 return $self->home->file('tracker.json');
52             }
53              
54             has 'config_file_locations' => (
55             is => 'ro',
56             isa => 'HashRef',
57             lazy_build => 1,
58             );
59              
60             sub _build_config_file_locations {
61 14     14   38 my $self = shift;
62 14         416 my $file = $self->home->file('projects.json');
63 14 50 33     1263 if ( -e $file && -s $file ) {
64 14         1380 my $decoded_json;
65             try {
66 14     14   1179 $decoded_json = decode_json( $file->slurp );
67             }
68             catch {
69 0     0   0 error_message( "Could not json decode '%s'.\nError: '%s'", $file, $_ );
70 0         0 exit 1;
71 14         191 };
72 14         4217 return $decoded_json;
73             }
74             else {
75 0         0 return {};
76             }
77             }
78              
79             has 'project' => ( is => 'rw', isa => 'Str', predicate => 'has_project' );
80              
81             has 'json_decoder' => ( is => 'ro', isa => 'JSON::XS', lazy_build => 1 );
82              
83             sub _build_json_decoder {
84 14     14   40 my $self = shift;
85 14         573 return JSON::XS->new->utf8->pretty->relaxed;
86             }
87              
88             sub run {
89 1     1 0 11434 my $self = shift;
90              
91             try {
92 1     1   89 my $config = $self->load_config;
93 1         6 my $class = $self->setup_class($config);
94 1 50       110 $class->name->new_with_options( {
95             home => $self->home,
96             config => $config,
97             ( $self->has_project
98             ? ( _current_project => $self->project )
99             : ()
100             ),
101             } )->run;
102             }
103             catch {
104 0     0   0 my $e = $_;
105 0 0 0     0 if ( blessed $e && $e->can('message') ) {
106 0         0 warn $e->message, "\n";
107             }
108             else {
109 0         0 warn "$e\n";
110             }
111             }
112 1         14 }
113              
114             sub setup_class {
115 25     25 0 27268 my ( $self, $config, $command ) = @_;
116              
117             # unique plugins
118 25   100     199 $config->{plugins} ||= [];
119 25         113 my %plugins_unique = map { $_ => 1 } @{ $config->{plugins} };
  0         0  
  25         127  
120 25         114 $config->{plugins} = [ keys %plugins_unique ];
121              
122             my $class = Moose::Meta::Class->create_anon_class(
123             superclasses => ['App::TimeTracker'],
124             roles => [
125 25         402 map { 'App::TimeTracker::Command::' . $_ } 'Core',
126 25         95 @{ $config->{plugins} }
  25         70  
127             ],
128             );
129              
130 25         269462 my %commands;
131 25         286 foreach my $method ( $class->get_all_method_names ) {
132 1875 100       38822 next unless $method =~ /^cmd_/;
133 350         901 $method =~ s/^cmd_//;
134 350         878 $commands{$method} = 1;
135             }
136              
137 25         220 my $load_attribs_for_command;
138 25 100       133 foreach my $cmd ( $command ? $command : @ARGV) {
139 24 50       115 if ( defined $commands{$cmd} ) {
140 24         70 $load_attribs_for_command = '_load_attribs_' . $cmd;
141              
142 24 50 66     324 if ($cmd eq 'start' && !$self->has_project) {
143 0         0 error_message( MISSING_PROJECT_HELP_MSG );
144 0         0 exit;
145             }
146              
147 24         84 last;
148             }
149             }
150 25 100 100     259 if ( $load_attribs_for_command
151             && $class->has_method($load_attribs_for_command) )
152             {
153 20         713 $class->name->$load_attribs_for_command($class, $config);
154             }
155 25         90981 $class->make_immutable();
156 25         11807 return $class;
157             }
158              
159             sub load_config {
160 15     15 0 52757 my ($self, $dir, $project) = @_;
161 15   66     123 $dir ||= Path::Class::Dir->new->absolute;
162 15         1450 my $config = {};
163 15         38 my @used_config_files;
164 15         569 my $cfl = $self->config_file_locations;
165              
166 15         76 my $projects = $self->slurp_projects;
167 15         229 my $opt_parser = Getopt::Long::Parser->new(
168             config => [qw( no_auto_help pass_through )] );
169 15         2038 $opt_parser->getoptions( "project=s" => \$project );
170              
171 15 100       4297 if ( defined $project ) {
172 8 100       40 if ( my $project_config = $projects->{$project} ) {
173 7         269 $self->project($project);
174 7         53 $dir = Path::Class::Dir->new($project_config);
175             }
176             else {
177 1         41 say "Unknown project: $project";
178 1         39 $self->project($project);
179 1         8 $dir = Path::Class::Dir->new( '/ttfake', $project );
180             }
181             }
182              
183 15         397 my $try = 0;
184 15         82 $dir = $dir->absolute;
185 15         517 WALKUP: while ( $try++ < 30 ) {
186 68         3442 my $config_file = $dir->file('.tracker.json');
187 68         4288 my $this_config;
188 68 100       217 if ( -e $config_file ) {
189 15         682 push( @used_config_files, $config_file->stringify );
190 15         491 $this_config = $self->slurp_config($config_file);
191 15         316 $config = merge( $config, $this_config );
192              
193 15         2633 my @path = $config_file->parent->dir_list;
194 15         279 my $project = $path[-1];
195 15         44 $cfl->{$project} = $config_file->stringify;
196 15 100       1024 $self->project($project)
197             unless $self->has_project;
198              
199             }
200 68 100       2460 last WALKUP if $dir->parent eq $dir;
201              
202 53 50       5389 if ( my $parent = $this_config->{'parent'} ) {
203 0 0       0 if ( $projects->{$parent} ) {
204 0         0 $dir = Path::Class::file( $projects->{$parent} )->parent;
205             }
206             else {
207 0         0 $dir = $dir->parent;
208 0         0 say
209             "Cannot find project >$parent< that's specified as a parent in $config_file";
210             }
211             }
212             else {
213 53         148 $dir = $dir->parent;
214             }
215             }
216              
217 15         1448 $self->_write_config_file_locations($cfl);
218              
219 15 100       735 if ( -e $self->global_config_file ) {
220 10         934 push( @used_config_files, $self->global_config_file->stringify );
221 10         624 $config = merge( $config,
222             $self->slurp_config( $self->global_config_file ) );
223             }
224 15         1851 $config->{_used_config_files} = \@used_config_files;
225              
226 15         159 return $config;
227             }
228              
229             sub _write_config_file_locations {
230 15     15   50 my ( $self, $cfl ) = @_;
231 15         527 my $fh = $self->home->file('projects.json')->openw;
232 15   33     19748 print $fh $self->json_decoder->encode( $cfl
233             || $self->config_file_locations );
234 15         1476 close $fh;
235             }
236              
237             sub slurp_config {
238 25     25 0 66 my ( $self, $file ) = @_;
239             try {
240 25     25   1812 my $content = $file->slurp();
241 25         6014 return $self->json_decoder->decode($content);
242             }
243             catch {
244 0     0   0 error_message( "Cannot parse config file $file:\n%s", $_ );
245 0         0 exit;
246 25         212 };
247             }
248              
249             sub slurp_projects {
250 15     15 0 38 my $self = shift;
251 15         469 my $file = $self->home->file('projects.json');
252 15 50 33     1238 unless ( -e $file && -s $file ) {
253 0         0 error_message("Cannot find projects.json\n");
254 0         0 exit;
255             }
256 15         1463 my $projects = decode_json( $file->slurp );
257 15         3173 return $projects;
258             }
259              
260             1;
261              
262             __END__
263              
264             =pod
265              
266             =encoding UTF-8
267              
268             =head1 NAME
269              
270             App::TimeTracker::Proto - App::TimeTracker Proto Class
271              
272             =head1 VERSION
273              
274             version 3.008
275              
276             =head1 DESCRIPTION
277              
278             Ugly internal stuff, see L<YAPC::Europe 2011 talk...|https://domm.plix.at/talks/2011_riga_app_timetracker/>
279              
280             =head1 AUTHOR
281              
282             Thomas Klausner <domm@plix.at>
283              
284             =head1 COPYRIGHT AND LICENSE
285              
286             This software is copyright (c) 2011 - 2021 by Thomas Klausner.
287              
288             This is free software; you can redistribute it and/or modify it under
289             the same terms as the Perl 5 programming language system itself.
290              
291             =cut