-module(glint). -compile(no_auto_import). -export([add_command_from_stub/2, new/0, with_config/2, with_global_flags/2, with_pretty_help/2, add_command/5, execute/2, run/2, help_flag/0]). -export_type([glint/1, config/0, command_input/0, contents/1, command/1, stub/1, out/1]). -opaque glint(FIW) :: {glint, config(), command(FIW), gleam@map:map_(binary(), glint@flag:contents())}. -type config() :: {config, gleam@option:option(glint@style:pretty_help())}. -type command_input() :: {command_input, list(binary()), gleam@map:map_(binary(), glint@flag:contents())}. -opaque contents(FIY) :: {contents, fun((command_input()) -> FIY), gleam@map:map_(binary(), glint@flag:contents()), binary()}. -opaque command(FIZ) :: {command, gleam@option:option(contents(FIZ)), gleam@map:map_(binary(), command(FIZ))}. -type stub(FJA) :: {stub, list(binary()), fun((command_input()) -> FJA), list({binary(), glint@flag:contents()}), binary()}. -type out(FJB) :: {out, FJB} | {help, binary()}. -spec add_command_from_stub(glint(FKB), stub(FKB)) -> glint(FKB). add_command_from_stub(Glint, Stub) -> add_command( Glint, erlang:element(2, Stub), erlang:element(3, Stub), erlang:element(4, Stub), erlang:element(5, Stub) ). -spec new() -> glint(any()). new() -> {glint, {config, none}, empty_command(), gleam@map:new()}. -spec with_config(glint(FKH), config()) -> glint(FKH). with_config(Glint, Config) -> erlang:setelement(2, Glint, Config). -spec with_global_flags(glint(FKK), list({binary(), glint@flag:contents()})) -> glint(FKK). with_global_flags(Glint, Flags) -> erlang:setelement( 4, Glint, gleam@list:fold( Flags, erlang:element(4, Glint), fun(M, F) -> gleam@map:insert(M, erlang:element(1, F), erlang:element(2, F)) end ) ). -spec empty_command() -> command(any()). empty_command() -> {command, none, gleam@map:new()}. -spec with_pretty_help(glint(FKQ), glint@style:pretty_help()) -> glint(FKQ). with_pretty_help(Glint, Pretty) -> erlang:setelement(2, Glint, {config, {some, Pretty}}). -spec sanitize_path(list(binary())) -> list(binary()). sanitize_path(Path) -> _pipe = Path, _pipe@1 = gleam@list:map(_pipe, fun gleam@string:trim/1), gleam@list:filter(_pipe@1, fun is_not_empty/1). -spec add_command( glint(FKV), list(binary()), fun((command_input()) -> FKV), list({binary(), glint@flag:contents()}), binary() ) -> glint(FKV). add_command(Glint, Path, F, Flags, Description) -> erlang:setelement( 3, Glint, begin _pipe = Path, _pipe@1 = sanitize_path(_pipe), do_add_command( erlang:element(3, Glint), _pipe@1, {contents, F, glint@flag:build_map(Flags), Description} ) end ). -spec do_add_command(command(FLB), list(binary()), contents(FLB)) -> command(FLB). do_add_command(Root, Path, Contents) -> case Path of [] -> erlang:setelement(2, Root, {some, Contents}); [X | Xs] -> Update_subcommand = fun(Node) -> _pipe = Node, _pipe@1 = gleam@option:lazy_unwrap(_pipe, fun empty_command/0), do_add_command(_pipe@1, Xs, Contents) end, erlang:setelement( 3, Root, gleam@map:update(erlang:element(3, Root), X, Update_subcommand) ) end. -spec execute_root( command(FLG), gleam@map:map_(binary(), glint@flag:contents()), list(binary()), list(binary()) ) -> {ok, out(FLG)} | {error, snag:snag()}. execute_root(Cmd, Global_flags, Args, Flag_inputs) -> _pipe@5 = case erlang:element(2, Cmd) of {some, Contents} -> case begin _pipe = Flag_inputs, gleam@list:try_fold( _pipe, gleam@map:merge(Global_flags, erlang:element(3, Contents)), fun glint@flag:update_flags/2 ) end of {error, _try} -> {error, _try}; {ok, New_flags} -> _pipe@1 = Args, _pipe@2 = {command_input, _pipe@1, New_flags}, _pipe@3 = (erlang:element(2, Contents))(_pipe@2), _pipe@4 = {out, _pipe@3}, {ok, _pipe@4} end; none -> snag:error(<<"command not found"/utf8>>) end, snag:context(_pipe@5, <<"failed to run command"/utf8>>). -spec execute(glint(FLL), list(binary())) -> {ok, out(FLL)} | {error, snag:snag()}. execute(Glint, Args) -> Help_flag = help_flag(), {Help, Args@2} = case gleam@list:pop(Args, fun(S) -> S =:= Help_flag end) of {ok, {_@1, Args@1}} -> {true, Args@1}; _@2 -> {false, Args} end, {Flags, Args@3} = gleam@list:partition( Args@2, fun(_capture) -> gleam@string:starts_with(_capture, <<"--"/utf8>>) end ), do_execute(Glint, Args@3, Flags, Help, []). -spec do_execute( glint(FLP), list(binary()), list(binary()), boolean(), list(binary()) ) -> {ok, out(FLP)} | {error, snag:snag()}. do_execute(Glint, Args, Flags, Help, Command_path) -> case Args of [] when Help -> _pipe = Command_path, _pipe@1 = cmd_help(_pipe, Glint), _pipe@2 = {help, _pipe@1}, {ok, _pipe@2}; [] -> execute_root( erlang:element(3, Glint), erlang:element(4, Glint), [], Flags ); [Arg | Rest] -> case gleam@map:get(erlang:element(3, erlang:element(3, Glint)), Arg) of {ok, Cmd} -> do_execute( erlang:setelement(3, Glint, Cmd), Rest, Flags, Help, [Arg | Command_path] ); _@1 when Help -> _pipe@3 = Command_path, _pipe@4 = cmd_help(_pipe@3, Glint), _pipe@5 = {help, _pipe@4}, {ok, _pipe@5}; _@2 -> execute_root( erlang:element(3, Glint), erlang:element(4, Glint), Args, Flags ) end end. -spec run(glint(any()), list(binary())) -> nil. run(Glint, Args) -> case execute(Glint, Args) of {error, Err} -> _pipe = Err, _pipe@1 = snag:pretty_print(_pipe), gleam@io:println(_pipe@1); {ok, {help, Help}} -> gleam@io:println(Help); _@1 -> nil end. -spec is_not_empty(binary()) -> boolean(). is_not_empty(S) -> S /= <<""/utf8>>. -spec help_flag() -> binary(). help_flag() -> gleam@string:append(<<"--"/utf8>>, <<"help"/utf8>>). -spec style_heading( gleam@option:option(list({list(binary()), list({binary(), list(binary())})})), binary(), binary() ) -> binary(). style_heading(Lookups, Heading, Colour) -> _pipe = Lookups, _pipe@1 = gleam@option:map( _pipe, fun(_capture) -> glint@style:heading(_capture, Heading, Colour) end ), _pipe@2 = gleam@option:unwrap(_pipe@1, Heading), gleam@string:append(_pipe@2, <<"\n\t"/utf8>>). -spec flags_help( gleam@map:map_(binary(), glint@flag:contents()), gleam@option:option(list({list(binary()), list({binary(), list(binary())})})) ) -> binary(). flags_help(Flags, Styling) -> _pipe = Flags, _pipe@1 = glint@flag:flags_help(_pipe), _pipe@2 = append_if_msg_not_empty(<<"\n\t"/utf8>>, _pipe@1), _pipe@3 = gleam@string:append( <<"--help\t\t\tPrint help information"/utf8>>, _pipe@2 ), gleam@string:append( style_heading(Styling, <<"FLAGS:"/utf8>>, <<"flags"/utf8>>), _pipe@3 ). -spec wrap_with_space(binary()) -> binary(). wrap_with_space(S) -> case S of <<""/utf8>> -> <<" "/utf8>>; _@1 -> gleam@string:concat([<<" "/utf8>>, S, <<" "/utf8>>]) end. -spec usage_help( binary(), gleam@map:map_(binary(), glint@flag:contents()), gleam@option:option(list({list(binary()), list({binary(), list(binary())})})) ) -> binary(). usage_help(Name, Flags, Styling) -> Flags@1 = begin _pipe = Flags, _pipe@1 = gleam@map:to_list(_pipe), _pipe@2 = gleam@list:map(_pipe@1, fun glint@flag:flag_type_help/1), gleam@list:sort(_pipe@2, fun gleam@string:compare/2) end, Flag_sb = case Flags@1 of [] -> gleam@string_builder:new(); _@1 -> _pipe@3 = Flags@1, _pipe@4 = gleam@list:intersperse(_pipe@3, <<" "/utf8>>), _pipe@5 = gleam@string_builder:from_strings(_pipe@4), _pipe@6 = gleam@string_builder:prepend(_pipe@5, <<" [ "/utf8>>), gleam@string_builder:append(_pipe@6, <<" ]"/utf8>>) end, _pipe@7 = [<<"gleam run"/utf8>>, wrap_with_space(Name), <<"[ ARGS ]"/utf8>>], _pipe@8 = gleam@string_builder:from_strings(_pipe@7), _pipe@9 = gleam@string_builder:append_builder(_pipe@8, Flag_sb), _pipe@10 = gleam@string_builder:prepend( _pipe@9, style_heading(Styling, <<"USAGE:"/utf8>>, <<"usage"/utf8>>) ), gleam@string_builder:to_string(_pipe@10). -spec cmd_help(list(binary()), glint(any())) -> binary(). cmd_help(Path, Glint) -> Styling = gleam@option:map( erlang:element(2, erlang:element(2, Glint)), fun glint@style:lookups/1 ), Name = begin _pipe = Path, _pipe@1 = gleam@list:reverse(_pipe), gleam@string:join(_pipe@1, <<" "/utf8>>) end, {Flags@1, Description, Usage} = case erlang:element( 2, erlang:element(3, Glint) ) of none -> {<<""/utf8>>, <<""/utf8>>, <<""/utf8>>}; {some, Contents} -> Flags = gleam@map:merge( erlang:element(4, Glint), erlang:element(3, Contents) ), {flags_help(Flags, Styling), erlang:element(4, Contents), usage_help(Name, Flags, Styling)} end, Header_items = begin _pipe@2 = [Name, Description], _pipe@3 = gleam@list:filter(_pipe@2, fun is_not_empty/1), gleam@string:join(_pipe@3, <<"\n"/utf8>>) end, Subcommands = begin _pipe@4 = erlang:element(3, erlang:element(3, Glint)), _pipe@5 = subcommands_help(_pipe@4), append_if_msg_not_empty( style_heading( Styling, <<"SUBCOMMANDS:"/utf8>>, <<"subcommands"/utf8>> ), _pipe@5 ) end, _pipe@6 = [Header_items, Usage, Flags@1, Subcommands], _pipe@7 = gleam@list:filter(_pipe@6, fun is_not_empty/1), gleam@string:join(_pipe@7, <<"\n\n"/utf8>>). -spec append_if_msg_not_empty(binary(), binary()) -> binary(). append_if_msg_not_empty(Prefix, Message) -> case Message of <<""/utf8>> -> <<""/utf8>>; _@1 -> gleam@string:append(Prefix, Message) end. -spec subcommands_help(gleam@map:map_(binary(), command(any()))) -> binary(). subcommands_help(Cmds) -> _pipe = Cmds, _pipe@1 = gleam@map:map_values(_pipe, fun subcommand_help/2), _pipe@2 = gleam@map:values(_pipe@1), _pipe@3 = gleam@list:sort(_pipe@2, fun gleam@string:compare/2), gleam@string:join(_pipe@3, <<"\n\t"/utf8>>). -spec subcommand_help(binary(), command(any())) -> binary(). subcommand_help(Name, Cmd) -> case erlang:element(2, Cmd) of none -> Name; {some, Contents} -> gleam@string:concat( [Name, <<"\t\t"/utf8>>, erlang:element(4, Contents)] ) end.