···11(* A shell... one day *)
22-22+open Merry.Import
33module C = Merry.Eval.Make (Merry_posix.State) (Merry_posix.Exec)
44module I = Merry.Interactive.Make (Merry_posix.State) (Merry_posix.Exec)
55···88 let ctx =
99 C.
1010 {
1111- state = Merry_posix.State.make ~home:(Sys.getenv "HOME") ();
1111+ state =
1212+ Merry_posix.State.make
1313+ ~home:(Sys.getenv "HOME" ^ "/")
1414+ (Fpath.v (Eunix.cwd ()));
1215 executor;
1316 fs = env#fs;
1417 options = Merry.Eval.Options.default;
+14
src/lib/ast.ml
···738738let of_file path =
739739 let fname = Eio.Path.native_exn path in
740740 Eio.Path.load path |> Morbig.parse_string fname |> of_program
741741+742742+let rec word_component_to_string : word_component -> string = function
743743+ | WordName s -> s
744744+ | WordLiteral s -> s
745745+ | WordDoubleQuoted s ->
746746+ String.concat " " (List.map word_component_to_string s)
747747+ | WordSingleQuoted s ->
748748+ String.concat " " (List.map word_component_to_string s)
749749+ | v ->
750750+ Fmt.failwith "Conversion of %a" Yojson.Safe.pp
751751+ (word_component_to_yojson v)
752752+753753+let word_components_to_string ws =
754754+ String.concat "" (List.map word_component_to_string ws)
+3
src/lib/ast.mli
···19192020(* class map : Ppxlib_traverse_builtins.map *)
21212222+val word_component_to_string : word_component -> string
2323+val word_components_to_string : word_cst -> string
2424+2225module Dump : sig
2326 val pp : t Fmt.t
2427 (** Dump the program *)
+48
src/lib/built_ins.ml
···11+(* Built-in Actions *)
22+type t = Cd of { path : string option } | Pwd
33+44+(* Change Directory *)
55+module Cd = struct
66+ open Cmdliner
77+88+ let path =
99+ let doc = "Directory to change to." in
1010+ Arg.(value & pos 0 (some string) None & info [] ~docv:"DIRECTORY" ~doc)
1111+1212+ let t =
1313+ let make_cd path = Cd { path } in
1414+ let term = Term.(const make_cd $ path) in
1515+ let info =
1616+ let doc = "Change directory, see man cd for more information." in
1717+ Cmd.info "cd" ~doc
1818+ in
1919+ Cmd.v info term
2020+end
2121+2222+(* Print working directory *)
2323+module Pwd = struct
2424+ open Cmdliner
2525+2626+ let t =
2727+ let make_pwd = Pwd in
2828+ let term = Term.(const make_pwd) in
2929+ let info =
3030+ let doc = "Print working directory" in
3131+ Cmd.info "pwd" ~doc
3232+ in
3333+ Cmd.v info term
3434+end
3535+3636+let of_args (w : string list) =
3737+ let open Cmdliner in
3838+ let exec_cmd cmd v =
3939+ let t = Cmd.eval_value ~argv:(Array.of_list cmd) v in
4040+ match t with
4141+ | Ok (`Ok v) -> Some v
4242+ | Ok `Version | Ok `Help | Error _ -> None
4343+ in
4444+4545+ match w with
4646+ | "cd" :: _ as cmd -> exec_cmd cmd Cd.t
4747+ | "pwd" :: _ as cmd -> exec_cmd cmd Pwd.t
4848+ | _ -> None
+7
src/lib/built_ins.mli
···11+type t =
22+ | Cd of { path : string option }
33+ (** Change directory to a path (if [None] then it should be [HOME]) *)
44+ | Pwd
55+66+val of_args : string list -> t option
77+(** Parses a command-line to the built-ins *)
···2121 then redirection is setup, and finally functions/built-ins/commands are
2222 executed. *)
23232424- let rec word_component_to_string : Ast.word_component -> string = function
2525- | WordName s -> s
2626- | WordLiteral s -> s
2727- | WordDoubleQuoted s ->
2828- String.concat " " (List.map word_component_to_string s)
2929- | WordSingleQuoted s ->
3030- String.concat " " (List.map word_component_to_string s)
3131- | v ->
3232- Fmt.failwith "Conversion of %a" Yojson.Safe.pp
3333- (Ast.word_component_to_yojson v)
3434-3535- let word_components_to_string ws =
3636- String.concat "" (List.map word_component_to_string ws)
3737-3824 class default_map =
3925 object (_)
4026 inherit Ast.map
···144130 | Io_op_less ->
145131 (* Simple redirection for input *)
146132 let r =
147147- Eio.Path.open_in ~sw (ctx.fs / word_components_to_string file)
133133+ Eio.Path.open_in ~sw (ctx.fs / Ast.word_components_to_string file)
148134 in
149135 let fd = Eio_unix.Resource.fd_opt r |> Option.get in
150136 Some (Types.Redirect (n, fd, `Blocking))
···166152 let append = v = Io_op_dgreat in
167153 let w =
168154 Eio.Path.open_out ~sw ~append ~create:(`If_missing 0o644)
169169- (ctx.fs / word_components_to_string file)
155155+ (ctx.fs / Ast.word_components_to_string file)
170156 in
171157 let fd = Eio_unix.Resource.fd_opt w |> Option.get in
172158 Some (Types.Redirect (n, fd, `Blocking))
···188174 | Ast.IoRedirect_IoHere _ ->
189175 Fmt.failwith "HERE documents not yet implemented!"
190176191191- let execute_commands ctx local_switch p =
192192- let rec loop (status_of_previous, stdout_of_previous) = function
177177+ let handle_built_in (ctx : ctx) = function
178178+ | Built_ins.Cd { path } ->
179179+ let cwd = S.cwd ctx.state in
180180+ let new_cwd =
181181+ match path with
182182+ | Some p ->
183183+ let fp = Fpath.append cwd (Fpath.v p) in
184184+ Eunix.chdir p;
185185+ fp
186186+ | None -> Fpath.v ""
187187+ in
188188+ let state = S.set_cwd ctx.state new_cwd in
189189+ ({ ctx with state }, Some 0)
190190+ | Pwd ->
191191+ Fmt.pr "%s\n%!" (Eunix.cwd ());
192192+ (ctx, Some 0)
193193+194194+ let cwd_of_ctx ctx = S.cwd ctx.state |> Fpath.to_string |> ( / ) ctx.fs
195195+196196+ let execute_commands initial_ctx local_switch p =
197197+ let rec loop ctx (status_of_previous, stdout_of_previous) :
198198+ Ast.command list -> ctx * int option = function
193199 | Ast.SimpleCommand (Prefixed _) :: next ->
194194- loop (status_of_previous, stdout_of_previous) next
200200+ loop ctx (status_of_previous, stdout_of_previous) next
195201 | Ast.SimpleCommand (Named (executable, None)) :: rest -> (
196196- let some_read, some_write =
197197- stdout_for_pipeline ~sw:local_switch rest
198198- in
199199- match stdout_of_previous with
200200- | None ->
201201- E.exec ctx.executor ?stdout:some_write
202202- (List.map word_component_to_string executable)
203203- |> Option.some
204204- | Some stdout ->
205205- let res =
206206- E.exec ctx.executor ~stdin:stdout ?stdout:some_write
207207- (List.map word_component_to_string executable)
202202+ match
203203+ Built_ins.of_args [ Ast.word_components_to_string executable ]
204204+ with
205205+ | Some bi -> handle_built_in ctx bi
206206+ | None -> (
207207+ let some_read, some_write =
208208+ stdout_for_pipeline ~sw:local_switch rest
208209 in
209209- Option.iter Eio.Flow.close some_write;
210210- loop (Some res, some_read) rest)
210210+ match stdout_of_previous with
211211+ | None ->
212212+ ( ctx,
213213+ E.exec ctx.executor ?stdout:some_write ~cwd:(cwd_of_ctx ctx)
214214+ (List.map Ast.word_component_to_string executable)
215215+ |> Option.some )
216216+ | Some stdout ->
217217+ let res =
218218+ E.exec ctx.executor ~stdin:stdout ?stdout:some_write
219219+ ~cwd:(cwd_of_ctx ctx)
220220+ (List.map Ast.word_component_to_string executable)
221221+ in
222222+ Option.iter Eio.Flow.close some_write;
223223+ loop ctx (Some res, some_read) rest))
211224 | Ast.SimpleCommand (Named (executable, Some suffix)) :: rest -> (
212225 let args =
213226 List.filter_map
214227 (function
215228 | Ast.Suffix_word w ->
216216- Some (List.map word_component_to_string w)
229229+ Some
230230+ (String.concat ""
231231+ @@ List.map Ast.word_component_to_string w)
217232 | Ast.Suffix_redirect _ -> None)
218233 suffix
219234 in
220220- let redirect =
221221- List.fold_left
222222- (fun acc -> function
223223- | Ast.Suffix_word _ -> acc
224224- | Ast.Suffix_redirect rdr ->
225225- handle_one_redirection ~sw:local_switch ctx rdr :: acc)
226226- [] suffix
227227- |> List.rev |> List.filter_map Fun.id
228228- in
229229- let some_read, some_write =
230230- stdout_for_pipeline ~sw:local_switch rest
231231- in
232232- match stdout_of_previous with
233233- | None ->
234234- let res =
235235- E.exec ~fds:redirect ctx.executor ?stdout:some_write
236236- (List.map word_component_to_string executable
237237- @ List.concat args)
238238- |> Option.some
235235+ match
236236+ Built_ins.of_args (Ast.word_components_to_string executable :: args)
237237+ with
238238+ | Some bi -> handle_built_in ctx bi
239239+ | None -> (
240240+ let redirect =
241241+ List.fold_left
242242+ (fun acc -> function
243243+ | Ast.Suffix_word _ -> acc
244244+ | Ast.Suffix_redirect rdr ->
245245+ handle_one_redirection ~sw:local_switch ctx rdr :: acc)
246246+ [] suffix
247247+ |> List.rev |> List.filter_map Fun.id
239248 in
240240- Option.iter Eio.Flow.close some_write;
241241- loop (res, some_read) rest
242242- | Some stdout ->
243243- let res =
244244- E.exec ~fds:redirect ctx.executor ~stdin:stdout
245245- ?stdout:some_write
246246- (List.map word_component_to_string executable
247247- @ List.concat args)
248248- |> Option.some
249249+ let some_read, some_write =
250250+ stdout_for_pipeline ~sw:local_switch rest
249251 in
250250- Option.iter Eio.Flow.close some_write;
251251- loop (res, some_read) rest)
252252+ match stdout_of_previous with
253253+ | None ->
254254+ let res =
255255+ E.exec ~fds:redirect ctx.executor ?stdout:some_write
256256+ ~cwd:(cwd_of_ctx ctx)
257257+ (List.map Ast.word_component_to_string executable @ args)
258258+ |> Option.some
259259+ in
260260+ Option.iter Eio.Flow.close some_write;
261261+ loop ctx (res, some_read) rest
262262+ | Some stdout ->
263263+ let res =
264264+ E.exec ~fds:redirect ctx.executor ~stdin:stdout
265265+ ~cwd:(cwd_of_ctx ctx) ?stdout:some_write
266266+ (List.map Ast.word_component_to_string executable @ args)
267267+ |> Option.some
268268+ in
269269+ Option.iter Eio.Flow.close some_write;
270270+ loop ctx (res, some_read) rest))
252271 | v :: _ ->
253272 Fmt.epr "TODO: %a" Yojson.Safe.pp (Ast.command_to_yojson v);
254273 failwith "Err"
255255- | [] -> status_of_previous
274274+ | [] -> (ctx, status_of_previous)
256275 in
257257- loop (None, None) p
276276+ loop initial_ctx (None, None) p
258277259278 let handle_single_pipeline ~sw ctx c =
260279 let pipeline = function
261280 | Ast.Pipeline p -> (Fun.id, p)
262281 | Ast.Pipeline_Bang p ->
263263- (Option.map (fun i -> if Int.equal i 0 then -1 else 0), p)
282282+ ( (fun (ctx, v) ->
283283+ (ctx, Option.map (fun i -> if Int.equal i 0 then -1 else 0) v)),
284284+ p )
264285 in
265286266287 let rec fold :
267267- Ast.and_or * int option -> Ast.pipeline Ast.and_or_list -> int option =
268268- fun (sep, exit_so_far) pipe ->
288288+ Ast.and_or * (ctx * int option) ->
289289+ Ast.pipeline Ast.and_or_list ->
290290+ ctx * int option =
291291+ fun (sep, (ctx, exit_so_far)) pipe ->
269292 match (sep, pipe) with
270293 | And, Nlist.Singleton (p, _) -> (
271294 match exit_so_far with
272295 | Some 0 ->
273296 let f, p = pipeline p in
274297 f @@ execute_commands ctx sw p
275275- | v -> v)
298298+ | v -> (ctx, v))
276299 | Or, Nlist.Singleton (p, _) -> (
277300 match exit_so_far with
278278- | Some 0 -> Some 0
301301+ | Some 0 -> (ctx, Some 0)
279302 | _ ->
280303 let f, p = pipeline p in
281304 f @@ execute_commands ctx sw p)
···290313 | Some 0 ->
291314 let f, p = pipeline p in
292315 fold (next_sep, f (execute_commands ctx sw p)) rest
293293- | (None | Some _) as v -> v)
316316+ | (None | Some _) as v -> (ctx, v))
294317 | Or, Nlist.Cons ((p, next_sep), rest) -> (
295318 match exit_so_far with
296296- | Some 0 -> fold (next_sep, exit_so_far) rest
319319+ | Some 0 -> fold (next_sep, (ctx, exit_so_far)) rest
297320 | None | Some _ ->
298321 let f, p = pipeline p in
299322 fold (next_sep, f (execute_commands ctx sw p)) rest)
300323 in
301301- fold (Noand_or, None) c
324324+ fold (Noand_or, (ctx, None)) c
302325303303- let exec ctx (ast : Ast.complete_command) =
326326+ let exec initial_ctx (ast : Ast.complete_command) =
304327 let command, _ = ast in
305305- let rec loop : Eio.Switch.t -> Ast.clist -> int option =
306306- fun sw -> function
328328+ let rec loop : Eio.Switch.t -> ctx -> Ast.clist -> ctx * int option =
329329+ fun sw ctx -> function
307330 | Nlist.Singleton (c, _) -> handle_single_pipeline ~sw ctx c
308331 | Nlist.Cons ((c, (Semicolon | Nosep)), cs) -> (
309332 match handle_single_pipeline ~sw ctx c with
310310- | Some 0 -> loop sw cs
333333+ | ctx, Some 0 -> loop sw ctx cs
311334 | v -> v)
312335 | _ -> Fmt.failwith "Background tasks not implemented yet!"
313336 in
314314- Eio.Switch.run @@ fun sw -> (loop sw command, ctx, ast)
337337+ Eio.Switch.run @@ fun sw -> (loop sw initial_ctx command, ast)
315338316339 let apply_pair (a, b) f = f a b
317340 let ( ||> ) = apply_pair
···328351 let ctx, cs =
329352 List.fold_left
330353 (fun (ctx, cs) command ->
331331- let exit_code, ctx, ast =
354354+ let (ctx, exit_code), ast =
332355 expand ctx command ||> redirect ||> execute
333356 in
334357 Option.iter (function 0 -> () | n -> exit n) exit_code;
+10
src/lib/import.ml
···5050 let fold_left f acc (vs : ('a, 'b) t) =
5151 Nlist.fold_left (fun acc (a, b) -> f acc a b) acc vs
5252end
5353+5454+let yojson_pp = Yojson.Safe.pretty_print ~std:true
5555+5656+module Eunix = struct
5757+ let cwd () =
5858+ Eio_unix.run_in_systhread ~label:"cwd" @@ fun () -> Unix.getcwd ()
5959+6060+ let chdir p =
6161+ Eio_unix.run_in_systhread ~label:"chdir" @@ fun () -> Unix.chdir p
6262+end
+2-1
src/lib/interactive.ml
···11module Make (S : Types.State) (E : Types.Exec) = struct
22 module Eval = Eval.Make (S) (E)
3344- let default_prompt _ = Fmt.str "%s >\t%!" (Unix.getcwd ())
44+ let default_prompt s =
55+ Fmt.str "%s > %!" (Fpath.normalize @@ S.cwd s |> Fpath.to_string)
5667 let with_stdin_in_raw_mode fn =
78 let saved_tio = Unix.tcgetattr Unix.stdin in
···1111 (** State for the shell and operating system that is carried from one
1212 evaluation step to the next. *)
13131414+ val cwd : t -> Fpath.t
1515+ (** The current working directory *)
1616+1717+ val set_cwd : t -> Fpath.t -> t
1818+ (** Update the cwd *)
1919+1420 val expand : t -> [ `Tilde ] -> string
1521 (** Expansions *)
1622···3945 ?stdin:_ Eio_unix.source ->
4046 ?stdout:_ Eio_unix.sink ->
4147 ?stderr:_ Eio_unix.sink ->
4848+ cwd:Eio.Fs.dir_ty Eio.Path.t ->
4249 t ->
4350 string list ->
4451 int
+11
test/built_ins.t
···11+Testing some shell built-ins
22+33+1. Cd
44+55+ $ osh -c "cd /usr; pwd"
66+ /usr
77+88+ $ osh -c "mkdir testing; cd testing; ls -a"
99+ .
1010+ ..
1111+