File Coverage

blib/lib/App/Dothe/Task.pm
Criterion Covered Total %
statement 44 154 28.5
branch 0 22 0.0
condition 0 6 0.0
subroutine 15 29 51.7
pod 0 11 0.0
total 59 222 26.5


line stmt bran cond sub pod time code
1             package App::Dothe::Task;
2             our $AUTHORITY = 'cpan:YANICK';
3             $App::Dothe::Task::VERSION = '0.0.1';
4 1     1   11 use 5.20.0;
  1         3  
5 1     1   4 use warnings;
  1         2  
  1         19  
6              
7 1     1   13 use Moose;
  1         2  
  1         6  
8              
9 1     1   6472 use Log::Any qw($log);
  1         6769  
  1         5  
10 1     1   2059 use Types::Standard qw/ ArrayRef InstanceOf /;
  1         63699  
  1         8  
11 1     1   906 use Type::Tiny;
  1         2  
  1         22  
12 1     1   488 use List::AllUtils qw/ min pairmap /;
  1         9607  
  1         88  
13 1     1   478 use Ref::Util qw/ is_arrayref is_hashref /;
  1         482  
  1         62  
14 1     1   440 use PerlX::Maybe;
  1         2090  
  1         4  
15 1     1   624 use Text::Template;
  1         3710  
  1         82  
16 1     1   698 use Path::Tiny;
  1         10232  
  1         80  
17 1     1   562 use File::Wildcard;
  1         5645  
  1         79  
18              
19              
20 1         7 use experimental qw/
21             signatures
22             postderef
23 1     1   375 /;
  1         2796  
