1#!/usr/bin/env php 2<?php 3/** 4 * CSSTidy - Command Line Interface (CLI) 5 * 6 * This file is part of CSSTidy. 7 * 8 * CSSTidy is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU Lesser General Public License as published by 10 * the Free Software Foundation; either version 2.1 of the License, or 11 * (at your option) any later version. 12 * 13 * CSSTidy is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU Lesser General Public License for more details. 17 * 18 * You should have received a copy of the GNU Lesser General Public License 19 * along with this program. If not, see <http://www.gnu.org/licenses/>. 20 * 21 * @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License 22 * @package csstidy 23 * @author Florian Schmitz (floele at gmail dot com) 2005-2007 24 * @author Brett Zamir (brettz9 at yahoo dot com) 2007 25 * @author Nikolay Matsievsky (speed at webo dot name) 2009-2010 26 * @author Christopher Finke (cfinke at gmail.com) 2012 27 * @author Etienne Desautels (etienne dot desautels at gmail dot com) 2012 28 * @author Cedric Morin (cedric at yterium dot com) 2010-2019 29 */ 30 31 32error_reporting(E_ALL); 33 34if (version_compare(PHP_VERSION, '5.4')<0){ 35 die('Requires PHP 5.4 or above'); 36} 37 38/** 39 * Contains the Parser class 40 * 41 * @version 1.6.5 42 */ 43require dirname(__DIR__) . DIRECTORY_SEPARATOR . 'class.csstidy.php'; 44$csstidy = new csstidy(); 45$dumpTree = false; 46$default_media = ''; 47 48/** 49 * Return the usage message to sdterr and exit with return code 1 50 * @access private 51 * @version 1.0 52 */ 53function _show_usage(){ 54 $exe = $GLOBALS['argv'][0]; 55 $version = "Version " . $GLOBALS['csstidy']->version; 56 57 $HELP = <<<EOT 58Usage: $exe [options] [input-file] 59$version 60 61Options include: 62 63-h, --help Show this message 64-t=template Set the output template [default|filename|low|high|highest] 65--case_properties [1|2|0] 66--compress_colors [true|false] 67--compress_font-weight [true|false] 68--default_media Set the default media rule to apply to the CSS 69--css_level [CSS3.0|CSS2.1|CSS2.0|CSS1.0] 70--discard_invalid_properties [false|true] 71--discard_invalid_selectors [false|true] 72--lowercase_s [false|true] 73--merge_selectors [0|1|2] 74--optimise_shorthands [1|2|0] 75--preserve_css [false|true] 76--remove_bslash [true|false] 77--remove_last_semicolon [false|true] 78--reverse_left_and_right [false|true] 79--sort_properties [false|true] 80--sort_selectors [false|true] 81--timestamp [false|true] 82 83-T Dump formatted parse tree 84-v, --version Print the version 85 86EOT; 87 exit($HELP); 88} 89 90 91/** 92 * Parse argument 93 * 94 * @param integer $i 95 * @param array $options 96 * 97 * @return string|null 98 */ 99function parseArgument(&$i, $options){ 100 global $argc; 101 global $argv; 102 103 if (!preg_match('/^(?:' . implode('|', (array)$options) . ')=?(.*)/', $argv[$i], $matches)){ 104 return; 105 } 106 107 if (strlen($matches[1])){ 108 return $matches[1]; 109 } 110 111 if ($i+1<$argc){ 112 $i++; 113 114 return $argv[$i]; 115 } 116} 117 118/** 119 * Cast Arguments relative to expected format 120 * @param mixed $value 121 * @param string $format 122 * @param array $expected 123 * @return mixed 124 */ 125function castArgument($value, $format, $expected = null) { 126 if ($expected and !in_array($value, $expected)) { 127 if (in_array('filename', $expected)) { 128 $fileName = castArgument($value, $format); 129 if (!file_exists($fileName)) { 130 _show_usage(); 131 } 132 } 133 else { 134 _show_usage(); 135 } 136 } 137 switch (strtolower($format)) { 138 case "int": 139 $value = intval($value); 140 break; 141 case "str": 142 $value = trim(strval($value)); 143 foreach (["'", '"'] as $q) { 144 if (substr($value, 0, 1) == $q && substr($value, -1, 1) == $q) { 145 $value = substr($value, 1, -1); 146 $value = str_replace("\\" . $q, $q, $value); 147 break; 148 } 149 } 150 break; 151 case "bool": 152 $value = strtolower(trim(strval($value))); 153 if (!in_array($value, [ '0', '1', 'on', 'off', 'true', 'false'])) { 154 _show_usage(); 155 } 156 $value = (in_array(strtolower($value), ['1', 'on', 'true']) ? true : false); 157 break; 158 } 159 return $value; 160} 161 162// Check if there's at least one argument 163if ($argc<1){ 164 _show_usage(); 165} 166 167// Set the settings defined on the CLI 168$settings = [ 169 'case_properties' => ['format' => 'int', 'expected' => [0, 1, 2]], 170 'compress_colors' => ['format' => 'bool'], 171 'compress_font-weight' => ['format' => 'bool'], 172 'css_level' => ['format' => 'str', 'expected' => ['CSS3.0', 'CSS2.1', 'CSS2.0', 'CSS1.0']], 173 'discard_invalid_properties' => ['format' => 'bool'], 174 'discard_invalid_selectors' => ['format' => 'bool'], 175 'merge_selectors' => ['format' => 'int', 'expected' => [0, 1, 2]], 176 'lowercase_s' => ['format' => 'bool'], 177 'optimise_shorthands' => ['format' => 'int', 'expected' => [0, 1, 2]], 178 'preserve_css' => ['format' => 'bool'], 179 'remove_bslash' => ['format' => 'bool'], 180 'remove_last_semicolon' => ['format' => 'bool', 'setting' => 'remove_last_;'], 181 'reverse_left_and_right' => ['format' => 'bool'], 182 'sort_properties' => ['format' => 'bool'], 183 'sort_selectors' => ['format' => 'bool'], 184 'template' => ['format' => 'str', 'short' => 't', 'expected' => ['default', 'filename', 'low', 'high', 'highest']], 185 'timestamp' => ['format' => 'bool'], 186]; 187 188foreach ($settings as $option_name => $desc) { 189 $options_list = [ "--$option_name" ]; 190 if (isset($desc['short'])) { 191 $options_list[] = "-" . $desc['short']; 192 } 193 $settings[$option_name]['options_list'] = $options_list; 194} 195 196 197for ($i = 1; $i<$argc; $i++){ 198 if ($argv[$i]==='-h' || $argv[$i]==='--help'){ 199 _show_usage(); 200 } 201 202 if ($argv[$i]==='-v' || $argv[$i]==='--version'){ 203 exit($csstidy->version . "\n"); 204 } 205 $value = parseArgument($i, ['--default_media']); 206 if (isset($value)) { 207 $value = castArgument($value, 'str'); 208 $default_media = trim($value); 209 if (strpos($default_media, '@') === false) { 210 $default_media = '@media ' . $default_media; 211 } 212 } 213 214 foreach ($settings as $option_name => $desc) { 215 $value = parseArgument($i, $desc['options_list']); 216 if (isset($value)){ 217 if (isset($desc['format'])) { 218 $value = castArgument($value, $desc['format'], isset($desc['expected']) ? $desc['expected'] : null); 219 } 220 $setting_name = $option_name; 221 if (isset($desc['setting'])) { 222 $setting_name = $desc['setting']; 223 } 224 $csstidy->set_cfg($setting_name, $value); 225 continue 2; 226 } 227 } 228 229 if ($argv[$i]==='-T'){ 230 $dumpTree = true; 231 continue; 232 } 233 234 if (file_exists($argv[$i])){ 235 $inputFile = $argv[$i]; 236 continue; 237 } 238} 239 240if (!$inputFile) { 241 $inputFile = 'php://stdin'; 242} 243 244// Get the data 245$css_code = file_get_contents($inputFile); 246 247// Exit on error when reading the data 248if ($css_code===false){ 249 file_put_contents('php://stderr', "The file \"" . $inputFile . "\" does not exist.\n"); 250 exit(1); 251} 252 253// Parse the CSS 254$csstidy->parse($css_code); 255if ($dumpTree){ 256 var_dump($csstidy->css); 257} else { 258 // Optimize and output the CSS file 259 echo $csstidy->print->plain($default_media); 260 echo "\n"; 261}