@@ -5,8 +5,9 @@ use lightningcss::stylesheet::{MinifyOptions, ParserOptions, PrinterOptions, Sty
55use lightningcss:: targets:: Browsers ;
66use parcel_sourcemap:: SourceMap ;
77use serde:: Serialize ;
8+ use std:: borrow:: Cow ;
89use std:: sync:: { Arc , RwLock } ;
9- use std:: { ffi, fs, io, path, path :: Path } ;
10+ use std:: { ffi, fs, io, path:: Path } ;
1011
1112#[ cfg( target_os = "macos" ) ]
1213#[ global_allocator]
@@ -21,10 +22,13 @@ static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc;
2122struct CliArgs {
2223 /// Target CSS file (default: stdin)
2324 #[ clap( value_parser) ]
24- input_file : Option < String > ,
25+ input_file : Vec < String > ,
2526 /// Destination file for the output
2627 #[ clap( short, long, group = "output_file" , value_parser) ]
2728 output_file : Option < String > ,
29+ /// Destination directory to output into.
30+ #[ clap( short = 'd' , long, group = "output_file" , value_parser) ]
31+ output_dir : Option < String > ,
2832 /// Minify the output
2933 #[ clap( short, long, value_parser) ]
3034 minify : bool ,
@@ -76,26 +80,39 @@ pub fn main() -> Result<(), std::io::Error> {
7680 // from it and create a fake name. Return an error if stdin was not
7781 // redirected (otherwise the program will hang waiting for input).
7882 //
79- let ( filename, source) = match & cli_args. input_file {
80- Some ( f) => {
81- let absolute_path = fs:: canonicalize ( f) ?;
82- let filename = pathdiff:: diff_paths ( absolute_path, & project_root) . unwrap ( ) ;
83- let filename = filename. to_string_lossy ( ) . into_owned ( ) ;
84- let contents = fs:: read_to_string ( f) ?;
85- ( filename, contents)
83+ let inputs = if !cli_args. input_file . is_empty ( ) {
84+ if cli_args. input_file . len ( ) > 1 && cli_args. output_file . is_some ( ) {
85+ eprintln ! ( "Cannot use the --output-file option with multiple inputs. Use --output-dir instead." ) ;
86+ std:: process:: exit ( 1 ) ;
8687 }
87- None => {
88- // Don't silently wait for input if stdin was not redirected.
89- if atty:: is ( Stream :: Stdin ) {
90- return Err ( io:: Error :: new (
91- io:: ErrorKind :: Other ,
92- "Not reading from stdin as it was not redirected" ,
93- ) ) ;
94- }
95- let filename = format ! ( "stdin-{}" , std:: process:: id( ) ) ;
96- let contents = io:: read_to_string ( io:: stdin ( ) ) ?;
97- ( filename, contents)
88+
89+ if cli_args. input_file . len ( ) > 1 && cli_args. output_file . is_none ( ) && cli_args. output_dir . is_none ( ) {
90+ eprintln ! ( "Cannot output to stdout with multiple inputs. Use --output-dir instead." ) ;
91+ std:: process:: exit ( 1 ) ;
9892 }
93+
94+ cli_args
95+ . input_file
96+ . into_iter ( )
97+ . map ( |ref f| -> Result < _ , std:: io:: Error > {
98+ let absolute_path = fs:: canonicalize ( f) ?;
99+ let filename = pathdiff:: diff_paths ( absolute_path, & project_root) . unwrap ( ) ;
100+ let filename = filename. to_string_lossy ( ) . into_owned ( ) ;
101+ let contents = fs:: read_to_string ( f) ?;
102+ Ok ( ( filename, contents) )
103+ } )
104+ . collect :: < Result < _ , _ > > ( ) ?
105+ } else {
106+ // Don't silently wait for input if stdin was not redirected.
107+ if atty:: is ( Stream :: Stdin ) {
108+ return Err ( io:: Error :: new (
109+ io:: ErrorKind :: Other ,
110+ "Not reading from stdin as it was not redirected" ,
111+ ) ) ;
112+ }
113+ let filename = format ! ( "stdin-{}" , std:: process:: id( ) ) ;
114+ let contents = io:: read_to_string ( io:: stdin ( ) ) ?;
115+ vec ! [ ( filename, contents) ]
99116 } ;
100117
101118 let css_modules = if let Some ( _) = cli_args. css_modules {
@@ -121,138 +138,149 @@ pub fn main() -> Result<(), std::io::Error> {
121138 } ;
122139
123140 let fs = FileProvider :: new ( ) ;
124- let warnings = if cli_args. error_recovery {
125- Some ( Arc :: new ( RwLock :: new ( Vec :: new ( ) ) ) )
126- } else {
127- None
128- } ;
129-
130- let mut source_map = if cli_args. sourcemap {
131- Some ( SourceMap :: new ( & project_root. to_string_lossy ( ) ) )
132- } else {
133- None
134- } ;
135141
136- let res = {
137- let mut options = ParserOptions {
138- nesting : cli_args. nesting ,
139- css_modules,
140- custom_media : cli_args. custom_media ,
141- error_recovery : cli_args. error_recovery ,
142- warnings : warnings. clone ( ) ,
143- ..ParserOptions :: default ( )
142+ for ( filename, source) in inputs {
143+ let warnings = if cli_args. error_recovery {
144+ Some ( Arc :: new ( RwLock :: new ( Vec :: new ( ) ) ) )
145+ } else {
146+ None
144147 } ;
145148
146- let mut stylesheet = if cli_args. bundle {
147- let mut bundler = Bundler :: new ( & fs, source_map. as_mut ( ) , options) ;
148- bundler. bundle ( Path :: new ( & filename) ) . unwrap ( )
149+ let mut source_map = if cli_args. sourcemap {
150+ Some ( SourceMap :: new ( & project_root. to_string_lossy ( ) ) )
149151 } else {
150- if let Some ( sm) = & mut source_map {
151- sm. add_source ( & filename) ;
152- let _ = sm. set_source_content ( 0 , & source) ;
153- }
154- options. filename = filename;
155- StyleSheet :: parse ( & source, options) . unwrap ( )
152+ None
156153 } ;
157154
158- let targets = if !cli_args. targets . is_empty ( ) {
159- Browsers :: from_browserslist ( cli_args. targets ) . unwrap ( )
160- } else if cli_args. browserslist {
161- Browsers :: load_browserslist ( ) . unwrap ( )
155+ let output_file = if let Some ( output_file) = & cli_args. output_file {
156+ Some ( Cow :: Borrowed ( Path :: new ( output_file) ) )
157+ } else if let Some ( dir) = & cli_args. output_dir {
158+ Some ( Cow :: Owned (
159+ Path :: new ( dir) . join ( Path :: new ( & filename) . file_name ( ) . unwrap ( ) ) ,
160+ ) )
162161 } else {
163162 None
164163 } ;
165164
166- stylesheet
167- . minify ( MinifyOptions {
168- targets,
169- ..MinifyOptions :: default ( )
170- } )
171- . unwrap ( ) ;
165+ let res = {
166+ let mut options = ParserOptions {
167+ nesting : cli_args. nesting ,
168+ css_modules : css_modules. clone ( ) ,
169+ custom_media : cli_args. custom_media ,
170+ error_recovery : cli_args. error_recovery ,
171+ warnings : warnings. clone ( ) ,
172+ ..ParserOptions :: default ( )
173+ } ;
172174
173- stylesheet
174- . to_css ( PrinterOptions {
175- minify : cli_args. minify ,
176- source_map : source_map. as_mut ( ) ,
177- project_root : Some ( & project_root. to_string_lossy ( ) ) ,
178- targets,
179- ..PrinterOptions :: default ( )
180- } )
181- . unwrap ( )
182- } ;
175+ let mut stylesheet = if cli_args. bundle {
176+ let mut bundler = Bundler :: new ( & fs, source_map. as_mut ( ) , options) ;
177+ bundler. bundle ( Path :: new ( & filename) ) . unwrap ( )
178+ } else {
179+ if let Some ( sm) = & mut source_map {
180+ sm. add_source ( & filename) ;
181+ let _ = sm. set_source_content ( 0 , & source) ;
182+ }
183+ options. filename = filename;
184+ StyleSheet :: parse ( & source, options) . unwrap ( )
185+ } ;
183186
184- let map = if let Some ( ref mut source_map) = source_map {
185- let mut vlq_output: Vec < u8 > = Vec :: new ( ) ;
186- source_map
187- . write_vlq ( & mut vlq_output)
188- . map_err ( |_| io:: Error :: new ( io:: ErrorKind :: Other , "Error writing sourcemap vlq" ) ) ?;
187+ let targets = if !cli_args. targets . is_empty ( ) {
188+ Browsers :: from_browserslist ( & cli_args. targets ) . unwrap ( )
189+ } else if cli_args. browserslist {
190+ Browsers :: load_browserslist ( ) . unwrap ( )
191+ } else {
192+ None
193+ } ;
189194
190- let sm = SourceMapJson {
191- version : 3 ,
192- mappings : unsafe { String :: from_utf8_unchecked ( vlq_output) } ,
193- sources : source_map. get_sources ( ) ,
194- sources_content : source_map. get_sources_content ( ) ,
195- names : source_map. get_names ( ) ,
195+ stylesheet
196+ . minify ( MinifyOptions {
197+ targets,
198+ ..MinifyOptions :: default ( )
199+ } )
200+ . unwrap ( ) ;
201+
202+ stylesheet
203+ . to_css ( PrinterOptions {
204+ minify : cli_args. minify ,
205+ source_map : source_map. as_mut ( ) ,
206+ project_root : Some ( & project_root. to_string_lossy ( ) ) ,
207+ targets,
208+ ..PrinterOptions :: default ( )
209+ } )
210+ . unwrap ( )
196211 } ;
197212
198- serde_json:: to_vec ( & sm) . ok ( )
199- } else {
200- None
201- } ;
213+ let map = if let Some ( ref mut source_map) = source_map {
214+ let mut vlq_output: Vec < u8 > = Vec :: new ( ) ;
215+ source_map
216+ . write_vlq ( & mut vlq_output)
217+ . map_err ( |_| io:: Error :: new ( io:: ErrorKind :: Other , "Error writing sourcemap vlq" ) ) ?;
202218
203- if let Some ( warnings) = warnings {
204- let warnings = Arc :: try_unwrap ( warnings) . unwrap ( ) . into_inner ( ) . unwrap ( ) ;
205- for warning in warnings {
206- eprintln ! ( "{}" , warning) ;
207- }
208- }
219+ let sm = SourceMapJson {
220+ version : 3 ,
221+ mappings : unsafe { String :: from_utf8_unchecked ( vlq_output) } ,
222+ sources : source_map. get_sources ( ) ,
223+ sources_content : source_map. get_sources_content ( ) ,
224+ names : source_map. get_names ( ) ,
225+ } ;
226+
227+ serde_json:: to_vec ( & sm) . ok ( )
228+ } else {
229+ None
230+ } ;
209231
210- if let Some ( output_file) = & cli_args. output_file {
211- let mut code = res. code ;
212- if cli_args. sourcemap {
213- if let Some ( map_buf) = map {
214- let map_filename: String = output_file. to_owned ( ) + ".map" ;
215- code += & format ! ( "\n /*# sourceMappingURL={} */\n " , map_filename) ;
216- fs:: write ( map_filename, map_buf) ?;
232+ if let Some ( warnings) = warnings {
233+ let warnings = Arc :: try_unwrap ( warnings) . unwrap ( ) . into_inner ( ) . unwrap ( ) ;
234+ for warning in warnings {
235+ eprintln ! ( "{}" , warning) ;
217236 }
218237 }
219238
220- let output_path = Path :: new ( output_file) ;
221- if let Some ( p) = output_path. parent ( ) {
222- fs:: create_dir_all ( p) ?
223- } ;
224- fs:: write ( output_file, code. as_bytes ( ) ) ?;
239+ if let Some ( output_file) = & output_file {
240+ let mut code = res. code ;
241+ if cli_args. sourcemap {
242+ if let Some ( map_buf) = map {
243+ let map_filename = output_file. to_string_lossy ( ) + ".map" ;
244+ code += & format ! ( "\n /*# sourceMappingURL={} */\n " , map_filename) ;
245+ fs:: write ( map_filename. as_ref ( ) , map_buf) ?;
246+ }
247+ }
225248
226- if let Some ( css_modules) = cli_args. css_modules {
227- let css_modules_filename = if let Some ( name) = css_modules {
228- name
229- } else {
230- infer_css_modules_filename ( & output_file) ?
249+ if let Some ( p) = output_file. parent ( ) {
250+ fs:: create_dir_all ( p) ?
231251 } ;
232- if let Some ( exports) = res. exports {
233- let css_modules_json = serde_json:: to_string ( & exports) ?;
234- fs:: write ( css_modules_filename, css_modules_json) ?;
252+ fs:: write ( output_file, code. as_bytes ( ) ) ?;
253+
254+ if let Some ( css_modules) = & cli_args. css_modules {
255+ let css_modules_filename = if let Some ( name) = css_modules {
256+ Cow :: Borrowed ( name)
257+ } else {
258+ Cow :: Owned ( infer_css_modules_filename ( output_file. as_ref ( ) ) ?)
259+ } ;
260+ if let Some ( exports) = res. exports {
261+ let css_modules_json = serde_json:: to_string ( & exports) ?;
262+ fs:: write ( css_modules_filename. as_ref ( ) , css_modules_json) ?;
263+ }
235264 }
236- }
237- } else {
238- if let Some ( exports) = res. exports {
239- println ! (
240- "{}" ,
241- serde_json:: json!( {
242- "code" : res. code,
243- "exports" : exports
244- } )
245- ) ;
246265 } else {
247- println ! ( "{}" , res. code) ;
266+ if let Some ( exports) = res. exports {
267+ println ! (
268+ "{}" ,
269+ serde_json:: json!( {
270+ "code" : res. code,
271+ "exports" : exports
272+ } )
273+ ) ;
274+ } else {
275+ println ! ( "{}" , res. code) ;
276+ }
248277 }
249278 }
250279
251280 Ok ( ( ) )
252281}
253282
254- fn infer_css_modules_filename ( output_file : & str ) -> Result < String , std:: io:: Error > {
255- let path = path:: Path :: new ( output_file) ;
283+ fn infer_css_modules_filename ( path : & Path ) -> Result < String , std:: io:: Error > {
256284 if path. extension ( ) == Some ( ffi:: OsStr :: new ( "json" ) ) {
257285 Err ( io:: Error :: new (
258286 io:: ErrorKind :: Other ,
0 commit comments