-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>>).