2019-12-25 04:44:51 -05:00
use std ::fs ::File ;
2021-08-07 05:10:47 -04:00
use std ::io ::{ stdin , stdout , Read , Write } ;
2022-06-22 00:03:19 -04:00
use std ::process ::exit ;
use std ::sync ::Arc ;
2019-12-25 07:29:18 -05:00
2022-07-04 22:03:11 -04:00
use rayon ::iter ::{ IntoParallelRefIterator , ParallelIterator } ;
2019-12-25 04:44:51 -05:00
use structopt ::StructOpt ;
2021-08-07 05:10:47 -04:00
use minify_html ::{ minify , Cfg } ;
2019-12-25 04:44:51 -05:00
#[ derive(StructOpt) ]
2021-08-07 05:10:47 -04:00
#[ structopt(
name = " minify-html " ,
about = " Extremely fast and smart HTML + JS + CSS minifier "
) ]
// WARNING: Keep descriptions in sync with Cfg.
2019-12-25 04:44:51 -05:00
struct Cli {
2022-06-22 00:03:19 -04:00
/// Files to minify; omit for stdin. If more than one is provided, they will be parallel minified in place, and --output must be omitted.
2021-08-07 21:44:13 -04:00
#[ structopt(parse(from_os_str)) ]
2022-06-22 00:03:19 -04:00
inputs : Vec < std ::path ::PathBuf > ,
2021-08-10 03:45:37 -04:00
2021-01-07 09:58:31 -05:00
/// Output destination; omit for stdout.
2019-12-25 04:44:51 -05:00
#[ structopt(short, long, parse(from_os_str)) ]
2021-08-07 21:44:13 -04:00
output : Option < std ::path ::PathBuf > ,
2021-08-10 03:45:37 -04:00
2021-08-07 05:10:47 -04:00
/// Minify JS in `<script>` tags that have a valid or no `type` attribute value.
2020-07-10 11:15:56 -04:00
#[ structopt(long) ]
2021-08-07 05:10:47 -04:00
minify_js : bool ,
2021-08-10 03:45:37 -04:00
2021-08-07 05:10:47 -04:00
/// Minify CSS in `<style>` tags and `style` attributes.
2021-01-07 08:26:02 -05:00
#[ structopt(long) ]
2021-08-07 05:10:47 -04:00
minify_css : bool ,
2021-08-10 03:45:37 -04:00
2023-02-16 03:23:03 -05:00
/// Use optimisation level 1 for the CSS minifier.
#[ structopt(long) ]
minify_css_level_1 : bool ,
/// Use optimisation level 2 for the CSS minifier. May perform some dangerous optimisations.
#[ structopt(long) ]
minify_css_level_2 : bool ,
/// Use optimisation level 3 for the CSS minifier. May perform many dangerous optimisations.
#[ structopt(long) ]
minify_css_level_3 : bool ,
2021-08-10 03:45:37 -04:00
#[ structopt(long) ]
/// Do not minify DOCTYPEs. Minified DOCTYPEs may not be spec compliant.
do_not_minify_doctype : bool ,
2021-08-07 11:45:25 -04:00
/// Ensure all unquoted attribute values in the output do not contain any characters prohibited by the WHATWG specification.
2021-08-07 12:31:32 -04:00
#[ structopt(long) ]
2021-08-07 12:22:29 -04:00
ensure_spec_compliant_unquoted_attribute_values : bool ,
2021-08-10 03:45:37 -04:00
2021-08-07 05:10:47 -04:00
/// Do not omit closing tags when possible.
#[ structopt(long) ]
keep_closing_tags : bool ,
2021-08-10 03:45:37 -04:00
2021-08-07 05:10:47 -04:00
/// Do not omit `<html>` and `<head>` opening tags when they don't have attributes.
#[ structopt(long) ]
keep_html_and_head_opening_tags : bool ,
2021-08-10 03:45:37 -04:00
2021-08-07 05:10:47 -04:00
/// Keep spaces between attributes when possible to conform to HTML standards.
#[ structopt(long) ]
keep_spaces_between_attributes : bool ,
2021-08-10 03:45:37 -04:00
2021-08-07 05:10:47 -04:00
/// Keep all comments.
#[ structopt(long) ]
keep_comments : bool ,
2021-08-10 03:45:37 -04:00
2021-08-07 05:10:47 -04:00
/// Remove all bangs.
#[ structopt(long) ]
remove_bangs : bool ,
2021-08-10 03:45:37 -04:00
2021-08-07 05:10:47 -04:00
/// Remove all processing_instructions.
#[ structopt(long) ]
remove_processing_instructions : bool ,
2020-01-25 19:30:41 -05:00
}
macro_rules ! io_expect {
2022-06-22 00:03:19 -04:00
( $name :expr , $expr :expr , $msg :literal ) = > {
2020-01-25 19:30:41 -05:00
match $expr {
Ok ( r ) = > r ,
Err ( e ) = > {
2022-06-22 00:03:19 -04:00
eprintln! ( " [ {} ] {} : {} " , $name , $msg , e ) ;
2020-01-25 19:30:41 -05:00
return ;
}
}
} ;
2019-12-25 04:44:51 -05:00
}
fn main ( ) {
let args = Cli ::from_args ( ) ;
2022-06-22 00:03:19 -04:00
if args . output . is_some ( ) & & args . inputs . len ( ) > 1 {
2022-06-22 00:47:48 -04:00
eprintln! ( " Cannot provide --output when multiple inputs are provided. " ) ;
2022-06-22 00:03:19 -04:00
exit ( 1 ) ;
2020-01-25 19:30:41 -05:00
} ;
2022-06-22 00:03:19 -04:00
let cfg = Arc ::new ( Cfg {
do_not_minify_doctype : args . do_not_minify_doctype ,
ensure_spec_compliant_unquoted_attribute_values : args
. ensure_spec_compliant_unquoted_attribute_values ,
keep_closing_tags : args . keep_closing_tags ,
keep_comments : args . keep_comments ,
keep_html_and_head_opening_tags : args . keep_html_and_head_opening_tags ,
keep_spaces_between_attributes : args . keep_spaces_between_attributes ,
minify_css : args . minify_css ,
2023-02-16 03:23:03 -05:00
minify_css_level_1 : args . minify_css_level_1 ,
minify_css_level_2 : args . minify_css_level_2 ,
minify_css_level_3 : args . minify_css_level_3 ,
2022-06-22 00:03:19 -04:00
minify_js : args . minify_js ,
remove_bangs : args . remove_bangs ,
remove_processing_instructions : args . remove_processing_instructions ,
} ) ;
2022-07-04 22:03:11 -04:00
if args . inputs . len ( ) < = 1 {
2022-07-04 22:09:00 -04:00
// Single file mode or stdin mode.
2022-06-22 00:03:19 -04:00
let input_name = args
. inputs
. get ( 0 )
. map ( | p | p . to_string_lossy ( ) . into_owned ( ) )
2022-07-04 22:03:11 -04:00
. unwrap_or_else ( | | " stdin " . to_string ( ) ) ;
2022-06-22 00:03:19 -04:00
let mut src_file : Box < dyn Read > = match args . inputs . get ( 0 ) {
Some ( p ) = > Box ::new ( io_expect! (
input_name ,
File ::open ( p ) ,
2022-06-22 00:47:48 -04:00
" Could not open source file "
2022-06-22 00:03:19 -04:00
) ) ,
None = > Box ::new ( stdin ( ) ) ,
} ;
let mut src_code = Vec ::< u8 > ::new ( ) ;
io_expect! (
input_name ,
src_file . read_to_end ( & mut src_code ) ,
2022-06-22 00:47:48 -04:00
" Could not load source code "
2022-06-22 00:03:19 -04:00
) ;
let out_code = minify ( & src_code , & cfg ) ;
let mut out_file : Box < dyn Write > = match args . output {
Some ( p ) = > Box ::new ( io_expect! (
input_name ,
File ::create ( p ) ,
2022-06-22 00:47:48 -04:00
" Could not open output file "
2022-06-22 00:03:19 -04:00
) ) ,
None = > Box ::new ( stdout ( ) ) ,
} ;
io_expect! (
input_name ,
out_file . write_all ( & out_code ) ,
2022-06-22 00:47:48 -04:00
" Could not save minified code "
2022-06-22 00:03:19 -04:00
) ;
} else {
2022-07-04 22:03:11 -04:00
args . inputs . par_iter ( ) . for_each ( | input | {
let input_name = input . to_string_lossy ( ) . into_owned ( ) ;
2022-06-22 00:03:19 -04:00
2022-07-04 22:03:11 -04:00
let mut src_file =
io_expect! ( input_name , File ::open ( & input ) , " Could not open source file " ) ;
let mut src_code = Vec ::< u8 > ::new ( ) ;
io_expect! (
input_name ,
src_file . read_to_end ( & mut src_code ) ,
" Could not load source code "
) ;
let out_code = minify ( & src_code , & cfg ) ;
let mut out_file = io_expect! (
input_name ,
File ::create ( & input ) ,
" Could not open output file "
) ;
io_expect! (
input_name ,
out_file . write_all ( & out_code ) ,
" Could not save minified code "
) ;
2022-07-04 22:09:00 -04:00
// Just print the name, since this is the default output and any prefix becomes redundant. It'd also allow piping into another command (quite nice for something like `minify-html *.html | xargs gzip`), copying as list of files, etc.
2022-07-04 22:03:11 -04:00
println! ( " {} " , input_name ) ;
} ) ;
2022-06-22 00:03:19 -04:00
}
2019-12-25 04:44:51 -05:00
}