main.ml 5.6 KB

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