-module(glint). -compile([no_auto_import, nowarn_unused_vars]). -export([with_config/2, with_global_flag/3, with_global_flags/2, new/0, with_pretty_help/2, without_pretty_help/1, default_pretty_help/0, command/1, description/2, flag/3, help_flag/0, flags/2, add/3, add_command_from_stub/2, execute/2, run_and_handle/3, run/2]). -export_type([glint/1, config/1, pretty_help/0, command_input/0, command/1, command_node/1, stub/1, out/1]). -opaque glint(GIC) :: {glint, config(GIC), command_node(GIC), gleam@map:map_(binary(), glint@flag:flag())}. -type config(GID) :: {config, gleam@option:option(pretty_help())} | {gleam_phantom, GID}. -type pretty_help() :: {pretty_help, gleam_community@colour:colour(), gleam_community@colour:colour(), gleam_community@colour:colour()}. -type command_input() :: {command_input, list(binary()), gleam@map:map_(binary(), glint@flag:flag())}. -opaque command(GIE) :: {command, fun((command_input()) -> GIE), gleam@map:map_(binary(), glint@flag:flag()), binary()}. -opaque command_node(GIF) :: {command_node, gleam@option:option(command(GIF)), gleam@map:map_(binary(), command_node(GIF))}. -type stub(GIG) :: {stub, list(binary()), fun((command_input()) -> GIG), list({binary(), glint@flag:flag()}), binary()}. -type out(GIH) :: {out, GIH} | {help, binary()}. -spec with_config(glint(GIS), config(GIS)) -> glint(GIS). with_config(Glint, Config) -> erlang:setelement(2, Glint, Config). -spec with_global_flag(glint(GIW), binary(), glint@flag:flag()) -> glint(GIW). with_global_flag(Glint, Key, Flag) -> erlang:setelement( 4, Glint, gleam@map:insert(erlang:element(4, Glint), Key, Flag) ). -spec with_global_flags(glint(GIZ), list({binary(), glint@flag:flag()})) -> glint(GIZ). with_global_flags(Glint, Flags) -> gleam@list:fold( Flags, Glint, fun(Acc, _use1) -> {Key, Flag} = _use1, with_global_flag(Acc, Key, Flag) end ). -spec empty_command() -> command_node(any()). empty_command() -> {command_node, none, gleam@map:new()}. -spec new() -> glint(any()). new() -> {glint, {config, none}, empty_command(), gleam@map:new()}. -spec with_pretty_help(glint(GJF), pretty_help()) -> glint(GJF). with_pretty_help(Glint, Pretty) -> _pipe = {config, {some, Pretty}}, with_config(Glint, _pipe). -spec without_pretty_help(glint(GJI)) -> glint(GJI). without_pretty_help(Glint) -> _pipe = {config, none}, with_config(Glint, _pipe). -spec default_pretty_help() -> pretty_help(). default_pretty_help() -> _assert_subject = gleam_community@colour:from_rgb255(182, 255, 234), {ok, Usage_colour} = case _assert_subject of {ok, _} -> _assert_subject; _assert_fail -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"glint"/utf8>>, function => <<"default_pretty_help"/utf8>>, line => 146}) end, _assert_subject@1 = gleam_community@colour:from_rgb255(255, 175, 243), {ok, Flags_colour} = case _assert_subject@1 of {ok, _} -> _assert_subject@1; _assert_fail@1 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@1, module => <<"glint"/utf8>>, function => <<"default_pretty_help"/utf8>>, line => 147}) end, _assert_subject@2 = gleam_community@colour:from_rgb255(252, 226, 174), {ok, Subcommands_colour} = case _assert_subject@2 of {ok, _} -> _assert_subject@2; _assert_fail@2 -> erlang:error(#{gleam_error => assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail@2, module => <<"glint"/utf8>>, function => <<"default_pretty_help"/utf8>>, line => 148}) end, {pretty_help, Usage_colour, Flags_colour, Subcommands_colour}. -spec command(fun((command_input()) -> GJS)) -> command(GJS). command(Runner) -> {command, Runner, gleam@map:new(), <<""/utf8>>}. -spec description(command(GJV), binary()) -> command(GJV). description(Cmd, Description) -> erlang:setelement(4, Cmd, Description). -spec is_not_empty(binary()) -> boolean(). is_not_empty(S) -> S /= <<""/utf8>>. -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 wrap_with_space(binary()) -> binary(). wrap_with_space(S) -> case S of <<""/utf8>> -> <<" "/utf8>>; _ -> <<<<" "/utf8, S/binary>>/binary, " "/utf8>> end. -spec subcommand_help(binary(), command_node(any())) -> binary(). subcommand_help(Name, Cmd) -> case erlang:element(2, Cmd) of none -> Name; {some, Contents} -> <<<>/binary, (erlang:element(4, Contents))/binary>> end. -spec subcommands_help(gleam@map:map_(binary(), command_node(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 heading_style(binary(), gleam_community@colour:colour()) -> binary(). heading_style(Heading, Colour) -> _pipe = Heading, _pipe@1 = gleam_community@ansi:bold(_pipe), _pipe@2 = gleam_community@ansi:underline(_pipe@1), _pipe@3 = gleam_community@ansi:italic(_pipe@2), _pipe@4 = gleam_community@ansi:hex( _pipe@3, gleam_community@colour:to_rgb_hex(Colour) ), gleam_community@ansi:reset(_pipe@4). -spec flag(command(GLL), binary(), glint@flag:flag()) -> command(GLL). flag(Cmd, Key, Flag) -> erlang:setelement( 3, Cmd, gleam@map:insert(erlang:element(3, Cmd), Key, Flag) ). -spec execute_root( command_node(GKD), gleam@map:map_(binary(), glint@flag:flag()), list(binary()), list(binary()) ) -> {ok, out(GKD)} | {error, snag:snag()}. execute_root(Cmd, Global_flags, Args, Flag_inputs) -> _pipe@3 = case erlang:element(2, Cmd) of {some, Contents} -> gleam@result:then( gleam@list:try_fold( Flag_inputs, gleam@map:merge(Global_flags, erlang:element(3, Contents)), fun glint@flag:update_flags/2 ), fun(New_flags) -> _pipe = {command_input, Args, New_flags}, _pipe@1 = (erlang:element(2, Contents))(_pipe), _pipe@2 = {out, _pipe@1}, {ok, _pipe@2} end ); none -> snag:error(<<"command not found"/utf8>>) end, snag:context(_pipe@3, <<"failed to run command"/utf8>>). -spec help_flag() -> binary(). help_flag() -> <<(<<"--"/utf8>>)/binary, "help"/utf8>>. -spec usage_help( binary(), gleam@map:map_(binary(), glint@flag:flag()), gleam@option:option(pretty_help()) ) -> 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(); _ -> _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@12 = gleam@string_builder:prepend( _pipe@9, <<(begin _pipe@10 = Styling, _pipe@11 = gleam@option:map( _pipe@10, fun(Styling@1) -> heading_style( <<"USAGE:"/utf8>>, erlang:element(2, Styling@1) ) end ), gleam@option:unwrap(_pipe@11, <<"USAGE:"/utf8>>) end)/binary, "\n\t"/utf8>> ), gleam@string_builder:to_string(_pipe@12). -spec cmd_help( list(binary()), command_node(any()), gleam@option:option(pretty_help()), gleam@map:map_(binary(), glint@flag:flag()) ) -> binary(). cmd_help(Path, Cmd, Pretty_help, Global_flags) -> Name = begin _pipe = Path, _pipe@1 = gleam@list:reverse(_pipe), gleam@string:join(_pipe@1, <<" "/utf8>>) end, Flags = begin _pipe@2 = gleam@option:map( erlang:element(2, Cmd), fun(Contents) -> erlang:element(3, Contents) end ), _pipe@3 = gleam@option:lazy_unwrap(_pipe@2, fun gleam@map:new/0), gleam@map:merge(Global_flags, _pipe@3) end, Flags_help_body = <<<<(begin _pipe@4 = Pretty_help, _pipe@5 = gleam@option:map( _pipe@4, fun(P) -> heading_style(<<"FLAGS:"/utf8>>, erlang:element(3, P)) end ), gleam@option:unwrap(_pipe@5, <<"FLAGS:"/utf8>>) end)/binary, "\n\t"/utf8>>/binary, (gleam@string:join( gleam@list:sort( [<<"--help\t\t\tPrint help information"/utf8>> | glint@flag:flags_help(Flags)], fun gleam@string:compare/2 ), <<"\n\t"/utf8>> ))/binary>>, Usage = usage_help(Name, Flags, Pretty_help), Description = begin _pipe@6 = erlang:element(2, Cmd), _pipe@7 = gleam@option:map( _pipe@6, fun(Contents@1) -> erlang:element(4, Contents@1) end ), gleam@option:unwrap(_pipe@7, <<""/utf8>>) end, Header_items = begin _pipe@8 = [Name, Description], _pipe@9 = gleam@list:filter(_pipe@8, fun is_not_empty/1), gleam@string:join(_pipe@9, <<"\n"/utf8>>) end, Subcommands = case subcommands_help(erlang:element(3, Cmd)) of <<""/utf8>> -> <<""/utf8>>; Subcommands_help_body -> <<<<(begin _pipe@10 = Pretty_help, _pipe@11 = gleam@option:map( _pipe@10, fun(P@1) -> heading_style( <<"SUBCOMMANDS:"/utf8>>, erlang:element(4, P@1) ) end ), gleam@option:unwrap(_pipe@11, <<"SUBCOMMANDS:"/utf8>>) end)/binary, "\n\t"/utf8>>/binary, Subcommands_help_body/binary>> end, _pipe@12 = [Header_items, Usage, Flags_help_body, Subcommands], _pipe@13 = gleam@list:filter(_pipe@12, fun is_not_empty/1), gleam@string:join(_pipe@13, <<"\n\n"/utf8>>). -spec flags(command(GLO), list({binary(), glint@flag:flag()})) -> command(GLO). flags(Cmd, Flags) -> gleam@list:fold( Flags, Cmd, fun(Cmd@1, _use1) -> {Key, Flag} = _use1, erlang:setelement( 3, Cmd@1, gleam@map:insert(erlang:element(3, Cmd@1), Key, Flag) ) end ). -spec do_add_command(command_node(GJY), list(binary()), command(GJY)) -> command_node(GJY). do_add_command(Root, Path, Contents) -> case Path of [] -> erlang:setelement(2, Root, {some, Contents}); [X | Xs] -> erlang:setelement( 3, Root, (gleam@map:update( erlang:element(3, Root), X, fun(Node) -> _pipe = Node, _pipe@1 = gleam@option:lazy_unwrap( _pipe, fun empty_command/0 ), do_add_command(_pipe@1, Xs, Contents) end )) ) end. -spec add(glint(GJN), list(binary()), command(GJN)) -> glint(GJN). add(Glint, Path, Contents) -> erlang:setelement( 3, Glint, begin _pipe = Path, _pipe@1 = sanitize_path(_pipe), do_add_command(erlang:element(3, Glint), _pipe@1, Contents) end ). -spec add_command_from_stub(glint(GIM), stub(GIM)) -> glint(GIM). add_command_from_stub(Glint, Stub) -> add( Glint, erlang:element(2, Stub), begin _pipe = command(erlang:element(3, Stub)), _pipe@1 = flags(_pipe, erlang:element(4, Stub)), description(_pipe@1, erlang:element(5, Stub)) end ). -spec do_execute( command_node(GKM), gleam@option:option(pretty_help()), gleam@map:map_(binary(), glint@flag:flag()), list(binary()), list(binary()), boolean(), list(binary()) ) -> {ok, out(GKM)} | {error, snag:snag()}. do_execute(Cmd, Pretty_help, Global_flags, Args, Flags, Help, Command_path) -> case Args of [] when Help -> _pipe = Command_path, _pipe@1 = cmd_help(_pipe, Cmd, Pretty_help, Global_flags), _pipe@2 = {help, _pipe@1}, {ok, _pipe@2}; [] -> execute_root(Cmd, Global_flags, [], Flags); [Arg | Rest] -> case gleam@map:get(erlang:element(3, Cmd), Arg) of {ok, Cmd@1} -> do_execute( Cmd@1, Pretty_help, Global_flags, Rest, Flags, Help, [Arg | Command_path] ); _ when Help -> _pipe@3 = Command_path, _pipe@4 = cmd_help(_pipe@3, Cmd, Pretty_help, Global_flags), _pipe@5 = {help, _pipe@4}, {ok, _pipe@5}; _ -> execute_root(Cmd, Global_flags, Args, Flags) end end. -spec execute(glint(GKI), list(binary())) -> {ok, out(GKI)} | {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, {_, Args@1}} -> {true, Args@1}; _ -> {false, Args} end, {Flags, Args@3} = gleam@list:partition( Args@2, fun(_capture) -> gleam@string:starts_with(_capture, <<"--"/utf8>>) end ), do_execute( erlang:element(3, Glint), erlang:element(2, erlang:element(2, Glint)), erlang:element(4, Glint), Args@3, Flags, Help, [] ). -spec run_and_handle(glint(GKW), list(binary()), fun((GKW) -> any())) -> nil. run_and_handle(Glint, Args, Handle) -> 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); {ok, {out, Out}} -> Handle(Out), nil end. -spec run(glint(any()), list(binary())) -> nil. run(Glint, Args) -> run_and_handle(Glint, Args, gleam@function:constant(nil)).