24              
25             has name => (
26             is => 'ro',
27             required => 1,
28             );
29              
30              
31             has cmds => (
32             is => 'ro',
33             lazy => 1,
34             default => sub { [] },
35             traits => [ 'Array' ],
36             handles => {
37             commands => 'elements',
38             },
39             );
40              
41             has raw_sources => (
42             is => 'ro',
43             init_arg => 'sources',
44             default => sub { [] },
45             );
46              
47             has raw_generates => (
48             is => 'ro',
49             init_arg => 'generates',
50             default => sub { [] },
51             );
52              
53             has sources => (
54             is => 'ro',
55             init_arg => undef,
56             lazy => 1,
57             default => sub($self) {
58             $self->vars->{sources} = $self->expand_files( $self->raw_sources )
59             },
60             );
61              
62             has generates => (
63             is => 'ro',
64             init_arg => undef,
65             lazy => 1,
66             default => sub ($self){
67             $self->vars->{generates} = $self->expand_files( $self->raw_generates )
68             },
69             );
70              
71 0     0 0   sub expand_files($self, $list ) {
  0            
  0            
  0            
72 0 0         $list = [ $list ] unless ref $list;
73              
74             [
75 0           map { File::Wildcard->new( path=> $_ )->all }
76 0           map { s!\*\*!/!gr }
77 0           map { $self->render( $_, $self->vars ) }
  0            
78             @$list ]
79             }
80              
81              
82             has tasks => (
83             is => 'ro',
84             required => 1,
85             );
86              
87              
88              
89 0     0 0   sub latest_source_mod($self) {
  0            
  0            
90 0           return min map { -M "".$_ } $self->sources->@*;
  0            
91             }
92              
93 0     0 0   sub latest_generate_mod($self) {
  0            
  0            
94 0           return min map { -M "".$_ } $self->generates->@*;
  0            
95             }
96              
97 0     0 0   sub is_uptodate ($self) {
  0            
  0            
98 0 0         return 0 if $self->tasks->force;
99              
100 0           my $source = $self->latest_source_mod;
101 0           my $gen = $self->latest_generate_mod;
102              
103 0   0       return ( $gen and $source >= $gen );
104             };
105              
106             has raw_vars => (
107             is => 'ro',
108             isa => 'HashRef',
109             init_arg => 'vars',
110             default => sub {
111             +{}
112             },
113             );
114              
115             has vars => (
116             is => 'ro',
117             lazy => 1,
118             init_arg => undef,
119             isa => 'HashRef',
120             builder => '_build_vars',
121             );
122              
123              
124 0     0 0   sub render($self,$template,$vars) {
  0            
  0            
  0            
  0            
125 0 0         if( is_arrayref $template ) {
126 0           return [ map { $self->render($_,$vars) } @$template ];
  0            
127             }
128              
129 0 0         if( is_hashref $template ) {
130 0     0     return { pairmap { $a => $self->render($b,$vars) } %$template }
  0            
131             }
132              
133 1     1   2622 no warnings 'uninitialized';
  1         3  
  1         799  
134 0           $self->template( $template )->fill_in( HASH => $vars,
135             PACKAGE => 'App::Dothe::Sandbox',
136             );
137             }
138              
139 0     0     sub _build_vars($self) {
  0            
  0            
140 0           my %vars = ( $self->tasks->vars->%*, $self->raw_vars->%* );
141              
142             %vars = (
143             %vars,
144 0     0     pairmap { $a => $self->render( $b, \%vars ) }
145 0           $self->raw_vars->%*
146             );
147              
148 0           return \%vars;
149             }
150              
151             has foreach => (
152             is => 'ro',
153             isa => 'Str',
154             );
155              
156 0     0 0   sub foreach_vars($self) {
  0            
  0            
157 0 0         my $foreach = $self->foreach or return;
158              
159 0           $self->sources;
160 0           $self->generates;
161              
162 0           return map { +{ item => $_ } } $self->vars->{$foreach}->@*;
  0            
163             }
164              
165             has deps => (
166             is => 'ro',
167             isa => 'ArrayRef',
168             default => sub {
169             []
170             },
171             );
172              
173 0     0 0   sub dependency_tree($self, $graph = undef ) {
  0            
  0            
  0            
174 0           require Graph::Directed;
175              
176 0   0       $graph ||= Graph::Directed->new;
177              
178 0 0         return $graph
179             if $graph->get_vertex_attribute( $self->name, 'done' );
180              
181 0           $graph->set_vertex_attribute( $self->name, 'done', 1 );
182              
183 0           for my $dep ( $self->deps->@* ) {
184 0           $graph->add_edge( $dep => $self->name );
185 0           $self->tasks->task($dep)->dependency_tree($graph);
186             }
187              
188 0           return $graph;
189             }
190              
191 0     0 0   sub dependencies($self) {
  0            
  0            
192 0           return grep { $_ ne $self->name } $self->dependency_tree->topological_sort;
  0            
193             }
194              
195 0     0 0   sub run($self) {
  0            
  0            
196 0           my @deps = $self->dependencies;
197              
198 0           $self->tasks->task($_)->run for @deps;
199              
200 0           $log->infof( "running task %s", $self->name );
201              
202 0 0         if ( $self->is_uptodate ) {
203 0           $log->infof( '%s is up-to-date', $self->name );
204 0           return;
205             }
206              
207 0           my $vars = $self->vars;
208 0           $self->sources;
209 0           $self->generates;
210              
211 0 0         if( $self->foreach ) {
212 0           for my $entry ( $self->foreach_vars ) {
213 0           App::Dothe::Task->new(
214             tasks => $self->tasks,
215             name => ( join ' - ', $self->name, values %$entry ),
216             sources => $self->sources,
217             generates => $self->generates,
218             vars => { %$vars, %$entry },
219             cmds => $self->cmds,
220             )->run;
221             }
222 0           return;
223             }
224              
225 0           for my $command ( $self->commands ) {
226 0           $self->run_command( $command, $vars );
227             }
228              
229             }
230              
231 0     0 0   sub run_command($self,$command,$vars) {
  0            
  0            
  0            
  0            
232              
233 0 0         if( !ref $command ) {
234 0           $command = { cmd => $command };
235             }
236              
237 0 0         if( my $subtask = $command->{task} ) {
238 0           my $t = $self->tasks->task($subtask);
239             my $newt = App::Dothe::Task->new(
240             name => $t->name,
241             tasks => $t->tasks,
242             maybe foreach => $t->foreach,
243             sources => $t->raw_sources,
244             generates => $t->raw_generates,
245             vars => {
246             $self->vars->%*,
247             $t->vars->%*,
248 0           eval { $command->{vars}->%* }
  0            
249             },
250             cmds => $t->cmds,
251             );
252 0           $newt->run;
253 0           return;
254             }
255              
256              
257 1     1   8 no warnings 'uninitialized';
  1         2  
  1         153  
258 0           my $processed = $self->template( $command->{cmd} )->fill_in(
259             HASH => $vars,
260             PACKAGE => 'App::Dothe::Sandbox',
261             );
262              
263 0           $log->debug( "vars", $vars );
264 0           $log->infof( "> %s", $processed );
265 0 0         system $processed and die "command failed, aborting\n";
266             }
267              
268 0     0 0   sub template ($self,$source) {
  0            
  0            
  0            
269 0           return Text::Template->new( TYPE => 'STRING', DELIMITERS => [ '{{', '}}' ],
270             SOURCE => $source );
271             }
272              
273             1;
274              
275             __END__
276              
277             =pod
278              
279             =encoding UTF-8
280              
281             =head1 NAME
282              
283             App::Dothe::Task
284              
285             =head1 VERSION
286              
287             version 0.0.1
288              
289             =head1 AUTHOR
290              
291             Yanick Champoux <yanick@babyl.ca>
292              
293             =head1 COPYRIGHT AND LICENSE
294              
295             This software is copyright (c) 2019 by Yanick Champoux.
296              
297             This is free software; you can redistribute it and/or modify it under
298             the same terms as the Perl 5 programming language system itself.
299              
300             =cut