Add a new toplevel directive #use_output "<command>"

Signed-off-by: Jeremie Dimino <jeremie@dimino.org>
Co-authored-by: David Allsopp <david.allsopp@metastack.com>
master
Jérémie Dimino 2020-03-16 17:48:41 +00:00 committed by GitHub
parent 58f8284aca
commit e54876a869
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 157 additions and 72 deletions

View File

@ -143,6 +143,10 @@ Working version
points to the grammar.
(Andreas Abel, review by Xavier Leroy)
- #9283: add a new toplevel directive `#use_output "<command>"` to
run a command and evaluate its output.
(Jérémie Dimino, review by David Allsopp)
### Manual and documentation:
- #9141: beginning of the ocamltest reference manual

View File

@ -198,6 +198,10 @@ will result in an ``unbound value "quit"'' error.
they were typed on standard input. The reading of the file stops at
the first error encountered.
\item["#use_output \""\var{command}"\";;"]
Execute a command and evaluate its output as if it had been captured
to a file and passed to "#use".
\item["#mod_use \""\var{file-name}"\";;"]
Similar to "#use" but also wrap the code into a top-level module of the
same name as capitalized file name without extensions, following

View File

@ -0,0 +1,25 @@
(* TEST
* expect
*)
(* Test a success case *)
#use_output {|echo let x = 42|}
[%%expect {|
val x : int = 42
|}];;
(* When the command fails *)
#use_output {|false|}
[%%expect {|
Command exited with code 1.
|}];;
(* When the code is invalid *)
#use_output {|echo 1 :: x|}
[%%expect {|
File "(command-output)", line 1, characters 5-6:
1 | 1 :: x
^
Error: This expression has type int but an expression was expected of type
int list
|}];;

View File

@ -115,8 +115,11 @@ let _ = Hashtbl.add directive_table "load" (Directive_string (dir_load std_out))
(* Load commands from a file *)
let dir_use ppf name = ignore(Opttoploop.use_file ppf name)
let dir_use_output ppf name = ignore(Opttoploop.use_output ppf name)
let _ = Hashtbl.add directive_table "use" (Directive_string (dir_use std_out))
let _ = Hashtbl.add directive_table "use_output"
(Directive_string (dir_use_output std_out))
(* Install, remove a printer *)

View File

@ -23,6 +23,7 @@ val dir_remove_directory : string -> unit
val dir_cd : string -> unit
val dir_load : formatter -> string -> unit
val dir_use : formatter -> string -> unit
val dir_use_output : formatter -> string -> unit
val dir_install_printer : formatter -> Longident.t -> unit
val dir_remove_printer : formatter -> Longident.t -> unit

View File

