| 1 |
<?php |
| 2 |
|
| 3 |
namespace tubalmartin\CssMin; |
| 4 |
|
| 5 |
class Command |
| 6 |
{ |
| 7 |
const SUCCESS_EXIT = 0; |
| 8 |
const FAILURE_EXIT = 1; |
| 9 |
|
| 10 |
protected $stats = array(); |
| 11 |
|
| 12 |
public static function main() |
| 13 |
{ |
| 14 |
$command = new self; |
| 15 |
$command->run(); |
| 16 |
} |
| 17 |
|
| 18 |
public function run() |
| 19 |
{ |
| 20 |
$opts = getopt( |
| 21 |
'hi:o:', |
| 22 |
array( |
| 23 |
'help', |
| 24 |
'input:', |
| 25 |
'output:', |
| 26 |
'dry-run', |
| 27 |
'keep-sourcemap', |
| 28 |
'keep-sourcemap-comment', |
| 29 |
'linebreak-position:', |
| 30 |
'memory-limit:', |
| 31 |
'pcre-backtrack-limit:', |
| 32 |
'pcre-recursion-limit:', |
| 33 |
'remove-important-comments' |
| 34 |
) |
| 35 |
); |
| 36 |
|
| 37 |
$help = $this->getOpt(array('h', 'help'), $opts); |
| 38 |
$input = $this->getOpt(array('i', 'input'), $opts); |
| 39 |
$output = $this->getOpt(array('o', 'output'), $opts); |
| 40 |
$dryrun = $this->getOpt('dry-run', $opts); |
| 41 |
$keepSourceMapComment = $this->getOpt(array('keep-sourcemap', 'keep-sourcemap-comment'), $opts); |
| 42 |
$linebreakPosition = $this->getOpt('linebreak-position', $opts); |
| 43 |
$memoryLimit = $this->getOpt('memory-limit', $opts); |
| 44 |
$backtrackLimit = $this->getOpt('pcre-backtrack-limit', $opts); |
| 45 |
$recursionLimit = $this->getOpt('pcre-recursion-limit', $opts); |
| 46 |
$removeImportantComments = $this->getOpt('remove-important-comments', $opts); |
| 47 |
|
| 48 |
if (!is_null($help)) { |
| 49 |
$this->showHelp(); |
| 50 |
die(self::SUCCESS_EXIT); |
| 51 |
} |
| 52 |
|
| 53 |
if (is_null($input)) { |
| 54 |
fwrite(STDERR, '-i <file> argument is missing' . PHP_EOL); |
| 55 |
$this->showHelp(); |
| 56 |
die(self::FAILURE_EXIT); |
| 57 |
} |
| 58 |
|
| 59 |
if (!is_readable($input)) { |
| 60 |
fwrite(STDERR, 'Input file is not readable' . PHP_EOL); |
| 61 |
die(self::FAILURE_EXIT); |
| 62 |
} |
| 63 |
|
| 64 |
$css = file_get_contents($input); |
| 65 |
|
| 66 |
if ($css === false) { |
| 67 |
fwrite(STDERR, 'Input CSS code could not be retrieved from input file' . PHP_EOL); |
| 68 |
die(self::FAILURE_EXIT); |
| 69 |
} |
| 70 |
|
| 71 |
$this->setStat('original-size', strlen($css)); |
| 72 |
|
| 73 |
$cssmin = new Minifier; |
| 74 |
|
| 75 |
if (!is_null($keepSourceMapComment)) { |
| 76 |
$cssmin->keepSourceMapComment(); |
| 77 |
} |
| 78 |
|
| 79 |
if (!is_null($removeImportantComments)) { |
| 80 |
$cssmin->removeImportantComments(); |
| 81 |
} |
| 82 |
|
| 83 |
if (!is_null($linebreakPosition)) { |
| 84 |
$cssmin->setLineBreakPosition($linebreakPosition); |
| 85 |
} |
| 86 |
|
| 87 |
if (!is_null($memoryLimit)) { |
| 88 |
$cssmin->setMemoryLimit($memoryLimit); |
| 89 |
} |
| 90 |
|
| 91 |
if (!is_null($backtrackLimit)) { |
| 92 |
$cssmin->setPcreBacktrackLimit($backtrackLimit); |
| 93 |
} |
| 94 |
|
| 95 |
if (!is_null($recursionLimit)) { |
| 96 |
$cssmin->setPcreRecursionLimit($recursionLimit); |
| 97 |
} |
| 98 |
|
| 99 |
$this->setStat('compression-time-start', microtime(true)); |
| 100 |
|
| 101 |
$css = $cssmin->run($css); |
| 102 |
|
| 103 |
$this->setStat('compression-time-end', microtime(true)); |
| 104 |
$this->setStat('peak-memory-usage', memory_get_peak_usage(true)); |
| 105 |
$this->setStat('compressed-size', strlen($css)); |
| 106 |
|
| 107 |
if (!is_null($dryrun)) { |
| 108 |
$this->showStats(); |
| 109 |
die(self::SUCCESS_EXIT); |
| 110 |
} |
| 111 |
|
| 112 |
if (is_null($output)) { |
| 113 |
fwrite(STDOUT, $css . PHP_EOL); |
| 114 |
$this->showStats(); |
| 115 |
die(self::SUCCESS_EXIT); |
| 116 |
} |
| 117 |
|
| 118 |
if (!is_writable(dirname($output))) { |
| 119 |
fwrite(STDERR, 'Output file is not writable' . PHP_EOL); |
| 120 |
die(self::FAILURE_EXIT); |
| 121 |
} |
| 122 |
|
| 123 |
if (file_put_contents($output, $css) === false) { |
| 124 |
fwrite(STDERR, 'Compressed CSS code could not be saved to output file' . PHP_EOL); |
| 125 |
die(self::FAILURE_EXIT); |
| 126 |
} |
| 127 |
|
| 128 |
$this->showStats(); |
| 129 |
|
| 130 |
die(self::SUCCESS_EXIT); |
| 131 |
} |
| 132 |
|
| 133 |
protected function getOpt($opts, $options) |
| 134 |
{ |
| 135 |
$value = null; |
| 136 |
|
| 137 |
if (is_string($opts)) { |
| 138 |
$opts = array($opts); |
| 139 |
} |
| 140 |
|
| 141 |
foreach ($opts as $opt) { |
| 142 |
if (array_key_exists($opt, $options)) { |
| 143 |
$value = $options[$opt]; |
| 144 |
break; |
| 145 |
} |
| 146 |
} |
| 147 |
|
| 148 |
return $value; |
| 149 |
} |
| 150 |
|
| 151 |
protected function setStat($statName, $statValue) |
| 152 |
{ |
| 153 |
$this->stats[$statName] = $statValue; |
| 154 |
} |
| 155 |
|
| 156 |
protected function formatBytes($size, $precision = 2) |
| 157 |
{ |
| 158 |
$base = log($size, 1024); |
| 159 |
$suffixes = array('B', 'K', 'M', 'G', 'T'); |
| 160 |
return round(pow(1024, $base - floor($base)), $precision) .' '. $suffixes[floor($base)]; |
| 161 |
} |
| 162 |
|
| 163 |
protected function formatMicroSeconds($microSecs, $precision = 2) |
| 164 |
{ |
| 165 |
// ms |
| 166 |
$time = round($microSecs * 1000, $precision); |
| 167 |
|
| 168 |
if ($time >= 60 * 1000) { |
| 169 |
$time = round($time / 60 * 1000, $precision) .' m'; // m |
| 170 |
} elseif ($time >= 1000) { |
| 171 |
$time = round($time / 1000, $precision) .' s'; // s |
| 172 |
} else { |
| 173 |
$time .= ' ms'; |
| 174 |
} |
| 175 |
|
| 176 |
return $time; |
| 177 |
} |
| 178 |
|
| 179 |
protected function showStats() |
| 180 |
{ |
| 181 |
$spaceSavings = round((1 - ($this->stats['compressed-size'] / $this->stats['original-size'])) * 100, 2); |
| 182 |
$compressionRatio = round($this->stats['original-size'] / $this->stats['compressed-size'], 2); |
| 183 |
$compressionTime = $this->formatMicroSeconds( |
| 184 |
$this->stats['compression-time-end'] - $this->stats['compression-time-start'] |
| 185 |
); |
| 186 |
$peakMemoryUsage = $this->formatBytes($this->stats['peak-memory-usage']); |
| 187 |
|
| 188 |
print <<<EOT |
| 189 |
|
| 190 |
------------------------------ |
| 191 |
CSSMIN STATS |
| 192 |
------------------------------ |
| 193 |
Space savings: {$spaceSavings} % |
| 194 |
Compression ratio: {$compressionRatio}:1 |
| 195 |
Compression time: $compressionTime |
| 196 |
Peak memory usage: $peakMemoryUsage |
| 197 |
|
| 198 |
|
| 199 |
EOT; |
| 200 |
} |
| 201 |
|
| 202 |
protected function showHelp() |
| 203 |
{ |
| 204 |
print <<<'EOT' |
| 205 |
Usage: cssmin [options] -i <file> [-o <file>] |
| 206 |
|
| 207 |
-i|--input <file> File containing uncompressed CSS code. |
| 208 |
-o|--output <file> File to use to save compressed CSS code. |
| 209 |
|
| 210 |
Options: |
| 211 |
|
| 212 |
-h|--help Prints this usage information. |
| 213 |
--dry-run Performs a dry run displaying statistics. |
| 214 |
--keep-sourcemap[-comment] Keeps the sourcemap special comment in the output. |
| 215 |
--linebreak-position <pos> Splits long lines after a specific column in the output. |
| 216 |
--memory-limit <limit> Sets the memory limit for this script. |
| 217 |
--pcre-backtrack-limit <limit> Sets the PCRE backtrack limit for this script. |
| 218 |
--pcre-recursion-limit <limit> Sets the PCRE recursion limit for this script. |
| 219 |
--remove-important-comments Removes !important comments from output. |
| 220 |
|
| 221 |
EOT; |
| 222 |
} |
| 223 |
} |
| 224 |
|