main.ml 5.7 KB

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