@ -449,40 +449,60 @@ let preprocess_phrase ppf phr =
if !Clflags.dump_source then Pprintast.top_phrase ppf phr;
phr
let use_file ppf wrap_mod name =
try
let (filename, ic, must_close) =
if name = "" then
("(stdin)", stdin, false)
else begin
let filename = Load_path.find name in
let ic = open_in_bin filename in
(filename, ic, true)
end
in
let lb = Lexing.from_channel ic in
Location.init lb filename;
(* Skip initial #! line if any *)
Lexer.skip_hash_bang lb;
let success =
protect_refs [ R (Location.input_name, filename) ] (fun () ->
try
List.iter
(fun ph ->
let ph = preprocess_phrase ppf ph in
if not (execute_phrase !use_print_results ppf ph) then raise Exit)
(if wrap_mod then
parse_mod_use_file name lb
else
!parse_use_file lb);
true
with
| Exit -> false
| Sys.Break -> fprintf ppf "Interrupted.@."; false
| x -> Location.report_exception ppf x; false) in
if must_close then close_in ic;
success
with Not_found -> fprintf ppf "Cannot find file %s.@." name; false
let use_channel ppf wrap_mod ic name filename =
let lb = Lexing.from_channel ic in
Location.init lb filename;
(* Skip initial #! line if any *)
Lexer.skip_hash_bang lb;
let success =
protect_refs [ R (Location.input_name, filename) ] (fun () ->
try
List.iter
(fun ph ->
let ph = preprocess_phrase ppf ph in
if not (execute_phrase !use_print_results ppf ph) then raise Exit)
(if wrap_mod then
parse_mod_use_file name lb
else
!parse_use_file lb);
true
with
| Exit -> false
| Sys.Break -> fprintf ppf "Interrupted.@."; false
| x -> Location.report_exception ppf x; false) in
success
let use_output ppf command =
let fn = Filename.temp_file "ocaml" "_toploop.ml" in
Misc.try_finally ~always:(fun () ->
try Sys.remove fn with Sys_error _ -> ())
(fun () ->
match
Printf.ksprintf Sys.command "%s > %s"
command
(Filename.quote fn)
with
| 0 ->
let ic = open_in_bin fn in
Misc.try_finally ~always:(fun () -> close_in ic)
(fun () -> use_channel ppf false ic "" "(command-output)")
| n ->
fprintf ppf "Command exited with code %d.@." n;
false)
let use_file ppf wrap_mode name =
match name with
| "" ->
use_channel ppf wrap_mode stdin name "(stdin)"
| _ ->
match Load_path.find name with
| filename ->
let ic = open_in_bin filename in
Misc.try_finally ~always:(fun () -> close_in ic)
(fun () -> use_channel ppf false ic name filename)
| exception Not_found ->
fprintf ppf "Cannot find file %s.@." name;
false
let mod_use_file ppf name = use_file ppf true name
let use_file ppf name = use_file ppf false name

View File

@ -55,6 +55,7 @@ val preprocess_phrase :
(* Preprocess the given toplevel phrase using regular and ppx
preprocessors. Return the updated phrase. *)
val use_file : formatter -> string -> bool
val use_output : formatter -> string -> bool
val use_silently : formatter -> string -> bool
val mod_use_file : formatter -> string -> bool
(* Read and execute commands from a file.

View File

@ -240,6 +240,7 @@ let load_file = load_file false
(* Load commands from a file *)
let dir_use ppf name = ignore(Toploop.use_file ppf name)
let dir_use_output ppf name = ignore(Toploop.use_output ppf name)
let dir_mod_use ppf name = ignore(Toploop.mod_use_file ppf name)
let _ = add_directive "use" (Directive_string (dir_use std_out))
@ -248,6 +249,13 @@ let _ = add_directive "use" (Directive_string (dir_use std_out))
doc = "Read, compile and execute source phrases from the given file.";
}
let _ = add_directive "use_output" (Directive_string (dir_use_output std_out))
{
section = section_run;
doc = "Execute a command and read, compile and execute source phrases \
from its output.";
}
let _ = add_directive "mod_use" (Directive_string (dir_mod_use std_out))
{
section = section_run;
@ -255,7 +263,6 @@ let _ = add_directive "mod_use" (Directive_string (dir_mod_use std_out))
wraps the contents in a module.";
}
(* Install, remove a printer *)
let filter_arrow ty =

View File

@ -23,6 +23,7 @@ val dir_remove_directory : string -> unit
val dir_cd : string -> unit
val dir_load : formatter -> string -> unit
val dir_use : formatter -> string -> unit
val dir_use_output : formatter -> string -> unit
val dir_install_printer : formatter -> Longident.t -> unit
val dir_remove_printer : formatter -> Longident.t -> unit
val dir_trace : formatter -> Longident.t -> unit

View File

@ -394,43 +394,61 @@ let preprocess_phrase ppf phr =
if !Clflags.dump_source then Pprintast.top_phrase ppf phr;
phr
let use_file ppf wrap_mod name =
try
let (filename, ic, must_close) =
if name = "" then
("(stdin)", stdin, false)
else begin
let filename = Load_path.find name in
let ic = open_in_bin filename in
(filename, ic, true)
end
in
let lb = Lexing.from_channel ic in
Warnings.reset_fatal ();
Location.init lb filename;
(* Skip initial #! line if any *)
Lexer.skip_hash_bang lb;
let success =
protect_refs [ R (Location.input_name, filename);
R (Location.input_lexbuf, Some lb); ]
(fun () ->
try
List.iter
(fun ph ->
let ph = preprocess_phrase ppf ph in
if not (execute_phrase !use_print_results ppf ph) then raise Exit)
(if wrap_mod then
parse_mod_use_file name lb
else
!parse_use_file lb);
true
with
| Exit -> false
| Sys.Break -> fprintf ppf "Interrupted.@."; false
| x -> Location.report_exception ppf x; false) in
if must_close then close_in ic;
success
with Not_found -> fprintf ppf "Cannot find file %s.@." name; false
let use_channel ppf wrap_mod ic name filename =
let lb = Lexing.from_channel ic in
Warnings.reset_fatal ();
Location.init lb filename;
(* Skip initial #! line if any *)
Lexer.skip_hash_bang lb;
protect_refs [ R (Location.input_name, filename);
R (Location.input_lexbuf, Some lb); ]
(fun () ->
try
List.iter
(fun ph ->
let ph = preprocess_phrase ppf ph in
if not (execute_phrase !use_print_results ppf ph) then raise Exit)
(if wrap_mod then
parse_mod_use_file name lb
else
!parse_use_file lb);
true
with
| Exit -> false
| Sys.Break -> fprintf ppf "Interrupted.@."; false
| x -> Location.report_exception ppf x; false)
let use_output ppf command =
let fn = Filename.temp_file "ocaml" "_toploop.ml" in
Misc.try_finally ~always:(fun () ->
try Sys.remove fn with Sys_error _ -> ())
(fun () ->
match
Printf.ksprintf Sys.command "%s > %s"
command
(Filename.quote fn)
with
| 0 ->
let ic = open_in_bin fn in
Misc.try_finally ~always:(fun () -> close_in ic)
(fun () -> use_channel ppf false ic "" "(command-output)")
| n ->
fprintf ppf "Command exited with code %d.@." n;
false)
let use_file ppf wrap_mode name =
match name with
| "" ->
use_channel ppf wrap_mode stdin name "(stdin)"
| _ ->
match Load_path.find name with
| filename ->
let ic = open_in_bin filename in
Misc.try_finally ~always:(fun () -> close_in ic)
(fun () -> use_channel ppf false ic name filename)
| exception Not_found ->
fprintf ppf "Cannot find file %s.@." name;
false
let mod_use_file ppf name = use_file ppf true name
let use_file ppf name = use_file ppf false name

View File

@ -75,6 +75,7 @@ val preprocess_phrase :
(* Preprocess the given toplevel phrase using regular and ppx
preprocessors. Return the updated phrase. *)
val use_file : formatter -> string -> bool
val use_output : formatter -> string -> bool
val use_silently : formatter -> string -> bool
val mod_use_file : formatter -> string -> bool
(* Read and execute commands from a file.