main.ml 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. open Lexing
  2. open Types
  3. type args = {
  4. infiles : string list;
  5. outfile : string option;
  6. verbose : bool;
  7. whitespace : bool;
  8. simple : bool;
  9. shorthands : bool;
  10. duplicates : bool;
  11. echo : bool;
  12. }
  13. let parse_args () =
  14. let usage =
  15. "Usage: " ^ Sys.argv.(0) ^
  16. " [<options>] [<file> ...]\n\
  17. \n\
  18. Generic options:\n \
  19. -h, --help Show this help message\n \
  20. -v, --verbose Verbose mode: show compression rate\n \
  21. -o <file> Output file (defaults to stdout)\n \
  22. <file> ... Input files (default is to read from stdin)\n\
  23. \n\
  24. Optimization flags (if none are specified, all are enabled):\n \
  25. -w, --whitespace Eliminate unnecessary whitespaces (has the greatest \
  26. effect, omit for pretty-printing)\n \
  27. -c, --simple Shorten colors and font weights\n \
  28. -s, --shorthands Generate shorthand properties\n \
  29. -d, --duplicates Prune duplicate properties (WARNING: may affect \
  30. cross-browser hacks)\n \
  31. -p, --pretty Shorthand for -c -s -d\n \
  32. -e, --echo Just parse and pretty-print, no optimizations\n"
  33. in
  34. let default_args = {
  35. infiles = [];
  36. outfile = None;
  37. verbose = false;
  38. whitespace = false;
  39. simple = false;
  40. shorthands = false;
  41. duplicates = false;
  42. echo = false;
  43. } in
  44. let rec handle args = function
  45. | ("-v" | "--verbose") :: tl ->
  46. handle {args with verbose = true} tl
  47. | ("-w" | "--whitespace") :: tl ->
  48. handle {args with whitespace = true} tl
  49. | ("-c" | "--simple") :: tl ->
  50. handle {args with simple = true} tl
  51. | ("-s" | "--shorthands") :: tl ->
  52. handle {args with shorthands = true} tl
  53. | ("-d" | "-duplicates") :: tl ->
  54. handle {args with duplicates = true} tl
  55. | ("-p" | "--pretty") :: tl ->
  56. handle {args with simple = true; shorthands = true; duplicates = true} tl
  57. | ("-e" | "--echo") :: tl ->
  58. handle {args with echo = true} tl
  59. | ("-h" | "--help") :: tl ->
  60. prerr_string usage;
  61. raise Exit_success
  62. | ["-o"] ->
  63. raise (Failure ("missing output file name"))
  64. | "-o" :: next :: tl when next.[0] = '-' ->
  65. raise (Failure ("missing output file name"))
  66. | "-o" :: filename :: tl ->
  67. handle {args with outfile = Some filename} tl
  68. | arg :: tl when arg.[0] = '-' ->
  69. prerr_string usage;
  70. raise (Failure ("unknown option " ^ arg))
  71. | filename :: tl ->
  72. handle {args with infiles = args.infiles @ [filename]} tl
  73. | [] -> args
  74. in
  75. match handle default_args (List.tl (Array.to_list Sys.argv)) with
  76. | { whitespace = false;
  77. simple = false;
  78. shorthands = false;
  79. duplicates = false;
  80. echo = false;
  81. _ } as args ->
  82. { args with
  83. whitespace = true;
  84. simple = true;
  85. shorthands = true;
  86. duplicates = true }
  87. | args -> args
  88. let parse_files = function
  89. | [] ->
  90. let input = Util.input_buffered stdin 512 in
  91. (input, Parse.parse_input "<stdin>" input)
  92. | files ->
  93. let rec loop = function
  94. | [] -> []
  95. | filename :: tl ->
  96. if not (Sys.file_exists filename) then
  97. raise (Failure ("file " ^ filename ^ " does not exist"));
  98. let input = Util.input_all (open_in filename) in
  99. let stylesheet = Parse.parse_input filename input in
  100. (input, stylesheet) :: loop tl
  101. in
  102. let inputs, stylesheets = List.split (loop files) in
  103. (String.concat "" inputs, List.concat stylesheets)
  104. let handle_args args =
  105. let write_output =
  106. match args.outfile with
  107. | None -> print_endline
  108. | Some name ->
  109. fun css -> let f = open_out name in output_string f css; close_out f
  110. in
  111. let switch flag fn = if flag then fn else fun x -> x in
  112. let input, css = parse_files args.infiles in
  113. let css = css
  114. (* unfold before pruning duplicates so that shorthand components are
  115. * correctly pruned *)
  116. |> switch args.shorthands Shorthand.unfold_stylesheet
  117. |> switch args.simple Simple.compress
  118. |> switch args.duplicates Duplicates.compress
  119. |> switch args.shorthands Shorthand.compress
  120. in
  121. let output =
  122. if args.whitespace
  123. then Stringify.minify_stylesheet css
  124. else Stringify.string_of_stylesheet css
  125. in
  126. write_output output;
  127. if args.verbose then begin
  128. let il = String.length input in
  129. let ol = String.length output in
  130. Printf.fprintf stderr "compression: %d -> %d bytes (%d%% of original)\n"
  131. il ol (int_of_float (float_of_int ol /. float_of_int il *. 100.))
  132. end
  133. (* Main function, returns exit status *)
  134. let main () =
  135. begin
  136. try
  137. handle_args (parse_args ());
  138. exit 0
  139. with
  140. | Loc_error (loc, msg) ->
  141. Util.prerr_loc_msg loc ("Error: " ^ msg);
  142. | Box_error (box, msg) ->
  143. prerr_endline ("Error: " ^ msg ^ ": " ^ Stringify.string_of_box box);
  144. | Failure msg ->
  145. prerr_endline ("Error: " ^ msg);
  146. | Exit_success ->
  147. exit 0
  148. end;
  149. exit 1
  150. let _ = main ()