Shells in OCaml
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

More async support

Still very experimental, and hidden behind a [set -o async] flag.

+149 -16
+35
src/lib/ast.ml
··· 752 752 753 753 let word_components_to_string ws = 754 754 String.concat "" (List.map word_component_to_string ws) 755 + 756 + class check_ast = 757 + object (_) 758 + inherit [bool] Sast.fold 759 + method int _ ctx = ctx 760 + method string _ ctx = ctx 761 + method char _ ctx = ctx 762 + method option f v ctx = Option.fold ~none:ctx ~some:(fun i -> f i ctx) v 763 + method nlist__t f v ctx = Nlist.fold_left (fun acc i -> f i acc) ctx v 764 + 765 + method nslist__t f g v ctx = 766 + Nslist.fold_left (fun acc a b -> f a acc |> g b) ctx v 767 + 768 + method list f v ctx = List.fold_left (fun acc i -> f i acc) ctx v 769 + end 770 + 771 + let has_async ast = 772 + let o = 773 + object 774 + inherit check_ast as super 775 + 776 + method! complete_command v ctx = 777 + match v with 778 + | _, Some Ampersand -> true 779 + | _ -> super#complete_command v ctx 780 + 781 + method! separator_list f l ctx = 782 + let v = 783 + Nlist.to_list l 784 + |> List.exists (function _, Ampersand -> true | _ -> false) 785 + in 786 + if v then v else super#separator_list f l ctx 787 + end 788 + in 789 + o#complete_command ast false
+4
src/lib/ast.mli
··· 22 22 val word_component_to_string : word_component -> string 23 23 val word_components_to_string : word_cst -> string 24 24 25 + val has_async : complete_command -> bool 26 + (** Checks, recursively, the command to see if there is any use of the async 27 + operator [&] *) 28 + 25 29 module Dump : sig 26 30 val pp : t Fmt.t 27 31 (** Dump the program *)
+38 -11
src/lib/built_ins.ml
··· 1 1 module Options = struct 2 - type t = { noclobber : bool; pipefail : bool } 2 + type t = { noclobber : bool; pipefail : bool; async : bool } 3 3 4 - let default = { noclobber = false; pipefail = false } 4 + let default = { noclobber = false; pipefail = false; async = false } 5 5 6 - let with_options ?noclobber ?pipefail t = 6 + let with_options ?noclobber ?pipefail ?async t = 7 7 { 8 8 noclobber = Option.value ~default:t.noclobber noclobber; 9 9 pipefail = Option.value ~default:t.pipefail pipefail; 10 + async = Option.value ~default:t.async async; 10 11 } 11 12 12 - type option = Noclobber | Pipefail 13 + type posix = [ `Noclobber | `Pipefail ] 14 + type merry = [ `Async ] 15 + type option = [ posix | merry ] 13 16 14 17 let update t options = 15 18 List.fold_left 16 19 (fun d -> function 17 - | Pipefail -> with_options ~pipefail:true d 18 - | Noclobber -> with_options ~noclobber:true d) 20 + | `Pipefail -> with_options ~pipefail:true d 21 + | `Noclobber -> with_options ~noclobber:true d 22 + | `Async -> with_options ~async:true d) 19 23 t options 20 24 21 25 let pp ppf opt = ··· 23 27 Fmt.pf ppf "%-12s %s@." name (if value then "on" else "off") 24 28 in 25 29 let opts = 26 - let { noclobber; pipefail } = opt in 27 - [ ("pipefail", pipefail); ("noclobber", noclobber) ] 30 + let { noclobber; pipefail; async } = opt in 31 + [ ("pipefail", pipefail); ("noclobber", noclobber); ("async", async) ] 28 32 in 29 33 Fmt.pf ppf "@[<v>%a@]" Fmt.(list pp_option) opts 30 34 end ··· 32 36 type set = { update : Options.option list; print_options : bool } 33 37 34 38 (* Built-in Actions *) 35 - type t = Cd of { path : string option } | Pwd | Exit of int | Set of set 39 + type t = 40 + | Cd of { path : string option } 41 + | Pwd 42 + | Exit of int 43 + | Set of set 44 + | Wait of int 36 45 37 46 (* Change Directory *) 38 47 module Cd = struct ··· 86 95 87 96 module Set = struct 88 97 open Cmdliner 89 - open Options 90 98 91 - let enum_map = [ ("pipefail", Pipefail); ("noclobber", Noclobber) ] 99 + let enum_map = 100 + [ ("pipefail", `Pipefail); ("noclobber", `Noclobber); ("async", `Async) ] 92 101 93 102 let option = 94 103 let doc = "Options." in ··· 104 113 Cmd.v info term 105 114 end 106 115 116 + module Wait = struct 117 + open Cmdliner 118 + 119 + let pid = 120 + let doc = "Process ID." in 121 + Arg.(value & pos 0 int 0 & info [] ~docv:"PID" ~doc) 122 + 123 + let t = 124 + let make_wait n = Wait n in 125 + let term = Term.(const make_wait $ pid) in 126 + let info = 127 + let doc = "Wait for a particular PID (default is 0)" in 128 + Cmd.info "wait" ~doc 129 + in 130 + Cmd.v info term 131 + end 132 + 107 133 let of_args (w : string list) = 108 134 let open Cmdliner in 109 135 let exec_cmd cmd v = ··· 119 145 | "pwd" :: _ as cmd -> exec_cmd cmd Pwd.t 120 146 | "exit" :: _ as cmd -> exec_cmd cmd Exit.t 121 147 | "set" :: _ as cmd -> exec_cmd cmd Set.t 148 + | "wait" :: _ as cmd -> exec_cmd cmd Wait.t 122 149 | _ -> None
+6 -3
src/lib/built_ins.mli
··· 1 1 module Options : sig 2 - type t = { noclobber : bool; pipefail : bool } 3 - type option = Noclobber | Pipefail 2 + type t = { noclobber : bool; pipefail : bool; async : bool } 3 + type posix = [ `Noclobber | `Pipefail ] 4 + type merry = [ `Async ] 5 + type option = [ posix | merry ] 4 6 5 7 val default : t 6 - val with_options : ?noclobber:bool -> ?pipefail:bool -> t -> t 8 + val with_options : ?noclobber:bool -> ?pipefail:bool -> ?async:bool -> t -> t 7 9 val update : t -> option list -> t 8 10 val pp : t Fmt.t 9 11 end ··· 16 18 | Pwd 17 19 | Exit of int 18 20 | Set of set 21 + | Wait of int 19 22 20 23 val of_args : string list -> (t, string) result option 21 24 (** Parses a command-line to the built-ins, errors are returned if parsing. *)
+16 -2
src/lib/eval.ml
··· 193 193 in 194 194 if print_options then Fmt.pr "%a%!" Built_ins.Options.pp ctx.options; 195 195 v 196 + | Wait i -> ( 197 + match Unix.waitpid [] i with 198 + | _, WEXITED 0 -> Exit.zero ctx 199 + | _, (WEXITED n | WSIGNALED n | WSTOPPED n) -> Exit.nonzero ctx n) 196 200 197 201 let cwd_of_ctx ctx = S.cwd ctx.state |> Fpath.to_string |> ( / ) ctx.fs 198 202 ··· 486 490 | Ast.ForClause fc -> handle_for_clause ctx fc 487 491 | Ast.IfClause if_ -> handle_if_clause ctx if_ 488 492 | _ as c -> 489 - Fmt.failwith "Compound command not supported: %a" yojson_pp 490 - (Ast.compound_command_to_yojson c) 493 + Fmt.epr "Compound command not supported: %a\n%!" yojson_pp 494 + (Ast.compound_command_to_yojson c); 495 + exit 127 491 496 492 497 and needs_subshelling = function 493 498 | [] -> false ··· 608 613 | [] -> (ctx, cs) 609 614 | command :: commands -> ( 610 615 let ctx = Exit.value ctx in 616 + (* For our sanity *) 617 + let has_async = Ast.has_async command in 618 + if has_async && not ctx.options.async then begin 619 + Fmt.epr 620 + "You are using asynchronous operators and [set -o async] has \ 621 + not been called.\n\ 622 + %!"; 623 + exit 1 624 + end; 611 625 let exit = execute ctx command in 612 626 match exit with 613 627 | Exit.Nonzero { exit_code; message; should_exit; _ } -> (
+23
test/async.t
··· 1 + Asynchronous jobs are tricky. For now, we disable them behind a flag in [set]. 2 + 3 + $ osh -c "echo hello &" 4 + You are using asynchronous operators and [set -o async] has not been called. 5 + [1] 6 + 7 + But we do have some support for them. 8 + 9 + $ cat > osh.sh << EOF 10 + > set -o async 11 + > sleep 10000 & echo hello 12 + > kill -9 \$! 13 + > EOF 14 + 15 + $ cat > test.sh << EOF 16 + > sleep 10000 & echo hello 17 + > kill -9 \$! 18 + > EOF 19 + 20 + $ sh test.sh 21 + hello 22 + $ osh osh.sh 23 + hello
+27
test/built_ins.t
··· 17 17 18 18 $ osh -c "exit" 19 19 exit 20 + 21 + 2. Wait 22 + 23 + $ cat > test_bad.sh << EOF 24 + > ( 25 + > sleep 1 && 26 + > echo hello > hello.txt 27 + > ) & 28 + > cat hello.txt 29 + > EOF 30 + 31 + $ sh test_bad.sh 32 + cat: hello.txt: No such file or directory 33 + [1] 34 + 35 + $ cat > test_good.sh << EOF 36 + > ( 37 + > sleep 1 && 38 + > echo hello > hello.txt 39 + > ) & 40 + > wait $! 41 + > cat hello.txt 42 + > EOF 43 + 44 + $ sh test_good.sh 45 + hello 46 +