A structured-data shell in Gleam, inspired by Nushell
0

Configure Feed

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

gleshell / src / gleshell_ffi.erl
4.2 kB 126 lines
1%% Erlang FFI for gleshell: REPL I/O, cwd, env, external processes. 2-module(gleshell_ffi). 3-export([ 4 get_line/1, 5 set_cwd/1, 6 get_cwd/0, 7 getenv/1, 8 setenv/2, 9 run_cmd/2, 10 which/1, 11 home_dir/0 12]). 13 14-spec get_line(binary()) -> {ok, binary()} | {error, binary()}. 15get_line(Prompt) when is_binary(Prompt) -> 16 %% OTP may return a charlist or a UTF-8 binary depending on the 17 %% standard_io encoding / binary options — accept both. 18 case io:get_line(unicode:characters_to_list(Prompt)) of 19 eof -> 20 {error, <<"eof">>}; 21 {error, _} -> 22 {error, <<"io_error">>}; 23 Line when is_list(Line); is_binary(Line) -> 24 Bin = unicode:characters_to_binary(Line), 25 Stripped = string:trim(Bin, trailing, [$\n, $\r]), 26 {ok, Stripped} 27 end. 28 29-spec set_cwd(binary()) -> {ok, nil} | {error, binary()}. 30set_cwd(Path) when is_binary(Path) -> 31 case file:set_cwd(unicode:characters_to_list(Path)) of 32 ok -> 33 {ok, nil}; 34 {error, Reason} -> 35 {error, reason_to_bin(Reason)} 36 end. 37 38-spec get_cwd() -> {ok, binary()} | {error, binary()}. 39get_cwd() -> 40 case file:get_cwd() of 41 {ok, Dir} -> 42 {ok, unicode:characters_to_binary(Dir)}; 43 {error, Reason} -> 44 {error, reason_to_bin(Reason)} 45 end. 46 47-spec getenv(binary()) -> {ok, binary()} | {error, nil}. 48getenv(Name) when is_binary(Name) -> 49 case os:getenv(unicode:characters_to_list(Name)) of 50 false -> 51 {error, nil}; 52 Value -> 53 {ok, unicode:characters_to_binary(Value)} 54 end. 55 56-spec setenv(binary(), binary()) -> {ok, nil}. 57setenv(Name, Value) when is_binary(Name), is_binary(Value) -> 58 os:putenv(unicode:characters_to_list(Name), unicode:characters_to_list(Value)), 59 {ok, nil}. 60 61-spec which(binary()) -> {ok, binary()} | {error, nil}. 62which(Command) when is_binary(Command) -> 63 case os:find_executable(unicode:characters_to_list(Command)) of 64 false -> 65 {error, nil}; 66 Path -> 67 {ok, unicode:characters_to_binary(Path)} 68 end. 69 70-spec home_dir() -> {ok, binary()} | {error, binary()}. 71home_dir() -> 72 case os:getenv("HOME") of 73 false -> 74 {error, <<"HOME not set">>}; 75 Home -> 76 {ok, unicode:characters_to_binary(Home)} 77 end. 78 79%% Run an executable with args; capture stdout+stderr and exit status. 80%% Returns {ok, {Status :: integer(), Output :: binary()}} | {error, binary()}. 81-spec run_cmd(binary(), [binary()]) -> {ok, {integer(), binary()}} | {error, binary()}. 82run_cmd(Command, Args) when is_binary(Command), is_list(Args) -> 83 case os:find_executable(unicode:characters_to_list(Command)) of 84 false -> 85 {error, <<"command not found: ", Command/binary>>}; 86 Path -> 87 PortArgs = [unicode:characters_to_list(A) || A <- Args], 88 try 89 Port = open_port( 90 {spawn_executable, Path}, 91 [ 92 binary, 93 exit_status, 94 stderr_to_stdout, 95 use_stdio, 96 stream, 97 {args, PortArgs} 98 ] 99 ), 100 collect_output(Port, <<>>) 101 catch 102 _:Reason -> 103 {error, reason_to_bin(Reason)} 104 end 105 end. 106 107collect_output(Port, Acc) -> 108 receive 109 {Port, {data, Data}} when is_binary(Data) -> 110 collect_output(Port, <<Acc/binary, Data/binary>>); 111 {Port, {data, Data}} when is_list(Data) -> 112 Bin = unicode:characters_to_binary(Data), 113 collect_output(Port, <<Acc/binary, Bin/binary>>); 114 {Port, {exit_status, Status}} -> 115 {ok, {Status, Acc}} 116 after 120_000 -> 117 catch port_close(Port), 118 {error, <<"command timed out after 120s">>} 119 end. 120 121reason_to_bin(Reason) when is_atom(Reason) -> 122 atom_to_binary(Reason, utf8); 123reason_to_bin(Reason) when is_binary(Reason) -> 124 Reason; 125reason_to_bin(Reason) -> 126 iolist_to_binary(io_lib:format("~p", [Reason])).