File Coverage

blib/lib/Mojolicious/Plugin/AssetPack/Pipe.pm
Criterion Covered Total %
statement 30 86 34.8
branch 1 28 3.5
condition 0 3 0.0
subroutine 12 19 63.1
pod 3 3 100.0
total 46 139 33.0


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::AssetPack::Pipe;
2 12     12   3403 use Mojo::Base -base;
  12         25  
  12         62  
3              
4 12     12   1638 use File::Temp ();
  12         24  
  12         152  
5 12     12   3461 use IPC::Run3 ();
  12         30254  
  12         263  
6 12     12   78 use List::Util 'first';
  12         23  
  12         756  
7 12     12   68 use Mojo::File 'path';
  12         24  
  12         462  
8 12     12   65 use Mojo::JSON;
  12         23  
  12         420  
9 12     12   366 use Mojolicious::Plugin::AssetPack::Asset;
  12         25  
  12         216  
10 12     12   354 use Mojolicious::Plugin::AssetPack::Util qw(diag has_ro DEBUG);
  12         30  
  12         11997  
11              
12             my $REQUIRE_JS = path(__FILE__)->dirname->child(qw(Pipe require.js))->realpath;
13              
14             $ENV{PATH} ||= '';
15              
16             has topic => '';
17             has_ro 'assetpack';
18              
19 0     0 1 0 sub app { shift->assetpack->ua->server->app }
20              
21             sub run {
22 0     0 1 0 my ($self, $cmd, @args) = @_;
23 0         0 my $name = path($cmd->[0])->basename;
24 0         0 local $cmd->[0] = $self->_find_app($name, $cmd->[0]);
25 0 0       0 die qq(@{[ref $self]} was unable to locate the "$name" application.) unless $cmd->[0];
  0         0  
26 0         0 diag '$ %s', join ' ', @$cmd if DEBUG > 1;
27 0 0       0 eval { IPC::Run3::run3($cmd, @args) } or do {
  0         0  
28 0 0       0 my $exit = $? > 0 ? $? >> 8 : $?;
29 0         0 my $bang = int $!;
30 0         0 die "run($cmd->[0]) failed: $@ (\$?=$exit, \$!=$bang, PATH=$ENV{PATH})";
31             };
32             }
33              
34 0     0 1 0 sub process { Carp::confess('Method "process" not implemented by subclass') }
35              
36             sub _find_app {
37 0     0   0 my ($self, $apps, $path) = @_;
38 0 0 0     0 return $path if $path and path($path)->is_abs;
39              
40 0 0       0 $apps = [$apps] unless ref $apps eq 'ARRAY';
41 0         0 for my $name (@$apps) {
42 0 0       0 return $self->{apps}{$name} if $self->{apps}{$name}; # Already found
43 0         0 my $key = uc "MOJO_ASSETPACK_${name}_APP";
44 0         0 diag 'Looking for "%s" in $%s', $name, $key if DEBUG > 1;
45 0 0       0 return $ENV{$key} if $ENV{$key}; # MOJO_ASSETPACK_FOO_APP wins
46              
47 0         0 diag 'Looking for "%s" in $PATH.', $name if DEBUG > 1;
48 0     0   0 $path = first {-e} map { path($_, $name) } File::Spec->path;
  0         0  
  0         0  
49 0 0       0 return $self->{apps}{$name} = $path if $path; # Found in $PATH
50             }
51              
52 0         0 my $code = $self->can(lc sprintf '_install_%s', $apps->[-1]);
53 0         0 diag 'Calling %s->_install_%s() ...', ref $self, $apps->[-1] if DEBUG > 1;
54 0 0       0 return $self->{apps}{$apps->[-1]} = $self->$code if $code;
55 0         0 return '';
56             }
57              
58             sub _install_node_modules {
59 0     0   0 my $self = shift;
60              
61 0         0 $self->run([$self->_find_app([qw(nodejs node)]), $REQUIRE_JS, @_], \undef, \my $status);
62 0         0 $status = Mojo::JSON::decode_json($status);
63              
64 0         0 for my $plugin ('rollup', @{$self->modules}, @{$self->plugins}) {
  0         0  
  0         0  
65 0 0       0 next unless $status->{$plugin};
66 0         0 $self->app->log->warn("Installing $plugin... Please wait. (npm install $plugin)");
67 0         0 $self->run([npm => install => $plugin]);
68             }
69             }
70              
71 1     1   600 sub _install_gem { shift->_i('https://rubygems.org/pages/download') }
72 1     1   643 sub _install_node { shift->_i('https://nodejs.org/en/download') }
73 1     1   637 sub _install_ruby { shift->_i('https://ruby-lang.org/en/documentation/installation') }
74 3 50   3   7 sub _i { die "@{[ref $_[0]]} requires @{[$_[1]=~/\/(\w+)/?$1:1]}. $_[1]\n" }
  3         12  
  3         37  
