let rec to_shell: type a. _ -> a t -> string =
fun params e ->
let continue e = to_shell params e in
let seq =
function
| [] -> ":"
| l -> String.concat ~sep:params.statement_separator l in
let die s =
match params.die_command with
| Some f -> f s
| None ->
ksprintf failwith
"Die command not set: you cannot use the `fail` construct together with the `~no_trap:true` option (error message was: %S)" s
in
let expand_octal s =
sprintf
{sh| printf -- "$(printf -- '%%s' %s | sed -e 's/\(.\{3\}\)/\\\1/g')" |sh}
s in
let to_argument varprefix =
let argument ?declaration ?variable_name argument =
object
method declaration = declaration
method export = Option.map ~f:(sprintf "export %s ; ") declaration
method variable_name = variable_name
method argument = argument
end in
function
| `String (Literal (Literal.String s)) when Literal.String.easy_to_escape s ->
argument (Filename.quote s)
| `String (Literal (Literal.String s)) when
Literal.String.impossible_to_escape_for_variable s ->
ksprintf failwith "to_shell: sorry literal %S is impossible to escape as `exec` argument" s
| `String v ->
let variable_name = Unique_name.variable varprefix in
let declaration =
sprintf "%s=$(%s; printf 'x')"
variable_name (continue v |> expand_octal)
in
argument ~variable_name ~declaration
(sprintf "\"${%s%%?}\"" variable_name)
| `Int (Literal (Literal.Int s)) -> argument (Int.to_string s)
| `Int other ->
let variable_name = Unique_name.variable varprefix in
let declaration = sprintf "%s=%s" variable_name (continue other) in
argument ~variable_name ~declaration
(sprintf "\"${%s%%?}\"" variable_name)
in
match e with
| Exec l ->
let variables = ref [] in
let args =
List.mapi l ~f:(fun index v ->
let varname = sprintf "argument_%d" index in
let arg = to_argument varname (`String v) in
match arg#declaration with
| None -> arg#argument
| Some vardef ->
variables := sprintf "%s ; " vardef :: !variables;
arg#argument) in
(List.rev !variables) @ args
|> String.concat ~sep:" "
|> sprintf " { %s ; } "
| Raw_cmd s -> s
| Returns {expr; value} ->
sprintf " { %s ; [ $? -eq %d ] ; }" (continue expr) value
| Bool_operator (a, op, b) ->
sprintf "{ %s %s %s ; }"
(continue a)
(match op with `And -> "&&" | `Or -> "||")
(continue b)
| String_operator (a, op, b) ->
sprintf "[ \"%s\" %s \"%s\" ]"
(continue a)
(match op with `Eq -> "=" | `Neq -> "!=")
(continue b)
| No_op -> ":"
| If (c, t, e) ->
seq [
sprintf "if { %s ; }" (continue c);
sprintf "then %s" (continue t);
sprintf "else %s" (continue e);
"fi";
]
| While {condition; body} ->
seq [
sprintf "while { %s ; }" (continue condition);
sprintf "do %s" (continue body);
"done"
]
| Seq l -> seq (List.map l ~f:continue)
| Not t ->
sprintf "! { %s ; }" (continue t)
| Redirect_output (unit_t, redirections) ->
let make_redirection { take; redirect_to } =
let takearg = to_argument "redirection_take" (`Int take) in
let retoarg =
to_argument "redirection_to"
(match redirect_to with `Fd i -> `Int i | `Path p -> `String p) in
let variables =
takearg#export :: retoarg#export :: [] |> List.filter_opt in
let exec =
sprintf "\"exec %%s>%s%%s\" %s %s"
(match redirect_to with `Fd _ -> "&" | `Path _ -> "")
takearg#argument
retoarg#argument
in
sprintf "%s eval \"$(printf -- %s)\" || { echo 'Exec %s failed' >&2 ; } "
(String.concat variables ~sep:"")
exec
exec
in
begin match redirections with
| [] -> continue unit_t
| one :: more ->
continue (Seq (
Raw_cmd (sprintf "( %s" (make_redirection one))
::
List.map more ~f:(fun r -> Raw_cmd (make_redirection r))
@ [unit_t]
@ [ Raw_cmd ")" ]
))
end
| Write_output { expr; stdout; stderr; return_value } ->
let ret_arg =
Option.map return_value ~f:(fun v -> to_argument "retval" (`String v))
in
let var =
Option.((ret_arg >>= fun ra -> ra#export) |> value ~default:"") in
let with_potential_return =
sprintf "%s { %s %s ; }" var (continue expr)
(Option.value_map ret_arg ~default:"" ~f:(fun r ->
sprintf "; printf -- \"$?\" > %s" r#argument))
in
let redirections =
let make fd =
Option.map
~f:(fun p -> {take = Construct.int fd; redirect_to = `Path p}) in
[make 1 stdout; make 2 stderr] |> List.filter_opt in
continue (Redirect_output (Raw_cmd with_potential_return, redirections))
| Literal lit ->
Literal.to_shell lit
| Output_as_string e ->
sprintf "\"$( { %s ; } | od -t o1 -An -v | tr -d ' \\n' )\"" (continue e)
| Int_to_string i ->
continue (Output_as_string (Raw_cmd (sprintf "printf -- '%%d' %s" (continue i))))
| String_to_int s ->
let var = Unique_name.variable "string_to_int" in
let value = sprintf "\"$%s\"" var in
sprintf " $( %s=$( %s ) ; if [ %s -eq %s ] ; then printf -- %s ; else %s ; fi ; ) "
var
(continue s |> expand_octal)
value value value
(die (sprintf "String_to_int: error, $%s is not an integer" var))
| Bool_to_string b ->
continue (Output_as_string (Raw_cmd (sprintf "printf -- '%s'"
(continue b))))
| String_to_bool s ->
continue (
If (
(String_operator (s, `Eq, Literal (Literal.String "true"))),
(Raw_cmd "true"),
If (
(String_operator (s, `Eq, Literal (Literal.String "false"))),
(Raw_cmd "false"),
(Fail))
)
)
| List l ->
let output o = sprintf "printf -- 'G%%s' \"%s\"" (continue o) in
let outputs = List.map l ~f:output in
let rec build =
function
| [] -> []
| [one] -> [one]
| one :: two :: t ->
one :: "printf -- '\\n'" :: build (two :: t)
in
(seq (build outputs))
| List_to_string (l, f) ->
continue (Output_as_string (Raw_cmd (continue l)))
| String_to_list (s, f) ->
continue s |> expand_octal |> sprintf "printf -- '%%s' \"$(%s)\""
| String_concat sl ->
let outputing_list = continue sl in
sprintf "$( { %s ; } | tr -d 'G\\n' )" outputing_list
| List_append (la, lb) ->
seq (continue la :: "printf -- '\\n'" :: continue lb :: [])
| List_iter (l, f) ->
let variter = Unique_name.variable "list_iter_var" in
let varlist = Unique_name.variable "list_iter_list" in
let outputing_list = continue l in
seq [
sprintf "export %s=\"$(%s)\" " varlist outputing_list;
sprintf "for %s in ${%s} " variter varlist;
"do : ";
continue (f (fun () ->
Raw_cmd (sprintf "${%s#G}" variter)));
"done";
]
| Int_bin_op (ia, op, ib) ->
sprintf "$(( %s %s %s ))"
(continue ia)
begin match op with
| `Div -> "/"
| `Minus -> "-"
| `Mult -> "*"
| `Plus -> "+"
| `Mod -> "%"
end
(continue ib)
| Int_bin_comparison (ia, op, ib) ->
sprintf "[ %s %s %s ]"
(continue ia)
begin match op with
| `Eq -> "-eq"
| `Ge -> "-ge"
| `Gt -> "-gt"
| `Le -> "-le"
| `Lt -> "-lt"
| `Ne -> "-ne"
end
(continue ib)
| Feed (string, e) ->
sprintf {sh| %s | %s |sh}
(continue string |> expand_octal) (continue e)
| Pipe [] -> ":"
| Pipe l ->
sprintf " %s "
(List.map l ~f:continue |> String.concat ~sep:" | ")
| Getenv s ->
let var = Unique_name.variable "getenv" in
let value = sprintf "\"$%s\"" var in
let cmd_outputs_value =
sprintf "{ %s=$(printf \\\"\\${%%s}\\\" $(%s | tr -d '\\n')) ; eval \"printf -- '%%s' %s\" ; } "
var (continue s |> expand_octal) value in
continue (Output_as_string (Raw_cmd cmd_outputs_value))
| Setenv (variable, value) ->
sprintf "export $(%s)=\"$(%s)\""
(continue variable |> expand_octal)
(continue value |> expand_octal)
| With_signal {signal_name; catch; run} ->
let var = Unique_name.variable "with_signal" in
let value = sprintf "\"$%s\"" var in
continue Construct.(seq [
Raw_cmd (sprintf "export %s=$$" var);
exec ["trap"; continue catch; signal_name];
exec [
"sh"; "-c";
run (Raw_cmd (sprintf " kill -s %s %s ; kill $$ " signal_name value))
|> continue
];
])
| Fail -> die "EDSL.fail called"