@@ -6,21 +6,165 @@ fn main() {
6
6
7
7
#[ cfg( feature = "cli" ) ]
8
8
fn main ( ) -> Result < ( ) , Box < dyn std:: error:: Error > > {
9
+ use core:: fmt;
9
10
use css_inline:: { CSSInliner , DefaultStylesheetResolver , InlineOptions } ;
10
11
use rayon:: prelude:: * ;
11
12
use std:: {
12
13
borrow:: Cow ,
14
+ env,
15
+ error:: Error ,
13
16
ffi:: OsString ,
14
- fmt:: { Display , Write as FmtWrite } ,
17
+ fmt:: Write as FmtWrite ,
15
18
fs:: { read_to_string, File } ,
16
19
io:: { self , Read , Write } ,
17
20
path:: Path ,
21
+ str:: FromStr ,
18
22
sync:: {
19
23
atomic:: { AtomicI32 , Ordering } ,
20
24
Arc ,
21
25
} ,
22
26
} ;
23
27
28
+ fn parse_url ( url : Option < & str > ) -> Result < Option < url:: Url > , url:: ParseError > {
29
+ Ok ( if let Some ( url) = url {
30
+ Some ( url:: Url :: parse ( url) ?)
31
+ } else {
32
+ None
33
+ } )
34
+ }
35
+
36
+ #[ derive( Debug ) ]
37
+ struct ParseError {
38
+ message : String ,
39
+ }
40
+
41
+ impl fmt:: Display for ParseError {
42
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
43
+ write ! ( f, "{}" , self . message)
44
+ }
45
+ }
46
+
47
+ impl Error for ParseError { }
48
+
49
+ struct ParsedArgs {
50
+ help : bool ,
51
+ version : bool ,
52
+ files : Vec < String > ,
53
+ inline_style_tags : bool ,
54
+ keep_style_tags : bool ,
55
+ keep_link_tags : bool ,
56
+ base_url : Option < String > ,
57
+ extra_css : Option < String > ,
58
+ output_filename_prefix : Option < OsString > ,
59
+ load_remote_stylesheets : bool ,
60
+ #[ cfg( feature = "stylesheet-cache" ) ]
61
+ cache_size : Option < usize > ,
62
+ }
63
+
64
+ impl Default for ParsedArgs {
65
+ fn default ( ) -> Self {
66
+ Self {
67
+ help : false ,
68
+ version : false ,
69
+ files : Vec :: new ( ) ,
70
+ inline_style_tags : true ,
71
+ keep_style_tags : false ,
72
+ keep_link_tags : false ,
73
+ base_url : None ,
74
+ extra_css : None ,
75
+ output_filename_prefix : None ,
76
+ load_remote_stylesheets : false ,
77
+ #[ cfg( feature = "stylesheet-cache" ) ]
78
+ cache_size : None ,
79
+ }
80
+ }
81
+ }
82
+
83
+ #[ cfg( feature = "stylesheet-cache" ) ]
84
+ macro_rules! if_cfg_feature_stylesheet_cache {
85
+ ( $val: expr) => {
86
+ $val
87
+ } ;
88
+ }
89
+
90
+ #[ cfg( not( feature = "stylesheet-cache" ) ) ]
91
+ macro_rules! if_cfg_feature_stylesheet_cache {
92
+ // Empty string that won't match
93
+ ( $val: expr) => {
94
+ ""
95
+ } ;
96
+ }
97
+
98
+ fn requires_value ( flag : & str ) -> bool {
99
+ matches ! (
100
+ flag,
101
+ "inline-style-tags"
102
+ | "base-url"
103
+ | "extra-css"
104
+ | "output-filename-prefix"
105
+ | if_cfg_feature_stylesheet_cache!( "cache-size" )
106
+ )
107
+ }
108
+
109
+ fn parse_value < T > ( value : & str , flag : & str ) -> Result < T , ParseError >
110
+ where
111
+ T : FromStr ,
112
+ T :: Err : fmt:: Display ,
113
+ {
114
+ value. parse :: < T > ( ) . map_err ( |e| ParseError {
115
+ message : format ! ( "Failed to parse value '{value}' for flag '{flag}': {e}" ) ,
116
+ } )
117
+ }
118
+
119
+ fn handle_flag_with_value (
120
+ parsed : & mut ParsedArgs ,
121
+ flag : & str ,
122
+ value : & str ,
123
+ ) -> Result < ( ) , ParseError > {
124
+ match flag {
125
+ "inline-style-tags" => parsed. inline_style_tags = parse_value ( value, flag) ?,
126
+ "base-url" => parsed. base_url = Some ( value. to_string ( ) ) ,
127
+ "extra-css" => parsed. extra_css = Some ( value. to_string ( ) ) ,
128
+ "output-filename-prefix" => {
129
+ parsed. output_filename_prefix = Some ( value. to_string ( ) . into ( ) ) ;
130
+ }
131
+ #[ cfg( feature = "stylesheet-cache" ) ]
132
+ "cache-size" => parsed. cache_size = Some ( parse_value ( value, flag) ?) ,
133
+ _ => {
134
+ return Err ( ParseError {
135
+ message : format ! ( "Unknown flag: --{flag}" ) ,
136
+ } )
137
+ }
138
+ }
139
+ Ok ( ( ) )
140
+ }
141
+
142
+ fn handle_boolean_flag ( parsed : & mut ParsedArgs , flag : & str ) -> Result < ( ) , ParseError > {
143
+ match flag {
144
+ "help" | "h" => parsed. help = true ,
145
+ "version" | "v" => parsed. version = true ,
146
+ "keep-style-tags" => parsed. keep_style_tags = true ,
147
+ "keep-link-tags" => parsed. keep_link_tags = true ,
148
+ "load-remote-stylesheets" => parsed. load_remote_stylesheets = true ,
149
+ _ => {
150
+ return Err ( ParseError {
151
+ message : format ! ( "Unknown flag: {flag}" ) ,
152
+ } )
153
+ }
154
+ }
155
+ Ok ( ( ) )
156
+ }
157
+
158
+ fn format_error ( filename : Option < & str > , error : impl fmt:: Display ) {
159
+ let mut buffer = String :: with_capacity ( 128 ) ;
160
+ if let Some ( filename) = filename {
161
+ writeln ! ( buffer, "Filename: {filename}" ) . expect ( "Failed to write to buffer" ) ;
162
+ }
163
+ buffer. push_str ( "Status: ERROR\n " ) ;
164
+ writeln ! ( buffer, "Details: {error}" ) . expect ( "Failed to write to buffer" ) ;
165
+ eprintln ! ( "{}" , buffer. trim( ) ) ;
166
+ }
167
+
24
168
const VERSION_MESSAGE : & [ u8 ] =
25
169
concat ! ( "css-inline " , env!( "CARGO_PKG_VERSION" ) , "\n " ) . as_bytes ( ) ;
26
170
const HELP_MESSAGE : & [ u8 ] = concat ! (
@@ -74,86 +218,76 @@ OPTIONS:
74
218
)
75
219
. as_bytes ( ) ;
76
220
77
- struct Args {
78
- inline_style_tags : bool ,
79
- keep_style_tags : bool ,
80
- keep_link_tags : bool ,
81
- base_url : Option < String > ,
82
- extra_css : Option < String > ,
83
- output_filename_prefix : Option < OsString > ,
84
- load_remote_stylesheets : bool ,
85
- #[ cfg( feature = "stylesheet-cache" ) ]
86
- cache_size : Option < usize > ,
87
- files : Vec < String > ,
88
- }
221
+ let mut raw_args = env:: args ( ) . skip ( 1 ) ;
222
+ let mut args = ParsedArgs :: default ( ) ;
89
223
90
- fn parse_url ( url : Option < String > ) -> Result < Option < url:: Url > , url:: ParseError > {
91
- Ok ( if let Some ( url) = url {
92
- Some ( url:: Url :: parse ( url. as_str ( ) ) ?)
224
+ while let Some ( arg) = raw_args. next ( ) {
225
+ if let Some ( flag) = arg. strip_prefix ( "--" ) {
226
+ // Handle --key=value format
227
+ if let Some ( ( flag, value) ) = flag. split_once ( '=' ) {
228
+ handle_flag_with_value ( & mut args, flag, value) ?;
229
+ } else {
230
+ // Handle --key format (boolean or expecting value)
231
+ if requires_value ( flag) {
232
+ // Expects a value
233
+ if let Some ( value) = raw_args. next ( ) {
234
+ handle_flag_with_value ( & mut args, flag, & value) ?;
235
+ } else {
236
+ eprintln ! ( "Error parsing arguments: Flag --{flag} requires a value" ) ;
237
+ std:: process:: exit ( 1 ) ;
238
+ }
239
+ } else {
240
+ // Boolean flag
241
+ handle_boolean_flag ( & mut args, flag) ?;
242
+ }
243
+ }
244
+ } else if let Some ( flag) = arg. strip_prefix ( '-' ) {
245
+ if flag. len ( ) == 1 {
246
+ // Single character short flag
247
+ handle_boolean_flag ( & mut args, flag) ?;
248
+ } else {
249
+ eprintln ! ( "Error parsing arguments: Invalid flag: -{flag}" ) ;
250
+ std:: process:: exit ( 1 ) ;
251
+ }
93
252
} else {
94
- None
95
- } )
96
- }
97
-
98
- fn format_error ( filename : Option < & str > , error : impl Display ) {
99
- let mut buffer = String :: with_capacity ( 128 ) ;
100
- if let Some ( filename) = filename {
101
- writeln ! ( buffer, "Filename: {}" , filename) . expect ( "Failed to write to buffer" ) ;
253
+ // Positional argument (file)
254
+ args. files . push ( arg) ;
102
255
}
103
- buffer. push_str ( "Status: ERROR\n " ) ;
104
- writeln ! ( buffer, "Details: {}" , error) . expect ( "Failed to write to buffer" ) ;
105
- eprintln ! ( "{}" , buffer. trim( ) ) ;
106
256
}
107
257
108
- let mut args = pico_args:: Arguments :: from_env ( ) ;
109
258
let exit_code = AtomicI32 :: new ( 0 ) ;
110
- if args. contains ( [ "-h" , "-- help" ] ) {
259
+ if args. help {
111
260
io:: stdout ( ) . write_all ( HELP_MESSAGE ) ?;
112
- } else if args. contains ( [ "-v" , "-- version" ] ) {
261
+ } else if args. version {
113
262
io:: stdout ( ) . write_all ( VERSION_MESSAGE ) ?;
114
263
} else {
115
- let args = Args {
116
- inline_style_tags : args
117
- . opt_value_from_str ( "--inline-style-tags" ) ?
118
- . unwrap_or ( true ) ,
119
- keep_style_tags : args. contains ( "--keep-style-tags" ) ,
120
- keep_link_tags : args. contains ( "--keep-link-tags" ) ,
121
- base_url : args. opt_value_from_str ( "--base-url" ) ?,
122
- extra_css : args. opt_value_from_str ( "--extra-css" ) ?,
123
- output_filename_prefix : args. opt_value_from_str ( "--output-filename-prefix" ) ?,
124
- load_remote_stylesheets : args. contains ( "--load-remote-stylesheets" ) ,
125
- #[ cfg( feature = "stylesheet-cache" ) ]
126
- cache_size : args. opt_value_from_str ( "--cache-size" ) ?,
127
- files : args. free ( ) ?,
128
- } ;
129
- let base_url = match parse_url ( args. base_url ) {
264
+ let base_url = match parse_url ( args. base_url . as_deref ( ) ) {
130
265
Ok ( base_url) => base_url,
131
266
Err ( error) => {
132
267
format_error ( None , error) ;
133
268
std:: process:: exit ( 1 ) ;
134
269
}
135
270
} ;
271
+ #[ cfg( feature = "stylesheet-cache" ) ]
272
+ let cache = if let Some ( size) = args. cache_size {
273
+ if size == 0 {
274
+ eprintln ! ( "ERROR: Cache size must be an integer greater than zero" ) ;
275
+ std:: process:: exit ( 1 ) ;
276
+ }
277
+ std:: num:: NonZeroUsize :: new ( size)
278
+ . map ( css_inline:: StylesheetCache :: new)
279
+ . map ( std:: sync:: Mutex :: new)
280
+ } else {
281
+ None
282
+ } ;
136
283
let options = InlineOptions {
137
284
inline_style_tags : args. inline_style_tags ,
138
285
keep_style_tags : args. keep_style_tags ,
139
286
keep_link_tags : args. keep_link_tags ,
140
287
base_url,
141
288
load_remote_stylesheets : args. load_remote_stylesheets ,
142
289
#[ cfg( feature = "stylesheet-cache" ) ]
143
- cache : {
144
- if let Some ( size) = args. cache_size {
145
- if size == 0 {
146
- eprintln ! ( "ERROR: Cache size must be an integer greater than zero" ) ;
147
- std:: process:: exit ( 1 ) ;
148
- }
149
-
150
- std:: num:: NonZeroUsize :: new ( size)
151
- . map ( css_inline:: StylesheetCache :: new)
152
- . map ( std:: sync:: Mutex :: new)
153
- } else {
154
- None
155
- }
156
- } ,
290
+ cache,
157
291
extra_css : args. extra_css . as_deref ( ) . map ( Cow :: Borrowed ) ,
158
292
preallocate_node_capacity : 32 ,
159
293
resolver : Arc :: new ( DefaultStylesheetResolver ) ,
@@ -192,7 +326,7 @@ OPTIONS:
192
326
} )
193
327
. for_each ( |result| match result {
194
328
Ok ( ( filename, result) ) => match result {
195
- Ok ( _ ) => println ! ( "{filename}: SUCCESS" ) ,
329
+ Ok ( ( ) ) => println ! ( "{filename}: SUCCESS" ) ,
196
330
Err ( error) => {
197
331
format_error ( Some ( filename. as_str ( ) ) , error) ;
198
332
exit_code. store ( 1 , Ordering :: SeqCst ) ;
0 commit comments