File Coverage

blib/lib/Mojolicious/Command/bundle.pm
Criterion Covered Total %
statement 36 90 40.0
branch 6 36 16.6
condition 2 13 15.3
subroutine 9 21 42.8
pod 1 1 100.0
total 54 161 33.5


line stmt bran cond sub pod time code
1             package Mojolicious::Command::bundle;
2              
3             =head1 NAME
4              
5             Mojolicious::Command::bundle - Bundle assets from other projects
6              
7             =head1 VERSION
8              
9             0.01
10              
11             =head1 DESCRIPTION
12              
13             L is a command for fetching online
14             assets and bundle them with your project.
15              
16             Bundling are done with C, where the remote L
17             is added to the current git project. The files are then copied into a
18             C directory. Updating the files to the
19             latest remote version is as easy as running the same command again, or
20             optionally with a different L.
21              
22             The git process is I accomplised using submodules, nor subtree. The
23             reason is that it seems a lot more flexible being able to jump between
24             branches and tags as you like.
25              
26             Please L
27             and issue if you have defined your own L repository.
28              
29             =head1 SYNOPSIS
30              
31             Usage:
32              
33             $ mojo bundle
34             $ mojo bundle materialize
35             $ mojo bundle materialize v0.97.1
36              
37             =head1 REPOSITORIES
38              
39             =head2 bootstrap
40              
41             Bundle the L project.
42              
43             =head2 materializecss
44              
45             Bundle the L project.
46              
47             See L for more details.
48              
49             =head2 custom
50              
51             This is not a real repository, but it is possible to specify locations with
52             a config file in the current working directory:
53              
54             $ cat - > .mojo_bundle.json
55             {
56             "materialize": {
57             "download_url": "https://github.com/Dogfalo/materialize/archive/$version.tar.gz",
58             "git_url": "https://github.com/Dogfalo/materialize.git"
59             }
60             }
61              
62             The content of this file will be merged with the default L.
63              
64             "download_url" is only required as a fallback, in case
65             L is not installed.
66              
67             =head1 ENVIRONMENT VARIABLES
68              
69             =head2 GIT_BIN
70              
71             Path to your "git" executable. The default is to use L to
72             find the executable.
73              
74             =head2 MOJO_ASSET_OUT_DIR
75              
76             Path to where the root of the repositories should be. Defaults to "assets/vendor".
77              
78             =cut
79              
80 1     1   12548 use Mojo::Base 'Mojolicious::Command';
  1         1  
  1         6  
81 1     1   129398 use Mojo::Util qw( spurt slurp );
  1         2  
  1         51  
82 1     1   900 use Mojo::JSON 'decode_json';
  1         17697  
  1         66  
83 1     1   1355 use File::Temp ();
  1         13784  
  1         26  
84 1     1   693 use File::Which ();
  1         897  
  1         27  
85 1     1   6 use File::Spec::Functions qw( catdir catfile );
  1         2  
  1         76  
86              
87 1   50 1   5 use constant GIT_BIN => $ENV{GIT_BIN} // File::Which::which('git') || '';
  1         2  
  1         9  
88 1   33 1   349 use constant OUT_DIR => $ENV{MOJO_ASSET_OUT_DIR} || catdir 'assets', 'vendor';
  1         2  
  1         1628  
