Current section
Files
Jump to
Current section
Files
src/shlex.erl
-module(shlex).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/shlex.gleam").
-export([split/1, quote/1, join/1]).
-export_type([lex_error/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 lex_error() :: unclosed_quotation | no_escaped_character.
-file("src/shlex.gleam", 185).
?DOC(" Merge the character buffer into a token and push it onto the accumulator\n").
-spec push_buffer(list(binary()), list(binary())) -> list(binary()).
push_buffer(Buf, Acc) ->
case Buf of
[] ->
[<<""/utf8>> | Acc];
_ ->
[begin
_pipe = Buf,
_pipe@1 = lists:reverse(_pipe),
gleam@string:join(_pipe@1, <<""/utf8>>)
end |
Acc]
end.
-file("src/shlex.gleam", 157).
-spec double_quote(list(binary()), list(binary()), list(binary())) -> {ok,
list(binary())} |
{error, lex_error()}.
double_quote(Input, Acc, Buf) ->
case Input of
[] ->
{error, unclosed_quotation};
[<<"\\"/utf8>>, C | Rest] ->
case C of
<<"\n"/utf8>> ->
double_quote(Rest, Acc, Buf);
<<"$"/utf8>> ->
double_quote(Rest, Acc, [C | Buf]);
<<"`"/utf8>> ->
double_quote(Rest, Acc, [C | Buf]);
<<"\""/utf8>> ->
double_quote(Rest, Acc, [C | Buf]);
<<"\\"/utf8>> ->
double_quote(Rest, Acc, [C | Buf]);
_ ->
double_quote(Rest, Acc, [<<"\\"/utf8, C/binary>> | Buf])
end;
[<<"\""/utf8>> | Rest@1] ->
word(Rest@1, Acc, Buf);
[Hd | Rest@2] ->
double_quote(Rest@2, Acc, [Hd | Buf])
end.
-file("src/shlex.gleam", 141).
-spec single_quote(list(binary()), list(binary()), list(binary())) -> {ok,
list(binary())} |
{error, lex_error()}.
single_quote(Input, Acc, Buf) ->
case Input of
[] ->
{error, unclosed_quotation};
[<<"'"/utf8>> | Rest] ->
word(Rest, Acc, Buf);
[Hd | Rest@1] ->
single_quote(Rest@1, Acc, [Hd | Buf])
end.
-file("src/shlex.gleam", 112).
-spec word(list(binary()), list(binary()), list(binary())) -> {ok,
list(binary())} |
{error, lex_error()}.
word(Input, Acc, Buf) ->
case Input of
[] ->
continue([], push_buffer(Buf, Acc));
[<<"\\"/utf8>>] ->
{error, no_escaped_character};
[<<"\\"/utf8>>, <<"\n"/utf8>> | Rest] ->
word(Rest, Acc, Buf);
[<<"\\"/utf8>>, Next | Rest@1] ->
word(Rest@1, Acc, [Next | Buf]);
[<<"'"/utf8>> | Rest@2] ->
single_quote(Rest@2, Acc, Buf);
[<<"\""/utf8>> | Rest@3] ->
double_quote(Rest@3, Acc, Buf);
[<<" "/utf8>> | Rest@4] ->
continue(Rest@4, push_buffer(Buf, Acc));
[<<"\t"/utf8>> | Rest@4] ->
continue(Rest@4, push_buffer(Buf, Acc));
[<<"\n"/utf8>> | Rest@4] ->
continue(Rest@4, push_buffer(Buf, Acc));
[Hd | Rest@5] ->
word(Rest@5, Acc, [Hd | Buf])
end.
-file("src/shlex.gleam", 101).
-spec comment(list(binary()), list(binary())) -> {ok, list(binary())} |
{error, lex_error()}.
comment(Input, Acc) ->
case Input of
[] ->
continue([], Acc);
[<<"\n"/utf8>> | Rest] ->
continue(Rest, Acc);
[_ | Rest@1] ->
comment(Rest@1, Acc)
end.
-file("src/shlex.gleam", 87).
-spec continue(list(binary()), list(binary())) -> {ok, list(binary())} |
{error, lex_error()}.
continue(Input, Acc) ->
case Input of
[] ->
_pipe = Acc,
_pipe@1 = lists:reverse(_pipe),
{ok, _pipe@1};
[<<" "/utf8>> | Rest] ->
continue(Rest, Acc);
[<<"\t"/utf8>> | Rest] ->
continue(Rest, Acc);
[<<"\n"/utf8>> | Rest] ->
continue(Rest, Acc);
[<<"#"/utf8>> | Rest@1] ->
comment(Rest@1, Acc);
_ ->
word(Input, Acc, [])
end.
-file("src/shlex.gleam", 36).
?DOC(
" Split a shell input into a list of string tokens.\n"
" \n"
" This aims to follow the POSIX standard defined by IEEE Std 1003.1-2024.\n"
" \n"
" ## Examples\n"
" \n"
" ```gleam\n"
" let assert Ok(tokens) = shlex.split(\"git commit -m 'hello world!'\")\n"
" assert tokens == [\"git\", \"commit\", \"-m\", \"hello world!\"]\n"
" ```\n"
).
-spec split(binary()) -> {ok, list(binary())} | {error, lex_error()}.
split(Input) ->
_pipe = Input,
_pipe@1 = gleam@string:to_graphemes(_pipe),
continue(_pipe@1, []).
-file("src/shlex.gleam", 71).
?DOC(
" Escape a string input into a shell word.\n"
" \n"
" __Warning__: The output is safe for POSIX shell parsing, but may be \n"
" vulnerable to shell injection in non-POSIX-compliant shells or interactive \n"
" shells with history expansion, quick substitution, or other parsing \n"
" extensions.\n"
" \n"
" ## Examples\n"
" \n"
" ```gleam\n"
" let unsafe_input = \"foo; cat ~/.ssh/id_rsa\"\n"
" assert shlex.quote(unsafe_input) == \"'foo; cat ~/.ssh/id_rsa'\"\n"
" ```\n"
).
-spec quote(binary()) -> binary().
quote(Input) ->
Safe = begin
_pipe = <<"%+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"/utf8>>,
_pipe@1 = gleam@string:to_graphemes(_pipe),
gleam@set:from_list(_pipe@1)
end,
Unsafe_inputs = begin
_pipe@2 = Input,
_pipe@3 = gleam@string:to_graphemes(_pipe@2),
_pipe@4 = gleam@set:from_list(_pipe@3),
gleam@set:difference(_pipe@4, Safe)
end,
gleam@bool:guard(
gleam@string:is_empty(Input),
<<"''"/utf8>>,
fun() ->
gleam@bool:guard(
gleam@set:is_empty(Unsafe_inputs),
Input,
fun() ->
<<<<"'"/utf8,
(gleam@string:replace(
Input,
<<"'"/utf8>>,
<<"'\"'\"'"/utf8>>
))/binary>>/binary,
"'"/utf8>>
end
)
end
).
-file("src/shlex.gleam", 54).
?DOC(
" Concatenate a list of string inputs into an escaped shell command.\n"
" \n"
" This is a convenience function that applies ``shlex.quote`` to each element\n"
" and joins the result. It is the inverse of ``shlex.split``. \n"
" \n"
" __Warning__: See ``shlex.quote`` for potential vulnerabilities in\n"
" non-POSIX and interactive shells.\n"
" \n"
" ## Examples\n"
" ```gleam\n"
" let unsafe_input = \"foo; cat ~/.ssh/id_rsa\"\n"
" assert shlex.join([\"git\", \"commit\", \"-m\", unsafe_input])\n"
" == \"git commit -m 'foo; cat ~/.ssh/id_rsa'\"\n"
" ```\n"
).
-spec join(list(binary())) -> binary().
join(Input) ->
_pipe = Input,
_pipe@1 = gleam@list:map(_pipe, fun quote/1),
gleam@string:join(_pipe@1, <<" "/utf8>>).