@@ -3,34 +3,29 @@ open Grain;
33open Compile ;
44open Grain_parsing ;
55open Grain_utils ;
6- open Filename ;
6+ open Grain_utils . Filepath . Args ;
7+
8+ [@ deriving cmdliner]
9+ type io_params = {
10+ /** Grain source file or directory of source files to format */
11+ [@pos 0] [@docv "FILE"]
12+ input: ExistingFileOrDirectory . t ,
13+ /** Output file or directory */
14+ [@name "o"] [@docv "FILE"]
15+ output: option (MaybeExistingFileOrDirectory . t ),
16+ };
717
818let get_program_string = filename => {
9- switch (filename) {
10- | None =>
11- let source_buffer = Buffer . create(1024 );
12- set_binary_mode_in(stdin, true );
13- /* read from stdin until we get end of buffer */
14- try (
15- while (true ) {
16- let c = input_char(stdin);
17- Buffer . add_char(source_buffer, c);
18- }
19- ) {
20- | exn => ()
21- };
22- Buffer . contents(source_buffer);
23- | Some (filename ) =>
24- let ic = open_in_bin(filename);
25- let n = in_channel_length(ic);
26- let source_buffer = Buffer . create(n);
27- Buffer . add_channel(source_buffer, ic, n);
28- close_in(ic);
29- Buffer . contents(source_buffer);
30- };
19+ let ic = open_in_bin(filename);
20+ let n = in_channel_length(ic);
21+ let source_buffer = Buffer . create(n);
22+ Buffer . add_channel(source_buffer, ic, n);
23+ close_in(ic);
24+ Buffer . contents(source_buffer);
3125};
3226
33- let compile_parsed = (filename: option (string )) => {
27+ let compile_parsed = filename => {
28+ let filename = Filepath . to_string(filename);
3429 switch (
3530 {
3631 let program_str = get_program_string(filename);
@@ -42,7 +37,7 @@ let compile_parsed = (filename: option(string)) => {
4237 Compile . compile_string(
4338 ~is_root_file= true ,
4439 ~hook= stop_after_parse,
45- ~name=? filename,
40+ ~name= filename,
4641 program_str,
4742 );
4843
@@ -59,110 +54,123 @@ let compile_parsed = (filename: option(string)) => {
5954 Grain_parsing . Location . report_exception(Stdlib . Format . err_formatter, exn);
6055 Option . iter(
6156 s =>
62- if (Grain_utils . Config . debug^ ) {
57+ if (Config . debug^ ) {
6358 prerr_string("Backtrace:\n " );
6459 prerr_string(s);
6560 prerr_string("\n " );
6661 },
6762 bt,
6863 );
6964 exit(2 );
70- | ({cstate_desc: Parsed (parsed_program )}, lines , eol ) =>
71- ` Ok ((parsed_program, Array . of_list(lines), eol))
72- | _ => ` Error ((false , "Invalid compilation state" ))
65+ | ({cstate_desc: Parsed (parsed_program )}, lines , eol ) => (
66+ parsed_program,
67+ Array . of_list(lines),
68+ eol,
69+ )
70+ | _ => failwith ("Invalid compilation state" )
7371 };
7472};
7573
7674let format_code =
7775 (
7876 ~eol,
79- srcfile: option (string ),
77+ ~output=?,
78+ ~original_source: array (string ),
8079 program: Parsetree . parsed_program ,
81- outfile,
82- original_source: array (string ),
83- format_in_place: bool ,
8480 ) => {
8581 let formatted_code = Format . format_ast(~original_source, ~eol, program);
8682
87- // return the file to its format
88-
8983 let buf = Buffer . create(0 );
9084 Buffer . add_string(buf, formatted_code);
9185
9286 let contents = Buffer . to_bytes(buf);
93- switch (outfile ) {
87+ switch (output ) {
9488 | Some (outfile ) =>
89+ let outfile = Filepath . to_string(outfile);
90+ // TODO: This crashes if you do something weird like `-o stdout/map.gr/foo`
91+ // because `foo` doesn't exist so it tries to mkdir it and raises
92+ Fs_access . ensure_parent_directory_exists(outfile);
9593 let oc = Fs_access . open_file_for_writing(outfile);
9694 output_bytes(oc, contents);
9795 close_out(oc);
9896 | None =>
99- switch (srcfile, format_in_place) {
100- | (Some (src ), true ) =>
101- let oc = Fs_access . open_file_for_writing(src);
102- output_bytes(oc, contents);
103- close_out(oc);
104- | _ =>
105- set_binary_mode_out(stdout, true );
106- print_bytes(contents);
107- }
97+ set_binary_mode_out(stdout, true );
98+ print_bytes(contents);
10899 };
109-
110- ` Ok () ;
111100};
112101
113- let grainformat =
114- (
115- srcfile: option (string ),
116- outfile,
117- format_in_place: bool ,
118- (program, lines: array (string ), eol),
119- ) =>
120- try (format_code(~eol, srcfile, program, outfile, lines, format_in_place)) {
121- | e => ` Error ((false , Printexc . to_string(e)))
122- };
123-
124- let input_file_conv = {
125- open Arg ;
126- let (prsr , prntr ) = non_dir_file;
127- (filename => prsr(filename), prntr);
128- };
129-
130- /** Converter which checks that the given output filename is valid */
131- let output_file_conv = {
132- let parse = s => {
133- let s_dir = dirname(s);
134- Sys . file_exists(s_dir)
135- ? if (Sys . is_directory(s_dir)) {
136- ` Ok (s);
137- } else {
138- ` Error (Stdlib . Format . sprintf("`% s ' is not a directory" , s_dir));
139- }
140- : ` Error (Stdlib . Format . sprintf("no `% s ' directory" , s_dir));
141- };
142- (parse, Stdlib . Format . pp_print_string);
102+ type run = {
103+ input_path: Fp . t (Fp . absolute ),
104+ output_path: option (Fp . t (Fp . absolute )),
143105};
144106
145- let output_filename = {
146- let doc = "Output filename" ;
147- let docv = "FILE" ;
148- Arg . (
149- value & opt(some(output_file_conv), None ) & info([ "o" ] , ~docv, ~doc)
107+ let enumerate_directory = (input_dir_path, output_dir_path) => {
108+ let all_files = Array . to_list(Fs_access . readdir(input_dir_path));
109+ let grain_files =
110+ List . filter(
111+ filepath => Filename . extension(Fp . toString(filepath)) == ".gr" ,
112+ all_files,
113+ );
114+ List . map(
115+ filepath => {
116+ // We relativize between the input directory and the full filepath
117+ // such that we can reconstruct the directory structure of the input directory
118+ let relative_path =
119+ Fp . relativizeExn(~source= input_dir_path, ~dest= filepath);
120+ let gr_basename = Option . get(Fp . baseName(relative_path));
121+ let dirname = Fp . dirName(relative_path);
122+ let md_relative_path = Fp . join(dirname, Fp . relativeExn(gr_basename));
123+ let output_path = Fp . join(output_dir_path, md_relative_path);
124+ {input_path: filepath, output_path: Some (output_path)};
125+ },
126+ grain_files,
150127 );
151128};
152129
153- let format_in_place = {
154- let doc = "Format in place" ;
155- let docv = "" ;
156- Arg . (value & flag & info([ "in-place" ] , ~docv, ~doc));
157- };
130+ let enumerate_runs = opts =>
131+ switch (opts. input, opts. output) {
132+ | (File (input_file_path ), None ) =>
133+ ` Ok ([ {input_path: input_file_path, output_path: None }] )
134+ | (File (input_file_path ), Some (Exists (File (output_file_path )))) =>
135+ ` Ok ([
136+ {input_path: input_file_path, output_path: Some (output_file_path)},
137+ ] )
138+ | (File (input_file_path ), Some (NotExists (output_file_path ))) =>
139+ ` Ok ([
140+ {input_path: input_file_path, output_path: Some (output_file_path)},
141+ ] )
142+ | (Directory (_ ), None ) =>
143+ ` Error ((
144+ false ,
145+ "Directory input must be used with `-o` flag to specify output directory" ,
146+ ))
147+ | (Directory (input_dir_path ), Some (Exists (Directory (output_dir_path )))) =>
148+ ` Ok (enumerate_directory(input_dir_path, output_dir_path))
149+ | (Directory (input_dir_path ), Some (NotExists (output_dir_path ))) =>
150+ ` Ok (enumerate_directory(input_dir_path, output_dir_path))
151+ | (File (input_file_path ), Some (Exists (Directory (output_dir_path )))) =>
152+ ` Error ((
153+ false ,
154+ "Using a file as input cannot be combined with directory output" ,
155+ ))
156+ | (Directory (_ ), Some (Exists (File (_ )))) =>
157+ ` Error ((
158+ false ,
159+ "Using a directory as input cannot be written as a single file output" ,
160+ ))
161+ };
158162
159- let input_filename = {
160- let doc = "Grain source file to format" ;
161- let docv = "FILE" ;
162- Arg . (
163- value
164- & pos(~rev= true , 0 , some(~none= "" , input_file_conv), None )
165- & info([] , ~docv, ~doc)
163+ let grainformat = runs => {
164+ List . iter(
165+ ({input_path, output_path}) => {
166+ let (program , original_source , eol ) = compile_parsed(input_path);
167+ try (format_code(~eol, ~output=? output_path, ~original_source, program)) {
168+ | exn =>
169+ Stdlib . Format . eprintf("@ [% s @ ] @ . " , Printexc . to_string(exn));
170+ exit(2 );
171+ };
172+ },
173+ runs,
166174 );
167175};
168176
@@ -178,18 +186,8 @@ let cmd = {
178186
179187 Cmd . v(
180188 Cmd . info(Sys . argv[ 0 ] , ~version, ~doc),
181- Term . (
182- ret(
183- const(grainformat)
184- $ input_filename
185- $ output_filename
186- $ format_in_place
187- $ ret(
188- Grain_utils . Config . with_cli_options(compile_parsed)
189- $ input_filename,
190- ),
191- )
192- ),
189+ Config . with_cli_options(grainformat)
190+ $ ret(const(enumerate_runs) $ io_params_cmdliner_term() ),
193191 );
194192};
195193
0 commit comments