Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
82.09% covered (warning)
82.09%
55 / 67
72.73% covered (warning)
72.73%
8 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Cli
82.09% covered (warning)
82.09%
55 / 67
72.73% covered (warning)
72.73%
8 / 11
32.50
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 run
80.77% covered (warning)
80.77%
21 / 26
0.00% covered (danger)
0.00%
0 / 1
10.71
 runCss
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 runCssRemap
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
2.09
 runJs
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 runJsMapWeb
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 runJsMapRaw
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 help
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 error
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 output
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExitCode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php declare( strict_types=1 );
2/**
3 * Copyright 2021 Timo Tijhof
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 * @file
18 * @license Apache-2.0
19 */
20
21namespace Wikimedia\Minify;
22
23/**
24 * Implementation of `minify` CLI.
25 *
26 * @internal For use by `bin/minify` only.
27 */
28final class Cli {
29    /** @var int $exitCode */
30    private $exitCode = 0;
31    /** @var resource $in */
32    private $in;
33    /** @var resource $out */
34    private $out;
35    /** @var string $self */
36    private $self;
37    /** @var string $command */
38    private $command;
39    /** @var string[] $params */
40    private $params;
41
42    /**
43     * @param resource $in An open stream for reading input with `fgets()`
44     * @param resource $out An open stream for writing output with `fwrite()`
45     * @param string[] $argv
46     */
47    public function __construct( $in, $out, array $argv ) {
48        $this->in = $in;
49        $this->out = $out;
50        $this->self = basename( $argv[0] ?? '/minify' );
51        $this->command = $argv[1] ?? '';
52        $this->params = array_slice( $argv, 2 );
53    }
54
55    /** Perform the specified command. */
56    public function run(): void {
57        try {
58            switch ( $this->command ) {
59                case 'css':
60                    $this->runCss( ...$this->params );
61                    break;
62                case 'css-remap':
63                    $this->runCssRemap( ...$this->params );
64                    break;
65                case 'js':
66                    $this->runJs( ...$this->params );
67                    break;
68                case 'jsmap-web':
69                    $this->runJsMapWeb( ...$this->params );
70                    break;
71                case 'jsmap-raw':
72                    $this->runJsMapRaw( ...$this->params );
73                    break;
74                case '':
75                case 'help':
76                    $this->exitCode = 1;
77                    $this->help();
78                    break;
79                default:
80                    $this->error( 'Unknown command' );
81                    break;
82            }
83        } catch ( \Throwable $e ) {
84            $this->exitCode = 1;
85            $this->output( (string)$e );
86        }
87    }
88
89    private function runCss( ?string $file = null ): void {
90        $data = $file === null ? stream_get_contents( $this->in ) : file_get_contents( $file );
91        $this->output( CSSMin::minify( $data ) );
92    }
93
94    private function runCssRemap( ?string $file = null ): void {
95        if ( $file === null ) {
96            $this->error( 'Remapping requires a filepath' );
97            return;
98        }
99        $fulldir = dirname( realpath( $file ) );
100        $data = file_get_contents( $file );
101        $data = CSSMin::remap( $data, $fulldir, $fulldir );
102        $this->output( CSSMin::minify( $data ) );
103    }
104
105    private function runJs( ?string $file = null ): void {
106        $data = $file === null ? stream_get_contents( $this->in ) : file_get_contents( $file );
107        $onError = function ( ParseError $error ) {
108            $this->output( 'ParseError: ' . $error->getMessage() . ' at position ' . $error->getOffset() );
109            $this->exitCode = 1;
110        };
111        $ret = JavaScriptMinifier::minify( $data, $onError );
112        if ( !$this->exitCode ) {
113            $this->output( $ret );
114        }
115    }
116
117    private function runJsMapWeb( ?string $file = null ): void {
118        $data = $file === null ? stream_get_contents( $this->in ) : file_get_contents( $file );
119        $sourceName = $file === null ? 'file.js' : basename( $file );
120        $mapper = JavaScriptMinifier::createSourceMapState();
121        $mapper->addSourceFile( $sourceName, $data, true );
122        $this->output( rtrim( $mapper->getSourceMap(), "\n" ) );
123    }
124
125    private function runJsMapRaw( ?string $file = null ): void {
126        $data = $file === null ? stream_get_contents( $this->in ) : file_get_contents( $file );
127        $sourceName = $file === null ? 'file.js' : basename( $file );
128        $mapper = JavaScriptMinifier::createSourceMapState();
129        $mapper->addSourceFile( $sourceName, $data, true );
130        $this->output( rtrim( $mapper->getRawSourceMap(), "\n" ) );
131    }
132
133    private function help(): void {
134        $this->output( <<<TEXT
135usage: {$this->self} <command>
136
137commands:
138   css [<file>]        Minify input data as CSS code, and write to output.
139                       Reads from stdin by default, or can read from a file.
140   css-remap <file>    Remap and process any "@embed" comments in a CSS file,
141                       and write the minified code to output.
142   js  [<file>]        Minify input data as JavaScript code, write to output.
143                       Reads from stdin by default, or can read from a file.
144   jsmap-raw [<file>]  Minify JavaScript code and write a raw source map to
145                       output. Such a source map should not be delivered over
146                       HTTP due to XSSI concerns.
147   jsmap-web [<file>]  Minify JavaScript code and write a source map with XSSI
148                       prefix.
149TEXT
150        );
151    }
152
153    private function error( string $text ): void {
154        $this->exitCode = 1;
155        $this->output( "\n{$this->self} error: $text\n\n------\n" );
156        $this->help();
157    }
158
159    private function output( string $text ): void {
160        fwrite( $this->out, $text . "\n" );
161    }
162
163    /** @return int */
164    public function getExitCode(): int {
165        return $this->exitCode;
166    }
167}