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