···11+module Options = struct
22+ type t = { noclobber : bool; pipefail : bool }
33+44+ let default = { noclobber = false; pipefail = false }
55+66+ let with_options ?noclobber ?pipefail t =
77+ {
88+ noclobber = Option.value ~default:t.noclobber noclobber;
99+ pipefail = Option.value ~default:t.pipefail pipefail;
1010+ }
1111+1212+ type option = Noclobber | Pipefail
1313+1414+ let update t options =
1515+ List.fold_left
1616+ (fun d -> function
1717+ | Pipefail -> with_options ~pipefail:true d
1818+ | Noclobber -> with_options ~noclobber:true d)
1919+ t options
2020+2121+ let pp ppf opt =
2222+ let pp_option ppf (name, value) =
2323+ Fmt.pf ppf "%-12s %s@." name (if value then "on" else "off")
2424+ in
2525+ let opts =
2626+ let { noclobber; pipefail } = opt in
2727+ [ ("pipefail", pipefail); ("noclobber", noclobber) ]
2828+ in
2929+ Fmt.pf ppf "@[<v>%a@]" Fmt.(list pp_option) opts
3030+end
3131+3232+type set = { update : Options.option list; print_options : bool }
3333+134(* Built-in Actions *)
22-type t = Cd of { path : string option } | Pwd | Exit of int
3535+type t = Cd of { path : string option } | Pwd | Exit of int | Set of set
336437(* Change Directory *)
538module Cd = struct
···5184 Cmd.v info term
5285end
53868787+module Set = struct
8888+ open Cmdliner
8989+ open Options
9090+9191+ let enum_map = [ ("pipefail", Pipefail); ("noclobber", Noclobber) ]
9292+9393+ let option =
9494+ let doc = "Options." in
9595+ Arg.(value & opt_all (enum enum_map) [] & info [ "o" ] ~docv:"OPTION" ~doc)
9696+9797+ let t =
9898+ let make_set update = Set { update; print_options = false } in
9999+ let term = Term.(const make_set $ option) in
100100+ let info =
101101+ let doc = "Set or unset options and positional parameters." in
102102+ Cmd.info "set" ~doc
103103+ in
104104+ Cmd.v info term
105105+end
106106+54107let of_args (w : string list) =
55108 let open Cmdliner in
56109 let exec_cmd cmd v =
57110 let t = Cmd.eval_value ~argv:(Array.of_list cmd) v in
58111 match t with
5959- | Ok (`Ok v) -> Some v
6060- | Ok `Version | Ok `Help | Error _ -> None
112112+ | Ok (`Ok v) -> Some (Ok v)
113113+ | Ok `Version | Ok `Help | Error `Parse -> Some (Error "parsing")
114114+ | Error _ -> None
61115 in
6211663117 match w with
64118 | "cd" :: _ as cmd -> exec_cmd cmd Cd.t
65119 | "pwd" :: _ as cmd -> exec_cmd cmd Pwd.t
66120 | "exit" :: _ as cmd -> exec_cmd cmd Exit.t
121121+ | "set" :: _ as cmd -> exec_cmd cmd Set.t
67122 | _ -> None
+15-2
src/lib/built_ins.mli
···11+module Options : sig
22+ type t = { noclobber : bool; pipefail : bool }
33+ type option = Noclobber | Pipefail
44+55+ val default : t
66+ val with_options : ?noclobber:bool -> ?pipefail:bool -> t -> t
77+ val update : t -> option list -> t
88+ val pp : t Fmt.t
99+end
1010+1111+type set = { update : Options.option list; print_options : bool }
1212+113type t =
214 | Cd of { path : string option }
315 (** Change directory to a path (if [None] then it should be [HOME]) *)
416 | Pwd
517 | Exit of int
1818+ | Set of set
61977-val of_args : string list -> t option
88-(** Parses a command-line to the built-ins *)
2020+val of_args : string list -> (t, string) result option
2121+(** Parses a command-line to the built-ins, errors are returned if parsing. *)
+18-17
src/lib/eval.ml
···55open Import
66open Exit.Syntax
7788-module Options = struct
99- type t = { noclobber : bool; pipefail : bool }
1010-1111- let default = { noclobber = false; pipefail = false }
1212-1313- let with_options ?noclobber ?pipefail t =
1414- {
1515- noclobber = Option.value ~default:t.noclobber noclobber;
1616- pipefail = Option.value ~default:t.pipefail pipefail;
1717- }
1818-end
1919-208(** An evaluator over the AST *)
219module Make (S : Types.State) (E : Types.Exec) = struct
2210 (* What follows uses the POSIX definition of what a shell does ($ 2.1).
···4533 local_state : (string * string) list;
4634 executor : E.t;
4735 fs : Eio.Fs.dir_ty Eio.Path.t;
4848- options : Options.t;
3636+ options : Built_ins.Options.t;
4937 stdout : Eio_unix.sink_ty Eio.Flow.sink option;
5038 }
5139···192180 { Exit.default_should_exit with interactive = `Yes }
193181 in
194182 Exit.nonzero_msg ~should_exit ctx ~exit_code:n "exit"
183183+ | Set { update; print_options } ->
184184+ let v =
185185+ Exit.zero
186186+ { ctx with options = Built_ins.Options.update ctx.options update }
187187+ in
188188+ if print_options then Fmt.pr "%a%!" Built_ins.Options.pp ctx.options;
189189+ v
195190196191 let cwd_of_ctx ctx = S.cwd ctx.state |> Fpath.to_string |> ( / ) ctx.fs
197192···236231 Built_ins.of_args
237232 [ handle_word_components_to_string ctx executable ]
238233 with
239239- | Some bi ->
234234+ | Some (Ok bi) ->
240235 let ctx = handle_built_in ctx bi in
241236 let built_in = ctx >|= fun _ -> () in
242242- (Exit.value ctx, handle_job ~pgid job (`Built_in built_in))
237237+ let job = handle_job ~pgid job (`Built_in built_in) in
238238+ loop (Exit.value ctx) job (pgid, stdout_of_previous) rest
239239+ | Some (Error _) ->
240240+ (ctx, handle_job ~pgid job (`Built_in (Exit.nonzero () 1)))
243241 | None -> (
244242 let some_read, some_write =
245243 stdout_for_pipeline ctx ~sw:pipeline_switch rest
···285283 Built_ins.of_args
286284 (handle_word_components_to_string ctx executable :: args)
287285 with
288288- | Some bi ->
286286+ | Some (Ok bi) ->
289287 let ctx = handle_built_in ctx bi in
290288 let built_in = ctx >|= fun _ -> () in
291291- (Exit.value ctx, handle_job ~pgid job (`Built_in built_in))
289289+ let job = handle_job ~pgid job (`Built_in built_in) in
290290+ loop (Exit.value ctx) job (pgid, stdout_of_previous) rest
291291+ | Some (Error _) ->
292292+ (ctx, handle_job ~pgid job (`Built_in (Exit.nonzero () 1)))
292293 | None -> (
293294 let redirect =
294295 List.fold_left