4
4
5
5
namespace CssLint ;
6
6
7
+ use RuntimeException ;
8
+ use Throwable ;
9
+
7
10
/**
8
11
* @phpstan-import-type Errors from \CssLint\Linter
9
12
* @package CssLint
@@ -24,58 +27,21 @@ class Cli
24
27
public function run (array $ arguments ): int
25
28
{
26
29
$ cliArgs = $ this ->parseArguments ($ arguments );
27
- if ($ cliArgs ->filePathOrCssString === null || $ cliArgs ->filePathOrCssString === '' || $ cliArgs ->filePathOrCssString === '0 ' ) {
30
+ if ($ cliArgs ->input === null || $ cliArgs ->input === '' || $ cliArgs ->input === '0 ' ) {
28
31
$ this ->printUsage ();
29
32
return self ::RETURN_CODE_SUCCESS ;
30
33
}
31
34
32
- $ properties = new \CssLint \Properties ();
33
- if ($ cliArgs ->options !== null && $ cliArgs ->options !== '' && $ cliArgs ->options !== '0 ' ) {
34
- $ options = json_decode ($ cliArgs ->options , true );
35
-
36
- if (json_last_error () !== 0 ) {
37
- $ errorMessage = json_last_error_msg ();
38
- $ this ->printError ('Unable to parse option argument: ' . $ errorMessage );
39
- return self ::RETURN_CODE_ERROR ;
40
- }
41
-
42
- if (!$ options ) {
43
- $ this ->printError ('Unable to parse empty option argument ' );
44
- return self ::RETURN_CODE_ERROR ;
45
- }
35
+ try {
36
+ $ properties = $ this ->getPropertiesFromOptions ($ cliArgs ->options );
46
37
47
- if (!is_array ($ options )) {
48
- $ this ->printError ('Unable to parse option argument: must be a json object ' );
49
- return self ::RETURN_CODE_ERROR ;
50
- }
51
-
52
- $ properties ->setOptions ($ options );
53
- }
38
+ $ cssLinter = new Linter ($ properties );
54
39
55
- $ cssLinter = new \CssLint \Linter ($ properties );
56
-
57
- $ filePathOrCssString = $ cliArgs ->filePathOrCssString ;
58
- if (!file_exists ($ filePathOrCssString )) {
59
- return $ this ->lintString ($ cssLinter , $ filePathOrCssString );
60
- }
61
-
62
- $ filePath = $ filePathOrCssString ;
63
- if (!is_readable ($ filePath )) {
64
- $ this ->printError ('File " ' . $ filePath . '" is not readable ' );
40
+ return $ this ->lintInput ($ cssLinter , $ cliArgs ->input );
41
+ } catch (Throwable $ throwable ) {
42
+ $ this ->printError ($ throwable ->getMessage ());
65
43
return self ::RETURN_CODE_ERROR ;
66
44
}
67
-
68
- return $ this ->lintFile ($ cssLinter , $ filePath );
69
- }
70
-
71
- /**
72
- * Retrieve the parsed Cli arguments from given arguments array
73
- * @param string[] $arguments arguments to be parsed (@see $_SERVER['argv'])
74
- * @return \CssLint\CliArgs an instance of Cli arguments object containing parsed arguments
75
- */
76
- private function parseArguments (array $ arguments ): \CssLint \CliArgs
77
- {
78
- return new \CssLint \CliArgs ($ arguments );
79
45
}
80
46
81
47
/**
@@ -86,7 +52,7 @@ private function printUsage(): void
86
52
$ this ->printLine ('Usage: ' . PHP_EOL .
87
53
'------ ' . PHP_EOL .
88
54
PHP_EOL .
89
- ' ' . self ::SCRIPT_NAME . " [--options='{ }'] css_file_or_string_to_lint " . PHP_EOL .
55
+ ' ' . self ::SCRIPT_NAME . " [--options='{ }'] input_to_lint " . PHP_EOL .
90
56
PHP_EOL .
91
57
'Arguments: ' . PHP_EOL .
92
58
'---------- ' . PHP_EOL .
@@ -100,35 +66,143 @@ private function printUsage(): void
100
66
' Example: --options= \'{ "constructors": {"o" : false}, "allowedIndentationChars": ["\t"] } \'' .
101
67
PHP_EOL .
102
68
PHP_EOL .
103
- ' css_file_or_string_to_lint ' . PHP_EOL .
104
- ' The CSS file path (absolute or relative) or a CSS string to be linted ' . PHP_EOL .
69
+ ' input_to_lint ' . PHP_EOL .
70
+ ' The CSS file path (absolute or relative) ' . PHP_EOL .
71
+ ' a glob pattern of file(s) to be linted ' . PHP_EOL .
72
+ ' or a CSS string to be linted ' . PHP_EOL .
105
73
' Example: ' . PHP_EOL .
106
- ' ./path/to/css_file_path_to_lint.css ' . PHP_EOL .
74
+ ' "./path/to/css_file_path_to_lint.css" ' . PHP_EOL .
75
+ ' "./path/to/css_file_path_to_lint/*.css" ' . PHP_EOL .
107
76
' ".test { color: red; }" ' . PHP_EOL .
108
77
PHP_EOL .
109
78
'Examples: ' . PHP_EOL .
110
79
'--------- ' . PHP_EOL .
111
80
PHP_EOL .
112
81
' Lint a CSS file: ' . PHP_EOL .
113
- ' ' . self ::SCRIPT_NAME . ' ./path/to/css_file_path_to_lint.css ' . PHP_EOL . PHP_EOL .
82
+ ' ' . self ::SCRIPT_NAME . ' " ./path/to/css_file_path_to_lint.css" ' . PHP_EOL . PHP_EOL .
114
83
' Lint a CSS string: ' . PHP_EOL .
115
- ' ' . self ::SCRIPT_NAME . ' ".test { color: red; }" ' . PHP_EOL . PHP_EOL .
84
+ ' ' . self ::SCRIPT_NAME . ' ".test { color: red; }" ' . PHP_EOL . PHP_EOL .
116
85
' Lint with only tabulation as indentation: ' . PHP_EOL .
117
86
' ' . self ::SCRIPT_NAME .
118
87
' --options= \'{ "allowedIndentationChars": ["\t"] } \' ".test { color: red; }" ' . PHP_EOL .
119
88
PHP_EOL . PHP_EOL );
120
89
}
121
90
91
+ /**
92
+ * Retrieve the parsed Cli arguments from given arguments array
93
+ * @param string[] $arguments arguments to be parsed (@see $_SERVER['argv'])
94
+ * @return CliArgs an instance of Cli arguments object containing parsed arguments
95
+ */
96
+ private function parseArguments (array $ arguments ): CliArgs
97
+ {
98
+ return new CliArgs ($ arguments );
99
+ }
100
+
101
+ /**
102
+ * Retrieve the properties from the given options
103
+ * @param string $options the options to be parsed
104
+ */
105
+ private function getPropertiesFromOptions (?string $ options ): Properties
106
+ {
107
+ $ properties = new Properties ();
108
+ if ($ options === null || $ options === '' || $ options === '0 ' ) {
109
+ return $ properties ;
110
+ }
111
+
112
+ $ options = json_decode ($ options , true );
113
+
114
+ if (json_last_error () !== 0 ) {
115
+ $ errorMessage = json_last_error_msg ();
116
+ throw new RuntimeException ('Unable to parse option argument: ' . $ errorMessage );
117
+ }
118
+
119
+ if (!$ options ) {
120
+ throw new RuntimeException ('Unable to parse empty option argument ' );
121
+ }
122
+
123
+ if (!is_array ($ options )) {
124
+ throw new RuntimeException ('Unable to parse option argument: must be a json object ' );
125
+ }
126
+
127
+ $ properties ->setOptions ($ options );
128
+
129
+ return $ properties ;
130
+ }
131
+
132
+ private function lintInput (Linter $ cssLinter , string $ input ): int
133
+ {
134
+ if (file_exists ($ input )) {
135
+ return $ this ->lintFile ($ cssLinter , $ input );
136
+ }
137
+
138
+ if ($ this ->isGlobPattern ($ input )) {
139
+ return $ this ->lintGlob ($ input );
140
+ }
141
+
142
+ return $ this ->lintString ($ cssLinter , $ input );
143
+ }
144
+
145
+ /**
146
+ * Checks if a given string is a glob pattern.
147
+ *
148
+ * A glob pattern typically includes wildcard characters:
149
+ * - '*' matches any sequence of characters.
150
+ * - '?' matches any single character.
151
+ * - '[]' matches any one character in the specified set.
152
+ *
153
+ * Optionally, if using the GLOB_BRACE flag, brace patterns like {foo,bar} are also valid.
154
+ *
155
+ * @param string $pattern The string to evaluate.
156
+ * @return bool True if the string is a glob pattern, false otherwise.
157
+ */
158
+ private function isGlobPattern (string $ pattern ): bool
159
+ {
160
+ // Must be one line, no unscaped spaces
161
+ if (preg_match ('/\s/ ' , $ pattern )) {
162
+ return false ;
163
+ }
164
+
165
+ // Check for basic wildcard characters.
166
+ if (str_contains ($ pattern , '* ' ) || str_contains ($ pattern , '? ' ) || str_contains ($ pattern , '[ ' )) {
167
+ return true ;
168
+ }
169
+
170
+ // Optionally check for brace patterns, used with GLOB_BRACE.
171
+ return str_contains ($ pattern , '{ ' ) || str_contains ($ pattern , '} ' );
172
+ }
173
+
174
+ private function lintGlob (string $ glob ): int
175
+ {
176
+ $ cssLinter = new Linter ();
177
+ $ files = glob ($ glob );
178
+ if ($ files === [] || $ files === false ) {
179
+ $ this ->printError ('No files found for glob " ' . $ glob . '" ' );
180
+ return self ::RETURN_CODE_ERROR ;
181
+ }
182
+
183
+ $ returnCode = self ::RETURN_CODE_SUCCESS ;
184
+ foreach ($ files as $ file ) {
185
+ $ returnCode = max ($ returnCode , $ this ->lintFile ($ cssLinter , $ file ));
186
+ }
187
+
188
+ return $ returnCode ;
189
+ }
190
+
122
191
/**
123
192
* Performs lint on a given file path
124
- * @param \CssLint\ Linter $cssLinter the instance of the linter
193
+ * @param Linter $cssLinter the instance of the linter
125
194
* @param string $filePath the path of the file to be linted
126
195
* @return int the return code related to the execution of the linter
127
196
*/
128
- private function lintFile (\ CssLint \ Linter $ cssLinter , string $ filePath ): int
197
+ private function lintFile (Linter $ cssLinter , string $ filePath ): int
129
198
{
130
199
$ this ->printLine ('# Lint CSS file " ' . $ filePath . '"... ' );
131
200
201
+ if (!is_readable ($ filePath )) {
202
+ $ this ->printError ('File " ' . $ filePath . '" is not readable ' );
203
+ return self ::RETURN_CODE_ERROR ;
204
+ }
205
+
132
206
if ($ cssLinter ->lintFile ($ filePath )) {
133
207
$ this ->printLine ("\033[32m => CSS file \"" . $ filePath . "\" is valid \033[0m " . PHP_EOL );
134
208
return self ::RETURN_CODE_SUCCESS ;
@@ -142,11 +216,11 @@ private function lintFile(\CssLint\Linter $cssLinter, string $filePath): int
142
216
143
217
/**
144
218
* Performs lint on a given string
145
- * @param \CssLint\ Linter $cssLinter the instance of the linter
219
+ * @param Linter $cssLinter the instance of the linter
146
220
* @param string $stringValue the CSS string to be linted
147
221
* @return int the return code related to the execution of the linter
148
222
*/
149
- private function lintString (\ CssLint \ Linter $ cssLinter , string $ stringValue ): int
223
+ private function lintString (Linter $ cssLinter , string $ stringValue ): int
150
224
{
151
225
$ this ->printLine ('# Lint CSS string... ' );
152
226
0 commit comments