89              
90             our $VERSION = '0.01';
91              
92             =head1 ATTRIBUTES
93              
94             =head2 description
95              
96             $str = $self->description;
97              
98             Returns short description of this command.
99              
100             =head2 repositories
101              
102             $hash_ref = $self->repositories;
103              
104             Holds a mapping between repository name and resource URLs.
105             See L for default value.
106              
107             =head2 usage
108              
109             $str = $self->usage;
110              
111             Returns how to use this command.
112              
113             =cut
114              
115             has description => 'Command for making remote assets available locally.';
116              
117             has repositories => sub {
118             my $self = shift;
119             my $repositories = $self->_default_repositories;
120              
121             if (-e '.mojo_bundle') {
122             my $extra = decode_json slurp '.mojo_bundle';
123             @$repositories{keys %$extra} = values %$extra;
124             }
125              
126             return $repositories;
127             };
128              
129             has usage => sub {
130             my $self = shift;
131             my $usage = "Usage:\n\n";
132              
133             for my $repo (sort keys %{$self->repositories}) {
134             next unless $repo =~ /^[a-z]/;
135             $usage .= " \$ mojo bundle $repo \n";
136             }
137              
138             return "$usage\n";
139             };
140              
141             =head1 METHODS
142              
143             =head2 run
144              
145             Command start point.
146              
147             =cut
148              
149             sub run {
150 0     0 1 0 my $self = shift;
151 0         0 my $method = GIT_BIN ? '_git_fetch' : '_download';
152              
153 0         0 return $self->$method(@_);
154             }
155              
156             sub _default_repositories {
157             return {
158 0     0   0 bootstrap => {
159             git_url => 'https://github.com/twbs/bootstrap.git',
160             download_url => 'https://github.com/twbs/bootstrap/archive/$version.zip',
161             },
162             materialize => {
163             git_url => 'https://github.com/Dogfalo/materialize.git',
164             download_url => 'https://github.com/Dogfalo/materialize/archive/$version.tar.gz',
165             }
166             };
167             }
168              
169             sub _download {
170 0     0   0 my $self = shift;
171 0   0     0 my $repo = shift || '';
172 0   0     0 my $version = shift || 'master';
173              
174 0         0 die 'TODO: Fetch .tar.gz';
175             }
176              
177             sub _git {
178 1 50   1   41 my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
179 1 50       8 my $comment = $_[-1] =~ /^\#\s*(.*)/ ? pop : undef;
180 1         3 my ($self, @cmd) = @_;
181 1         2 my $exit;
182              
183 1 50       4 if (defined $comment) {
    0          
184 1 50       50 warn "$comment\n" if $comment =~ /\w/;
185             }
186             elsif (!$self->quiet) {
187 0         0 warn "+ git @cmd\n";
188             }
189              
190 1 50       4 if ($cb) {
191 1 50       15060 my $pid = open my $GIT, '-|', GIT_BIN, @cmd or die "git @cmd: $!";
192 1         50631 while (<$GIT>) {
193 0         0 chomp;
194 0         0 $cb->();
195             }
196 1         65 waitpid $pid, 0;
197 1         78 $exit = $? >> 8;
198             }
199             else {
200 0         0 system GIT_BIN, @cmd;
201 0         0 $exit = $? >> 8;
202 0 0       0 die "! git @cmd: \$?=$exit\n" if $exit;
203             }
204              
205 1         56 return $exit;
206             }
207              
208             sub _git_fetch {
209 0     0     my $self = shift;
210 0   0       my $repo = shift || '';
211 0   0       my $version = shift || 'master';
212 0 0         my $url = $self->repositories->{$repo}{git_url} or die $self->usage;
213 0           my $latest_file = $self->_latest_file($repo);
214 0           my $object = "$repo/$version";
215 0           my $vendor_dir = catfile OUT_DIR, $repo;
216 0           my ($exists, $latest_commit);
217              
218 0 0   0     $self->_git(qw( remote -v ), '#', sub { /^$repo\b/ and $exists = 1 });
  0            
219 0 0         $self->_git(qw( remote add ), $repo => $url) unless $exists;
220 0           $self->_git(qw( remote update ), $repo);
221 0 0   0     $self->_git(qw( tag ), '#', sub { $object = $_ if $_ eq $version });
  0            
222              
223 0 0         if (-e $latest_file) {
224 0           $latest_commit = slurp $latest_file;
225 0           chomp $latest_commit;
226 0           open my $GIT_APPLY, '|-', GIT_BIN, 'apply';
227             $self->_git(
228             qw( diff-tree -p --binary ),
229             '--dst-prefix' => "b/$vendor_dir/",
230             '--src-prefix' => "a/$vendor_dir/",
231 0     0     $latest_commit, $object, sub { print $GIT_APPLY "$_\n" },
232 0           );
233 0 0         close $GIT_APPLY or die "git apply: $!";
234 0           $self->_git(add => $vendor_dir);
235             }
236             else {
237 0 0         File::Path::make_path($vendor_dir) unless -d $vendor_dir;
238 0           $self->_git(qw( read-tree -u ), "--prefix=$vendor_dir", $object);
239             }
240              
241 0 0   0     unless ($self->_git(qw( diff --cached --quiet ), '# Checking for changes', sub { })) {
242 0           return warn "Updated $repo/$version, but no changes was found.\n";
243             }
244              
245 0           my $tmp = File::Temp->new;
246 0 0         my $msg = sprintf "%s %s from %s %s\n", $latest_commit ? "Updated" : "Created", $vendor_dir, $url, $version;
247 0           print $tmp "$msg\n";
248              
249 0 0         if ($latest_commit) {
250 0     0     $self->_git(qw( log --no-color --oneline ), "$latest_commit..$object", '#', sub { print $tmp " $_\n" });
  0            
251             }
252              
253 0           close $tmp; # flush content
254 0     0     $self->_git(qw( log --format=%H -n1 ), $object, '#', sub { spurt $_ => $latest_file });
  0            
255 0           $self->_git(qw( add ), $latest_file);
256 0     0     $self->_git(qw( commit -F ), $tmp, sub { });
257 0           warn "# $msg";
258 0           return 0;
259             }
260              
261 0     0     sub _latest_file { catfile OUT_DIR, ".$_[1].latest"; }
262              
263             =head1 COPYRIGHT AND LICENSE
264              
265             Copyright (C) 2014, Jan Henning Thorsen
266              
267             This program is free software, you can redistribute it and/or modify it under
268             the terms of the Artistic License version 2.0.
269              
270             =head1 AUTHOR
271              
272             Jan Henning Thorsen - C
273              
274             =cut
275              
276             1;