main.ml 5.66 KB
Newer Older
1 2 3
open Lexing
open Types

4
type args = {
5 6 7 8
  infiles    : string list;
  outfile    : string option;
  verbose    : bool;
  whitespace : bool;
9
  simple     : bool;
10 11 12
  shorthands : bool;
  duplicates : bool;
  echo       : bool;
Taddeüs Kroes's avatar
Taddeüs Kroes committed
13
  sort       : bool;
14 15
}

16
let parse_args () =
17 18 19 20 21 22 23 24 25 26
  let usage =
    "Usage: " ^ Sys.argv.(0) ^
    " [<options>] [<file> ...]\n\
     \n\
     Generic options:\n \
     -h, --help        Show this help message\n \
     -v, --verbose     Verbose mode: show compression rate\n \
     -o <file>         Output file (defaults to stdout)\n \
     <file> ...        Input files (default is to read from stdin)\n\
     \n\
27
     Optimization flags (default is -w -c -s -d):\n \
28 29
     -w, --whitespace  Eliminate unnecessary whitespaces (has the greatest \
                       effect, omit for pretty-printing)\n \
30
     -c, --simple      Shorten colors, font weights and nth-child\n \
31 32 33
     -s, --shorthands  Generate shorthand properties\n \
     -d, --duplicates  Prune duplicate properties (WARNING: may affect \
                       cross-browser hacks)\n \
34
     -p, --pretty      Shorthand for -c -s -d\n \
Taddeüs Kroes's avatar
Taddeüs Kroes committed
35
     \n\
36
     Formatting options:\n \
37
     -r, --sort        Sort declarations in each ruleset (always on when \
38
                       --shorthands is enabled)\n \
39
     -e, --echo        Just parse and pretty-print, no optimizations\n\
Taddeüs Kroes's avatar
Taddeüs Kroes committed
40
     "
41
  in
42

43 44 45 46 47
  let default_args = {
    infiles    = [];
    outfile    = None;
    verbose    = false;
    whitespace = false;
48
    simple     = false;
49 50 51
    shorthands = false;
    duplicates = false;
    echo       = false;
Taddeüs Kroes's avatar
Taddeüs Kroes committed
52
    sort       = false;
53
  } in
54

55 56 57 58 59
  let rec handle args = function
    | ("-v" | "--verbose") :: tl ->
      handle {args with verbose = true} tl
    | ("-w" | "--whitespace") :: tl ->
      handle {args with whitespace = true} tl
60 61
    | ("-c" | "--simple") :: tl ->
      handle {args with simple = true} tl
62 63 64 65 66
    | ("-s" | "--shorthands") :: tl ->
      handle {args with shorthands = true} tl
    | ("-d" | "-duplicates") :: tl ->
      handle {args with duplicates = true} tl
    | ("-p" | "--pretty") :: tl ->
67
      handle {args with simple = true; shorthands = true; duplicates = true} tl
68 69
    | ("-e" | "--echo") :: tl ->
      handle {args with echo = true} tl
70
    | ("-r" | "--sort") :: tl ->
Taddeüs Kroes's avatar
Taddeüs Kroes committed
71
      handle {args with sort = true} tl
72 73 74 75 76 77 78 79 80 81 82 83

    | ("-h" | "--help") :: tl ->
      prerr_string usage;
      raise Exit_success

    | ["-o"] ->
      raise (Failure ("missing output file name"))
    | "-o" :: next :: tl when next.[0] = '-' ->
      raise (Failure ("missing output file name"))
    | "-o" :: filename :: tl ->
      handle {args with outfile = Some filename} tl

84 85 86 87 88
    | arg :: tl when String.length arg > 1 && arg.[0] = '-' && arg.[1] <> '-' ->
      let rec handle_opts args = function
        | i when i = String.length arg -> args
        | i -> handle_opts (handle args ["-" ^ String.make 1 arg.[i]]) (i + 1)
      in
Taddeüs Kroes's avatar
Taddeüs Kroes committed
89
      handle (handle_opts args 1) tl
90

91 92 93 94 95 96 97 98
    | arg :: tl when arg.[0] = '-' ->
      prerr_string usage;
      raise (Failure ("unknown option " ^ arg))

    | filename :: tl ->
      handle {args with infiles = args.infiles @ [filename]} tl

    | [] -> args
99 100
  in

101
  match handle default_args (List.tl (Array.to_list Sys.argv)) with
Taddeüs Kroes's avatar
Taddeüs Kroes committed
102 103 104 105 106 107 108
  | { echo = true; _ } as args ->
    { args with
      whitespace = false;
      simple     = false;
      shorthands = false;
      duplicates = false }

109
  | { whitespace = false;
110
      simple     = false;
111 112 113 114 115
      shorthands = false;
      duplicates = false;
      _ } as args ->
    { args with
      whitespace = true;
116
      simple     = true;
117 118
      shorthands = true;
      duplicates = true }
Taddeüs Kroes's avatar
Taddeüs Kroes committed
119

120
  | args -> args
121

122 123 124 125 126 127 128 129
let parse_files = function
  | [] ->
    let input = Util.input_buffered stdin 512 in
    (input, Parse.parse_input "<stdin>" input)
  | files ->
    let rec loop = function
      | [] -> []
      | filename :: tl ->
130 131
        if not (Sys.file_exists filename) then
          raise (Failure ("file " ^ filename ^ " does not exist"));
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
        let input = Util.input_all (open_in filename) in
        let stylesheet = Parse.parse_input filename input in
        (input, stylesheet) :: loop tl
    in
    let inputs, stylesheets = List.split (loop files) in
    (String.concat "" inputs, List.concat stylesheets)

let handle_args args =
  let write_output =
    match args.outfile with
    | None -> print_endline
    | Some name ->
      fun css -> let f = open_out name in output_string f css; close_out f
  in

147
  let switch flag fn = if flag then fn else fun x -> x in
148

149 150
  let input, css = parse_files args.infiles in
  let css = css
151 152 153
    (* unfold before pruning duplicates so that shorthand components are
     * correctly pruned *)
    |> switch args.shorthands Shorthand.unfold_stylesheet
154
    |> switch args.simple Simple.compress
155
    |> switch args.duplicates Duplicates.compress
156
    |> switch args.shorthands Shorthand.compress
Taddeüs Kroes's avatar
Taddeüs Kroes committed
157
    |> switch args.sort Util.sort_stylesheet
158
  in
159 160 161 162 163 164
  let output =
    if args.whitespace
      then Stringify.minify_stylesheet css
      else Stringify.string_of_stylesheet css
  in
  write_output output;
165

166 167 168 169 170 171
  if args.verbose then begin
    let il = String.length input in
    let ol = String.length output in
    Printf.fprintf stderr "compression: %d -> %d bytes (%d%% of original)\n"
    il ol (int_of_float (float_of_int ol /. float_of_int il *. 100.))
  end
172 173

(* Main function, returns exit status *)
174
let main () =
175 176
  begin
    try
177
      handle_args (parse_args ());
178 179
      exit 0
    with
180
    | Loc_error (loc, msg) ->
181
      Util.prerr_loc_msg loc ("Error: " ^ msg);
182 183 184 185
    | Box_error (box, msg) ->
      prerr_endline ("Error: " ^ msg ^ ": " ^ Stringify.string_of_box box);
    | Failure msg ->
      prerr_endline ("Error: " ^ msg);
186 187
    | Exit_success ->
      exit 0
188
  end;
189
  exit 1
190

191
let _ = main ()