75              
76             sub _run_app {
77 0     0     my ($self, $asset) = @_;
78 0           my $output = '';
79 0           my ($tmp, @args);
80              
81 0           for my $arg (@{$self->app_args}) {
  0            
82 0 0         if ($arg eq '$input') {
83 0           $tmp = File::Temp->new;
84 0           unshift @args, $tmp;
85 0           push @args, "$tmp";
86 0 0         defined $tmp->syswrite($asset->content) or die "Can't write to file $tmp: $!";
87 0           close $tmp;
88             }
89             else {
90 0           push @args, $arg;
91             }
92             }
93              
94 0 0         if ($tmp) {
95 0           $self->run([$self->app, @args]);
96 0           $output = path($tmp)->slurp;
97             }
98             else {
99 0           $self->run([$self->app, @args], \$asset->content, \$output);
100             }
101              
102 0           return \$output;
103             }
104              
105             1;
106              
107             =encoding utf8
108              
109             =head1 NAME
110              
111             Mojolicious::Plugin::AssetPack::Pipe - Base class for a pipe
112              
113             =head1 SYNOPSIS
114              
115             =head2 Write a custom pipe
116              
117             package MyApp::MyCoolPipe;
118             use Mojo::Base "Mojolicious::Plugin::AssetPack::Pipe";
119             use Mojolicious::Plugin::AssetPack::Util qw(diag DEBUG);
120              
121             sub process {
122             my ($self, $assets) = @_;
123              
124             # Normally a Mojolicious::Plugin::AssetPack::Store object
125             my $store = $self->assetpack->store;
126              
127             # Loop over Mojolicious::Plugin::AssetPack::Asset objects
128             $assets->each(
129             sub {
130             my ($asset, $index) = @_;
131              
132             # Skip every file that is not css
133             return if $asset->format ne "css";
134              
135             # Change $attr if this pipe will modify $asset attributes
136             my $attr = $asset->TO_JSON;
137             my $content = $asset->content;
138              
139             # Private name to load/save meta data under
140             $attr->{key} = "coolpipe";
141              
142             # Return asset if already processed
143             if ($content !~ /white/ and $file = $store->load($attr)) {
144             return $asset->content($file);
145             }
146              
147             # Process asset content
148             diag q(Replace white with red in "%s".), $asset->url if DEBUG;
149             $content =~ s!white!red!g;
150             $asset->content($store->save(\$content, $attr))->minified(1);
151             }
152             );
153             }
154              
155             =head2 Use the custom pipe
156              
157             use Mojolicious::Lite;
158             plugin AssetPack => {pipes => [qw(MyApp::MyCoolPipe Css)]};
159              
160             Note that the above will not load the other default pipes, such as
161             L.
162              
163             =head1 DESCRIPTION
164              
165             This is the base class for all pipe classes.
166              
167             =head1 ATTRIBUTES
168              
169             =head2 assetpack
170              
171             $obj = $self->assetpack;
172              
173             Holds a L object.
174              
175             =head2 topic
176              
177             $str = $self->topic;
178             $self = $self->topic("app.css");
179              
180             Returns the name of the current asset topic.
181              
182             =head1 METHODS
183              
184             =head2 after_process
185              
186             $self->after_process(Mojo::Collection->new);
187              
188             L will call this method before
189             any of the pipe L method is called.
190              
191             Note that this method is not defined in L!
192              
193             =head2 app
194              
195             $obh = $self->app;
196              
197             Returns the L application object.
198              
199             =head2 before_process
200              
201             $self->before_process(Mojo::Collection->new);
202              
203             L will call this method after all of
204             the pipes L method is called.
205              
206             Note that this method is not defined in L!
207              
208             =head2 process
209              
210             $self->process(Mojo::Collection->new);
211              
212             A method used to process the assets.
213             Each of the element in the collection will be a
214             L object or an object with the same
215             API.
216              
217             This method need to be defined in the subclass.
218              
219             =head2 run
220              
221             $self->run([som_app => @args], \$stdin, \$stdout, ...);
222              
223             See L for details about the arguments. This method will try to
224             call C<_install_some_app()> unless "som_app" was found in
225             L. This method could then try to install the application
226             and must return the path to the installed application.
227              
228             =head1 SEE ALSO
229              
230             =over 2
231              
232             =item * L
233              
234             =item * L
235              
236             =item * L
237              
238             =item * L
239              
240             =back
241              
242             =cut