GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 9b6a28...3985d3 )
by Brad
01:56
created

BuildAsset::cachebust()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php declare(strict_types=1);
2
////////////////////////////////////////////////////////////////////////////////
3
// __________ __             ________                   __________
4
// \______   \  |__ ______  /  _____/  ____ _____ ______\______   \ _______  ___
5
//  |     ___/  |  \\____ \/   \  ____/ __ \\__  \\_  __ \    |  _//  _ \  \/  /
6
//  |    |   |   Y  \  |_> >    \_\  \  ___/ / __ \|  | \/    |   (  <_> >    <
7
//  |____|   |___|  /   __/ \______  /\___  >____  /__|  |______  /\____/__/\_ \
8
//                \/|__|           \/     \/     \/             \/            \/
9
// -----------------------------------------------------------------------------
10
//          Designed and Developed by Brad Jones <brad @="bjc.id.au" />
11
// -----------------------------------------------------------------------------
12
////////////////////////////////////////////////////////////////////////////////
13
14
namespace Gears\Asset\Tasks;
15
16
use Robo;
17
use SplFileInfo;
18
use RuntimeException;
19
use Gears\Asset\Contracts\Compiler;
20
use Symfony\Component\Finder\Finder;
21
22
class BuildAsset extends Robo\Task\BaseTask implements Robo\Contract\BuilderAwareInterface
23
{
24
    use Robo\Common\BuilderAwareTrait;
25
26
    /**
27
     * This should be set the final location of the asset.
28
     * Eg: ```./assets/script.min.js```
29
     *
30
     * @var SplFileInfo
31
     */
32
    protected $destination;
33
34
    /**
35
     * An array of source file / folder paths, that will be
36
     * concatenated together to build the final asset.
37
     *
38
     * @var string[]
39
     */
40
    protected $source;
41
42
    /**
43
     * If this is set to true, we will not minify the asset.
44
     * Thus allowing for easier debugging during in development.
45
     *
46
     * **Defaults to:** ```false```
47
     *
48
     * @var bool
49
     */
50
    protected $debug = false;
51
52
    /**
53
     * If set to true and debug is also set to false we will rename the final
54
     * asset to include an md5 hash of it's contents. You may also use this in
55
     * conjuction with the template option.
56
     *
57
     * @var bool
58
     */
59
    protected $cachebust = false;
60
61
    /**
62
     * If cachebust is set to true, the assets will be renamed.
63
     * Files in this array will be opened and searched for the old filenames,
64
     * replacing with the new cache busted filenames.
65
     *
66
     * @var string[]
67
     */
68
    protected $template = [];
69
70
    /**
71
     * If set to true we will also output a gzipped version of the asset so that
72
     * you can setup your webserver to serve the pre gzipped version of the
73
     * asset instead of doing it on the fly.
74
     *
75
     * **Defaults to:** ```false```
76
     *
77
     * @var bool
78
     */
79
    protected $gz = false;
80
81
    /**
82
     * Only applies when building css assets. If set to true the default we will
83
     * automatically prefix the built css using ```vladkens/autoprefixer-php```.
84
     *
85
     * If set to false we will not do any prefixing.
86
     *
87
     * If set to a string or an array, we will perform prefixing and pass the
88
     * value through to the setBrowsers method of the Autoprefixer class,
89
     * allowing you to easily configure the prefixer.
90
     *
91
     * **Defaults to:** ```false```
92
     *
93
     * @var bool
94
     */
95
    protected $autoprefix = false;
96
97
    /**
98
     * BuildAssetTask Constructor.
99
     *
100
     * @param string $destination  The path to where we will save the built asset.
101
     */
102
    public function __construct(string $destination)
103
    {
104
        $this->destination = new SplFileInfo($destination);
105
    }
106
107
    /**
108
     * Set the source(s) of the asset.
109
     *
110
     * @param  string|string[]|Finder $value Can be a single path to a folder or
111
     *                                       file, an array of files of folders,
112
     *                                       or a Finder instance.
113
     *
114
     * @return BuildAsset                    Returns our self for method chaining.
115
     */
116
    public function source($value): self
117
    {
118
        $this->source = $this->normaliseSrcInput($value); return $this;
119
    }
120
121
    /**
122
     * Debug setter.
123
     *
124
     * @param  bool $value
125
     * @return self
126
     */
127
    public function debug(bool $value): self
128
    {
129
        $this->debug = $value; return $this;
130
    }
131
132
    /**
133
     * Template setter.
134
     *
135
     * @param  string|string[]|Finder $value Can be a single path to a folder or
136
     *                                       file, an array of files of folders,
137
     *                                       or a Finder instance.
138
     *
139
     * @return self
140
     */
141
    public function template($value): self
142
    {
143
        $this->template = $this->normaliseSrcInput($value); return $this;
144
    }
145
146
    /**
147
     * Gz Setter.
148
     *
149
     * @param  bool $value
150
     * @return self
151
     */
152
    public function gz(bool $value): self
153
    {
154
        $this->gz = $value; return $this;
155
    }
156
157
    /**
158
     * Autoprefix Setter.
159
     *
160
     * @param  bool $value
161
     * @return self
162
     */
163
    public function autoprefix(bool $value): self
164
    {
165
        $this->autoprefix = $value; return $this;
166
    }
167
168
    /**
169
     * Cachebust Setter.
170
     *
171
     * @param  bool $value
172
     * @return self
173
     */
174
    public function cachebust(bool $value): self
175
    {
176
        $this->cachebust = $value; return $this;
177
    }
178
179
    /**
180
     * The main run method.
181
     *
182
     * ```php
183
     * $this->taskBuildAsset('/path/to/asset.js')
184
     * 		->source
185
     * 		([
186
     * 			'/path/to/asset1.js',
187
     * 			'/path/to/asset2.js',
188
     * 			'/path/to/asset3.js',
189
     * 			'/path/to/assetetc.js'
190
     * 		])
191
     * ->run();
192
     * ```
193
     * @return Robo\Result
194
     */
195
    public function run(): Robo\Result
196
    {
197
        // Touch the destination so that "realpath" works.
198
        $result = $this->collectionBuilder()->taskFilesystemStack()
199
            ->mkdir($this->destination->getPath())
200
            ->touch($this->destination->getPathname())
201
        ->run()->wasSuccessful();
202
203
        // Plus this should error out early if we can't write to the file
204
        if (!$result)
205
        {
206
            throw new RuntimeException
207
            (
208
                'We can not write to the destination file: '.
209
                $this->destination->getPathname()
210
            );
211
        }
212
213
        // Initialise the asset, this is what we will eventually
214
        // write to the file-system at the end of this method.
215
        $asset_contents = '';
216
217
        // Loop through the source files
218
        foreach ($this->source as $file)
219
        {
220
            // Tell the world what we are doing
221
            $this->printTaskInfo('Compiling - <info>'.$file.'</info>');
222
223
            // Run the compiler for each file
224
            $asset_contents .= $this->getCompiler(new SplFileInfo($file))->compile();
225
        }
226
227
        // If a template file has been set lets update it
228
        if ($this->cachebust === true)
229
        {
230
            $this->bustCacheBalls($asset_contents);
231
        }
232
233
        // Now write the asset
234
        $this->writeAsset($asset_contents);
235
236
        // If we get to here assume everything worked
237
        return \Robo\Result::success($this);
238
    }
239
240
    /**
241
     * Creates a new compiler based on the file extension type.
242
     *
243
     * @param  SplFileInfo $file
244
     * @return Compiler
245
     */
246
    protected function getCompiler(SplFileInfo $file): Compiler
247
    {
248
        // Grab the source type
249
        $source_type = $this->getSourceType($file);
250
251
        // Which compiler will we use?
252
        $compiler_type = '\Gears\Asset\Compilers\\';
253
        $compiler_type .= ucfirst($source_type);
254
255
        // Does the compiler exist
256
        if (!class_exists($compiler_type))
257
        {
258
            throw new RuntimeException
259
            (
260
                'The source file type is not supported! - ('.$file.')'
261
            );
262
        }
263
264
        // Return the compiler
265
        return new $compiler_type
266
        (
267
            $file,
268
            $this->destination,
269
            $this->debug,
270
            $this->autoprefix
271
        );
272
    }
273
274
    /**
275
     * Determins the type of source we are dealing file.
276
     *
277
     * Normally this is as simple as looking at the file extension,
278
     * however a folder doesn't have one of those so we mimic it here.
279
     *
280
     * @param  SplFileInfo $file
281
     * @return string
282
     */
283
    protected function getSourceType(SplFileInfo $file): string
284
    {
285
        if ($file->isDir()) return 'folder';
286
        return $file->getExtension();
287
    }
288
289
    /**
290
     * So that we can bust the client cache in browser, we will rename the
291
     * asset filename, using a timestamp. But we also need to update the
292
     * HTML that includes the asset into the web page.
293
     * This method does all that for us.
294
     *
295
     * @param string $asset_contents
296
     *
297
     * @return void
298
     */
299
    protected function bustCacheBalls(string $asset_contents)
300
    {
301
        // Get some details about the asset
302
        $asset_ext = $this->destination->getExtension();
303
        $asset_name = $this->destination->getBasename('.'.$asset_ext);
304
        $asset_name_quoted = preg_quote($asset_name, '/');
305
306
        // Create our regular expression
307
        $search_for =
308
        '/'.
309
            $asset_name_quoted.'\..*?\.'.$asset_ext.'|'.
310
            $asset_name_quoted.'\..*?\.min\.'.$asset_ext.'|'.
311
            $asset_name_quoted.'\.min\.'.$asset_ext.'|'.
312
            $asset_name_quoted.'\.'.$asset_ext.
313
        '/';
314
315
        // This is the new asset name
316
        $replace_with = $asset_name.'.'.md5($asset_contents).'.'.$asset_ext;
317
318
        foreach ($this->template as $templateFile)
319
        {
320
            // Tell the world what we are doing
321
            $this->printTaskInfo('Updating template file - <info>'.$templateFile.'</info>');
322
323
            // Run the search and replace
324
            $this->collectionBuilder()
325
                ->taskReplaceInFile($templateFile)
326
                ->regex($search_for)
327
                ->to($replace_with)
328
            ->run();
329
        }
330
331
        // Grab the asset base dir
332
        $asset_base_dir = $this->destination->getPath();
333
334
        // Update the final asset filename to match
335
        $this->destination = new SplFileInfo($asset_base_dir.'/'.$replace_with);
336
337
        // Delete any old assets
338
        $files_to_delete = new Finder();
339
        $files_to_delete->files();
340
        $files_to_delete->name($asset_name.'.'.$asset_ext);
341
        $files_to_delete->name($asset_name.'.*.'.$asset_ext);
342
        $files_to_delete->name($asset_name.'.*.'.$asset_ext.'.gz');
343
        $files_to_delete->in($asset_base_dir);
344
        $files_to_delete->depth('== 0');
345
        foreach ($files_to_delete as $file_to_delete)
346
        {
347
            unlink($file_to_delete->getPathname());
348
        }
349
    }
350
351
    /**
352
     * The business end, finally lets actually save the
353
     * compiled / minified asset.
354
     *
355
     * @param string $asset_contents
356
     */
357
    protected function writeAsset(string $asset_contents)
358
    {
359
        // Tell the world what we are doing
360
        $this->printTaskInfo('Writing to final asset - <info>'.$this->destination->getPathname().'</info>');
361
362
        // Write the normal asset
363
        if (file_put_contents($this->destination->getPathname(), $asset_contents) === false)
364
        {
365
            throw new RuntimeException
366
            (
367
                'Failed to write asset: '.$this->destination->getPathname()
368
            );
369
        }
370
371
        // Create a gzipped version of the asset
372
        if ($this->debug === false && $this->gz === true)
373
        {
374
            $gz_file_name = $this->destination->getPathname().'.gz';
375
376
            $gz_contents = gzencode($asset_contents);
377
378
            // Tell the world what we are doing
379
            $this->printTaskInfo('Writing gzipped version of final asset - <info>'.$gz_file_name.'</info>');
380
381
            if (file_put_contents($gz_file_name, $gz_contents) === false)
382
            {
383
                throw new RuntimeException
384
                (
385
                    'Failed to write gzipped version of asset: '.
386
                    $gz_file_name
387
                );
388
            }
389
        }
390
    }
391
392
    /**
393
     * Helper method to convert several possible inputs
394
     * to a simple array of file paths.
395
     *
396
     * @param  string|string[]|Finder $input Can be a single path to a folder or
397
     *                                       file, an array of files of folders,
398
     *                                       or a Finder instance.
399
     *
400
     * @return string[]
401
     */
402
    protected function normaliseSrcInput($input): array
403
    {
404
        $output = [];
405
406
        if ($input instanceof Finder)
407
        {
408
            foreach ($input as $fileInfo)
409
            {
410
                $output[] = $fileInfo->getRealpath();
411
            }
412
        }
413
        else
414
        {
415
            if (!is_array($input)) $input = [$input];
416
417
            if (count($input) === 0) throw new \UnexpectedValueException;
418
419
            if (!is_string($input[0])) throw new \UnexpectedValueException;
420
421
            $output = $input;
422
        }
423
424
        return $output;
425
    }
426
}
427