Current section

Files

Jump to
shellout src shellout.erl
Raw

src/shellout.erl

-module(shellout).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([display/1, color/1, background/1, arguments/0, command/4, exit/1, which/1, style/3]).
-export_type([style/0, style_acc/0, command_opt/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type style() :: {name, binary()} | {rgb, list(binary())}.
-type style_acc() :: {style_acc, list(style()), integer()}.
-type command_opt() :: let_be_stderr |
let_be_stdout |
overlapped_stdio |
{set_environment, list({binary(), binary()})}.
-file("src/shellout.gleam", 118).
?DOC(
" Converts a list of `\"display\"` style labels into a\n"
" [`StyleFlags`](#StyleFlags).\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" style(\n"
" \"radical\",\n"
" with: display([\"bold\", \"italic\", \"tubular\"]),\n"
" custom: [],\n"
" )\n"
" // -> \"\\u{1b}[1;3mradical\\u{1b}[0m\\u{1b}[K\"\n"
" ```\n"
).
-spec display(list(binary())) -> gleam@dict:dict(binary(), list(binary())).
display(Values) ->
maps:from_list([{<<"display"/utf8>>, Values}]).
-file("src/shellout.gleam", 136).
?DOC(
" Converts a list of `\"color\"` style labels into a\n"
" [`StyleFlags`](#StyleFlags).\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" style(\n"
" \"uh...\",\n"
" with: color([\"yellow\", \"brightgreen\", \"gnarly\"]),\n"
" custom: [],\n"
" )\n"
" // -> \"\\u{1b}[33;92muh...\\u{1b}[0m\\u{1b}[K\"\n"
" ```\n"
).
-spec color(list(binary())) -> gleam@dict:dict(binary(), list(binary())).
color(Values) ->
maps:from_list([{<<"color"/utf8>>, Values}]).
-file("src/shellout.gleam", 154).
?DOC(
" Converts a list of `\"background\"` style labels into a\n"
" [`StyleFlags`](#StyleFlags).\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" style(\n"
" \"awesome\",\n"
" with: background([\"yellow\", \"brightgreen\", \"bodacious\"]),\n"
" custom: [],\n"
" )\n"
" // -> \"\\u{1b}[43;102mawesome\\u{1b}[0m\\u{1b}[K\"\n"
" ```\n"
).
-spec background(list(binary())) -> gleam@dict:dict(binary(), list(binary())).
background(Values) ->
maps:from_list([{<<"background"/utf8>>, Values}]).
-file("src/shellout.gleam", 213).
-spec escape(binary(), binary()) -> binary().
escape(Code, String) ->
<<<<<<<<"\x{1b}["/utf8, Code/binary>>/binary, "m"/utf8>>/binary,
String/binary>>/binary,
"\x{1b}[0m\x{1b}[K"/utf8>>.
-file("src/shellout.gleam", 323).
?DOC(
" Retrieves a list of strings corresponding to any extra arguments passed when\n"
" invoking a runtime—via `gleam run`, for example.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" // $ gleam run -- pizza --order=5 --anchovies=false\n"
" arguments()\n"
" // -> [\"pizza\", \"--order=5\", \"--anchovies=false\"]\n"
" ```\n"
"\n"
" ```gleam\n"
" // $ gleam run --target=javascript\n"
" arguments()\n"
" // -> []\n"
" ```\n"
).
-spec arguments() -> list(binary()).
arguments() ->
shellout_ffi:start_arguments().
-file("src/shellout.gleam", 415).
?DOC(
" Results in any output captured from the given `executable` on success, or an\n"
" `Error` on failure.\n"
"\n"
" An `Error` result wraps a tuple in which the first element is an OS error\n"
" status code and the second is a message about what went wrong (or an empty\n"
" string).\n"
"\n"
" The `executable` is given `arguments` and run from within the given\n"
" `directory`.\n"
"\n"
" Any number of [`CommandOpt`](#CommandOpt) options can be given to alter the\n"
" behavior of this function.\n"
"\n"
" The standard error stream is by default redirected to the standard output\n"
" stream, and both are captured. When targeting JavaScript, anything captured\n"
" from the standard error stream is appended to anything captured from the\n"
" standard output stream.\n"
"\n"
" The standard input stream is by default handled in\n"
" [raw mode](https://www.wikiwand.com/en/Terminal_mode) when targeting\n"
" JavaScript, allowing full interaction with the spawned process. When\n"
" targeting Erlang, however, it's always handled in\n"
" [cooked mode](https://www.wikiwand.com/en/Terminal_mode).\n"
"\n"
" Note that while `shellout` aims for near feature parity between runtimes,\n"
" some discrepancies exist and are documented herein.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" command(run: \"echo\", with: [\"-n\", \"Cool!\"], in: \".\", opt: [])\n"
" // -> Ok(\"Cool!\")\n"
" ```\n"
"\n"
" ```gleam\n"
" command(run: \"echo\", with: [\"Cool!\"], in: \".\", opt: [LetBeStdout])\n"
" // Cool!\n"
" // -> Ok(\"\")\n"
" ```\n"
"\n"
" ```gleam\n"
" // $ stat -c '%a %U %n' /tmp/dimension_x\n"
" // 700 root /tmp/dimension_x\n"
" command(run: \"ls\", with: [\"dimension_x\"], in: \"/tmp\", opt: [])\n"
" // -> Error(#(2, \"ls: cannot open directory 'dimension_x': Permission denied\\n\"))\n"
" ```\n"
"\n"
" ```gleam\n"
" command(run: \"dimension_x\", with: [], in: \".\", opt: [])\n"
" // -> Error(#(1, \"command `dimension_x` not found\\n\"))\n"
" ```\n"
"\n"
" ```gleam\n"
" // $ ls -p\n"
" // gleam.toml manifest.toml src/ test/\n"
" command(run: \"echo\", with: [], in: \"dimension_x\", opt: [])\n"
" // -> Error(#(2, \"The directory \\\"dimension_x\\\" does not exist\\n\"))\n"
" ```\n"
).
-spec command(binary(), list(binary()), binary(), list(command_opt())) -> {ok,
binary()} |
{error, {integer(), binary()}}.
command(Executable, Arguments, Directory, Options) ->
Environment = gleam@list:flat_map(Options, fun(Option) -> case Option of
{set_environment, Env} ->
Env;
_ ->
[]
end end),
_pipe = Options,
_pipe@1 = gleam@list:map(_pipe, fun(Opt) -> {Opt, true} end),
_pipe@2 = maps:from_list(_pipe@1),
shellout_ffi:os_command(
Executable,
Arguments,
Directory,
_pipe@2,
Environment
).
-file("src/shellout.gleam", 466).
?DOC(
" Halts the runtime and passes the given `status` code to the operating\n"
" system.\n"
"\n"
" A `status` code of `0` typically indicates success, while any other integer\n"
" represents an error.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" // $ gleam run && echo \"Pizza time!\"\n"
" exit(0)\n"
" // Pizza time!\n"
" ```\n"
"\n"
" ```gleam\n"
" // $ gleam run || echo \"Ugh, shell shock ...\"\n"
" exit(1)\n"
" // Ugh, shell shock ...\n"
" ```\n"
).
-spec exit(integer()) -> nil.
exit(Status) ->
shellout_ffi:os_exit(Status).
-file("src/shellout.gleam", 490).
?DOC(
" Results in a path to the given `executable` on success, or an `Error` when\n"
" no such path is found.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" which(\"echo\")\n"
" // -> Ok(\"/sbin/echo\")\n"
" ```\n"
"\n"
" ```gleam\n"
" which(\"./priv/party\")\n"
" // -> Ok(\"./priv/party\")\n"
" ```\n"
"\n"
" ```gleam\n"
" which(\"dimension_x\")\n"
" // -> Error(\"command `dimension_x` not found\")\n"
" ```\n"
).
-spec which(binary()) -> {ok, binary()} | {error, binary()}.
which(Executable) ->
shellout_ffi:os_which(Executable).
-file("src/shellout.gleam", 226).
-spec do_style(list({binary(), list(binary())}), list(binary()), binary()) -> list(binary()).
do_style(Lookup, Strings, Flag) ->
Lookup@1 = begin
_pipe@2 = case Flag of
<<"display"/utf8>> ->
maps:from_list(
[{<<"reset"/utf8>>, [<<"0"/utf8>>]},
{<<"bold"/utf8>>, [<<"1"/utf8>>]},
{<<"dim"/utf8>>, [<<"2"/utf8>>]},
{<<"italic"/utf8>>, [<<"3"/utf8>>]},
{<<"underline"/utf8>>, [<<"4"/utf8>>]},
{<<"blink"/utf8>>, [<<"5"/utf8>>]},
{<<"fastblink"/utf8>>, [<<"6"/utf8>>]},
{<<"reverse"/utf8>>, [<<"7"/utf8>>]},
{<<"hide"/utf8>>, [<<"8"/utf8>>]},
{<<"strike"/utf8>>, [<<"9"/utf8>>]},
{<<"normal"/utf8>>, [<<"22"/utf8>>]},
{<<"noitalic"/utf8>>, [<<"23"/utf8>>]},
{<<"nounderline"/utf8>>, [<<"24"/utf8>>]},
{<<"noblink"/utf8>>, [<<"25"/utf8>>]},
{<<"noreverse"/utf8>>, [<<"27"/utf8>>]},
{<<"nohide"/utf8>>, [<<"28"/utf8>>]},
{<<"nostrike"/utf8>>, [<<"29"/utf8>>]}]
);
<<"color"/utf8>> ->
maps:from_list(
[{<<"black"/utf8>>, [<<"30"/utf8>>]},
{<<"red"/utf8>>, [<<"31"/utf8>>]},
{<<"green"/utf8>>, [<<"32"/utf8>>]},
{<<"yellow"/utf8>>, [<<"33"/utf8>>]},
{<<"blue"/utf8>>, [<<"34"/utf8>>]},
{<<"magenta"/utf8>>, [<<"35"/utf8>>]},
{<<"cyan"/utf8>>, [<<"36"/utf8>>]},
{<<"white"/utf8>>, [<<"37"/utf8>>]},
{<<"default"/utf8>>, [<<"39"/utf8>>]},
{<<"brightblack"/utf8>>, [<<"90"/utf8>>]},
{<<"brightred"/utf8>>, [<<"91"/utf8>>]},
{<<"brightgreen"/utf8>>, [<<"92"/utf8>>]},
{<<"brightyellow"/utf8>>, [<<"93"/utf8>>]},
{<<"brightblue"/utf8>>, [<<"94"/utf8>>]},
{<<"brightmagenta"/utf8>>, [<<"95"/utf8>>]},
{<<"brightcyan"/utf8>>, [<<"96"/utf8>>]},
{<<"brightwhite"/utf8>>, [<<"97"/utf8>>]}]
);
<<"background"/utf8>> ->
_pipe = [{<<"black"/utf8>>, [<<"30"/utf8>>]},
{<<"red"/utf8>>, [<<"31"/utf8>>]},
{<<"green"/utf8>>, [<<"32"/utf8>>]},
{<<"yellow"/utf8>>, [<<"33"/utf8>>]},
{<<"blue"/utf8>>, [<<"34"/utf8>>]},
{<<"magenta"/utf8>>, [<<"35"/utf8>>]},
{<<"cyan"/utf8>>, [<<"36"/utf8>>]},
{<<"white"/utf8>>, [<<"37"/utf8>>]},
{<<"default"/utf8>>, [<<"39"/utf8>>]},
{<<"brightblack"/utf8>>, [<<"90"/utf8>>]},
{<<"brightred"/utf8>>, [<<"91"/utf8>>]},
{<<"brightgreen"/utf8>>, [<<"92"/utf8>>]},
{<<"brightyellow"/utf8>>, [<<"93"/utf8>>]},
{<<"brightblue"/utf8>>, [<<"94"/utf8>>]},
{<<"brightmagenta"/utf8>>, [<<"95"/utf8>>]},
{<<"brightcyan"/utf8>>, [<<"96"/utf8>>]},
{<<"brightwhite"/utf8>>, [<<"97"/utf8>>]}],
_pipe@1 = maps:from_list(_pipe),
gleam@dict:map_values(
_pipe@1,
fun(_, Code) ->
[Code@1] = case Code of
[_] -> Code;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
value => _assert_fail,
module => <<"shellout"/utf8>>,
function => <<"do_style"/utf8>>,
line => 235})
end,
_assert_subject = gleam_stdlib:parse_int(Code@1),
{ok, Code@2} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail@1 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
value => _assert_fail@1,
module => <<"shellout"/utf8>>,
function => <<"do_style"/utf8>>,
line => 236})
end,
[erlang:integer_to_binary(Code@2 + 10)]
end
);
_ ->
erlang:error(#{gleam_error => panic,
message => <<"invalid lookup flag"/utf8>>,
module => <<"shellout"/utf8>>,
function => <<"do_style"/utf8>>,
line => 239})
end,
maps:merge(_pipe@2, maps:from_list(Lookup))
end,
Acc = {style_acc, [], 0},
Acc@2 = begin
_pipe@3 = Strings,
gleam@list:fold(
_pipe@3,
Acc,
fun(Acc@1, Item) -> case gleam_stdlib:parse_int(Item) of
{ok, Int} ->
Item@1 = begin
_pipe@4 = Int,
_pipe@5 = gleam@int:clamp(_pipe@4, 0, 255),
erlang:integer_to_binary(_pipe@5)
end,
Rgb_counter = erlang:element(3, Acc@1),
case Rgb_counter < 3 of
true when Rgb_counter > 0 ->
_assert_subject@1 = erlang:element(2, Acc@1),
[{rgb, Values} | Styles] = case _assert_subject@1 of
[{rgb, _} | _] -> _assert_subject@1;
_assert_fail@2 ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
value => _assert_fail@2,
module => <<"shellout"/utf8>>,
function => <<"do_style"/utf8>>,
line => 256}
)
end,
{style_acc,
[{rgb, [Item@1 | Values]} | Styles],
Rgb_counter + 1};
_ ->
{style_acc,
[{rgb, [Item@1]} | erlang:element(2, Acc@1)],
1}
end;
_ ->
{style_acc,
[{name, Item} | erlang:element(2, Acc@1)],
0}
end end
)
end,
Prepare_rgb = fun(Strings@1) ->
New_strings = begin
_pipe@6 = <<"0"/utf8>>,
_pipe@7 = gleam@list:repeat(_pipe@6, 3 - erlang:length(Strings@1)),
lists:append(Strings@1, _pipe@7)
end,
Code@3 = case Flag of
<<"color"/utf8>> ->
<<"38"/utf8>>;
_ ->
<<"48"/utf8>>
end,
[Code@3, <<"2"/utf8>> | New_strings]
end,
_pipe@8 = erlang:element(2, Acc@2),
_pipe@9 = lists:reverse(_pipe@8),
_pipe@15 = gleam@list:filter_map(_pipe@9, fun(Style) -> case Style of
{name, String} ->
_pipe@10 = Lookup@1,
_pipe@11 = gleam_stdlib:map_get(_pipe@10, String),
gleam@result:map(
_pipe@11,
fun(Strings@2) -> case erlang:length(Strings@2) > 1 of
false ->
Strings@2;
true ->
Prepare_rgb(Strings@2)
end end
);
{rgb, Strings@3} ->
_pipe@12 = Strings@3,
_pipe@13 = lists:reverse(_pipe@12),
_pipe@14 = Prepare_rgb(_pipe@13),
{ok, _pipe@14}
end end),
gleam@list:flatten(_pipe@15).
-file("src/shellout.gleam", 188).
?DOC(
" Applies ANSI styles to a string, resetting styling at the end.\n"
"\n"
" If a style label isn't found within a [`Lookup`](#Lookup) associated with\n"
" the corresponding [`StyleFlags`](#StyleFlags) key's category, that label is\n"
" silently ignored.\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" import gleam/dict\n"
" pub const lookups: Lookups = [\n"
" #(\n"
" [\"color\", \"background\"],\n"
" [\n"
" #(\"buttercup\", [\"252\", \"226\", \"174\"]),\n"
" #(\"mint\", [\"182\", \"255\", \"234\"]),\n"
" #(\"pink\", [\"255\", \"175\", \"243\"]),\n"
" ],\n"
" ),\n"
" ]\n"
" style(\n"
" \"cowabunga\",\n"
" with: display([\"bold\", \"italic\", \"awesome\"])\n"
" |> dict.merge(color([\"pink\", \"righteous\"]))\n"
" |> dict.merge(background([\"brightblack\", \"excellent\"])),\n"
" custom: lookups,\n"
" )\n"
" // -> \"\\u{1b}[1;3;38;2;255;175;243;100mcowabunga\\u{1b}[0m\\u{1b}[K\"\n"
" ```\n"
).
-spec style(
binary(),
gleam@dict:dict(binary(), list(binary())),
list({list(binary()), list({binary(), list(binary())})})
) -> binary().
style(String, Flags, Lookups) ->
_pipe = [<<"display"/utf8>>, <<"color"/utf8>>, <<"background"/utf8>>],
_pipe@7 = gleam@list:map(
_pipe,
fun(Flag) ->
gleam@result:'try'(
gleam_stdlib:map_get(Flags, Flag),
fun(Strings) -> _pipe@1 = Lookups,
_pipe@4 = gleam@list:filter_map(
_pipe@1,
fun(Item) ->
{Keys, Lookup} = Item,
_pipe@2 = Keys,
_pipe@3 = gleam@list:find(
_pipe@2,
fun(Key) -> Key =:= Flag end
),
gleam@result:map(_pipe@3, fun(_) -> Lookup end)
end
),
_pipe@5 = gleam@list:flatten(_pipe@4),
_pipe@6 = do_style(_pipe@5, Strings, Flag),
{ok, _pipe@6} end
)
end
),
_pipe@8 = gleam@result:values(_pipe@7),
_pipe@9 = gleam@list:flatten(_pipe@8),
_pipe@10 = gleam@string:join(_pipe@9, <<";"/utf8>>),
escape(_pipe@10, String).