diff --git a/apps/arweave_config/src/arweave_config_arguments.erl b/apps/arweave_config/src/arweave_config_arguments.erl new file mode 100644 index 000000000..f3b5a02e9 --- /dev/null +++ b/apps/arweave_config/src/arweave_config_arguments.erl @@ -0,0 +1,457 @@ +%%%=================================================================== +%%% GNU General Public License, version 2 (GPL-2.0) +%%% The GNU General Public License (GPL-2.0) +%%% Version 2, June 1991 +%%% +%%% ------------------------------------------------------------------ +%%% +%%% @copyright 2025 (c) Arweave +%%% @author Arweave Team +%%% @author Mathieu Kerjouan +%%% @doc Arweave Configuration CLI Arguments Parser. +%%% +%%% This module is in charge of parsing the arguments from the command +%%% line, usually defined at startup. The parser will use the +%%% specifications from `arweave_config_spec' (this means, for now, +%%% `arweave_config' must be started to correctly parse something). +%%% +%%% The main idea is to have all arguments flags directly available +%%% from the specifications and parse the arguments from CLI using +%%% them. It should then return a list of actions to be executed. +%%% +%%% This module is also a process called `arweave_config_arguments', +%%% keeping the original command line passed by the user. +%%% +%%% @todo add support for more than one parameter taken from the flags +%%% +%%% @todo add support for short string flags (e.g. -def will look for +%%% -d, -e and -f flags if they are boolean). +%%% +%%% @todo returns a comprehensive error message. +%%% +%%% @todo when parsing fails, the documentation of the last parameter +%%% should be displayed, or the documentation of the whole +%%% application. +%%% +%%% @todo sub-arguments parsing, a more complex way to parse certain +%%% kind of value will be required in some situation, for example for +%%% peers and storage modules. see section below. +%%% +%%% == TODO: sub-arguments parsing == +%%% +%%% Note: the following section is a draft and acts as an example for +%%% the future implementation. +%%% +%%% Let say one wants to configure one specific peer. Instead of +%%% pushing this value in many different place, one can set some +%%% options on this peer. For example: +%%% +%%% ``` +%%% --peer europe.arweave.xyz --trusted \ +%%% --peer vdf-server-3.arweave.xyz --trusted --vdf +%%% ''' +%%% +%%% In this code, the peer(s) `europe.arweave.xyz' will now be set as +%%% `trusted' and `vdf-server-3.arweave.xyz' will be set as `trusted' +%%% and `vdf'. The main information - here the peer - is not +%%% duplicated across many options. +%%% +%%% ``` +%%% { +%%% "peers": { +%%% "europe.arweave.xyz": { +%%% "trusted": true +%%% }, +%%% "vdf-server-3.arweave.xyz": { +%%% "trusted": true, +%%% "vdf": true +%%% } +%%% } +%%% } +%%% ''' +%%% +%%% Another example with storage modules. One wants to configure +%%% storages module with many different options. +%%% +%%% ``` +%%% --storage.module 0 \ +%%% --protocol unpacked \ +%%% --enabled \ +%%% --storage.module 0 +%%% --protocol replica.2.9 \ +%%% --start 0 \ +%%% --end 7200000000000 \ +%%% --pubkey L-cPRwGcnMFyQW-APh_fS4lMGioLg76-ECovqXIlmJ4 \ +%%% --enabled +%%% ''' +%%% +%%% The first argument will create storage module `0' using unpacked +%%% data. The second storage module will use `replica.2.9' with custom +%%% options (e.g. offset and pubkey). The final representation of the +%%% data as JSON would be represented like the following snippet: +%%% +%%% ``` +%%% { +%%% "storage": { +%%% { +%%% "modules": [ +%%% { +%%% "partition": "0", +%%% "protocol": "unpacked", +%%% "status": "enabled" +%%% }, +%%% { +%%% "partition": "0", +%%% "protocol: "replica.2.9", +%%% "start": "0", +%%% "end": "7200000000000", +%%% "pubkey": "L-cPRwGcnMFyQW-APh_fS4lMGioLg76-ECovqXIlmJ4", +%%% "status": "enabled" +%%% } +%%% ] +%%% } +%%% } +%%% ''' +%%% +%%% On the specification side, a sub-argument could be represented by +%%% a function, returning a list of spec. +%%% +%%% ``` +%%% #{ +%%% short_argument => {<<"-S">>, fun storage/0}, +%%% long_argument => {<<"--storage.module">>, {M,F,A}} +%%% } +%%% +%%% storage() -> +%%% [ +%%% #{ +%%% % implicit definition of parameter key +%%% % parameter_key => [storage,modules,0] +%%% long_argument => <<"--enabled">>, +%%% short_argument => $E, +%%% type => boolean, +%%% default => true +%%% }, +%%% #{ +%%% long_argument => <<"--protocol">>, +%%% type => storage_protocol +%%% required => true +%%% } +%%% ]. +%%% ''' +%%% +%%% @end +%%%=================================================================== +-module(arweave_config_arguments). +-behavior(gen_server). +-compile(warnings_as_errors). +-compile({no_auto_import,[get/0]}). +-export([ + start_link/0, + load/0, + load/1, + get/0, + parse/1, + parse/2 +]). +-export([init/1]). +-export([handle_call/3, handle_cast/2, handle_info/2]). +-include_lib("kernel/include/logger.hrl"). + +%%-------------------------------------------------------------------- +%% @doc Parses command line arguments. +%% @see parse/2 +%% @end +%%-------------------------------------------------------------------- +-spec parse(Args) -> Return when + Args :: [binary()], + Return :: {ok, [{Spec, Values}]} | {error, Reason}, + Spec :: map(), + Values :: [term()], + Reason :: map(). + +parse(Args) -> + parse(Args, #{}). + +%%-------------------------------------------------------------------- +%% @doc Parses an argument from command line. Erlang is usually giving +%% us these arguments as a `[string()]', but we want it to be a +%% `[binary()]', to make our life easier when displaying this +%% information somewhere else (e.g. JSON). +%% +%% Custom specifications can be set using `long_arguments' and +%% `short_arguments' options. Those are mostly used for testing and +%% debugging purpose, by default, this function will fetch +%% specifications from `arweave_config_spec' process. +%% @end +%%-------------------------------------------------------------------- +-spec parse(Args, Opts) -> Return when + Args :: [binary()], + Opts :: #{ + long_arguments => #{}, + short_arguments => #{} + }, + Return :: {ok, [{Spec, Values}]} | {error, Reason}, + Spec :: map(), + Values :: [term()], + Reason :: map(). + +parse(Args, Opts) -> + parse_converter(Args, Opts). + +%%-------------------------------------------------------------------- +%% @hidden +%% @doc type converter, the parser only check binary data. +%% @end +%%-------------------------------------------------------------------- +parse_converter(Args, Opts) -> + parse_converter(Args, [], Opts). + +parse_converter([], Buffer, Opts) -> + Reverse = lists:reverse(Buffer), + parse_final(Reverse, Opts); +parse_converter([Arg|Rest], Buffer, Opts) when is_list(Arg) -> + NewBuffer = [list_to_binary(Arg)|Buffer], + parse_converter(Rest, NewBuffer, Opts); +parse_converter([Arg|Rest], Buffer, Opts) when is_integer(Arg) -> + NewBuffer = [integer_to_binary(Arg)|Buffer], + parse_converter(Rest, NewBuffer, Opts); +parse_converter([Arg|Rest], Buffer, Opts) when is_float(Arg) -> + NewBuffer = [float_to_binary(Arg)|Buffer], + parse_converter(Rest, NewBuffer, Opts); +parse_converter([Arg|Rest], Buffer, Opts) when is_atom(Arg) -> + NewBuffer = [atom_to_binary(Arg)|Buffer], + parse_converter(Rest, NewBuffer, Opts); +parse_converter([Arg|Rest], Buffer, Opts) when is_binary(Arg) -> + NewBuffer = [Arg|Buffer], + parse_converter(Rest, NewBuffer, Opts). + +%%-------------------------------------------------------------------- +%% @hidden +%%-------------------------------------------------------------------- +parse_final(Args, Opts) -> + try + LongArgs = maps:get( + long_arguments, + Opts, + % @todo it's annoying to convert these values, longs/short + % args from specifications should be returned as map directly. + maps:from_list( + arweave_config_spec:get_long_arguments() + ) + ), + ShortArgs = maps:get( + short_arguments, + Opts, + % @todo it's annoying to convert these values, longs/short + % args from specifications should be returned as map directly. + maps:from_list( + arweave_config_spec:get_short_arguments() + ) + ), + State = #{ + args => Args, + la => LongArgs, + sa => ShortArgs, + pos => 1, + actions => [] + }, + parse(Args, State, Opts) + catch + _:R -> + {error, #{ + reason => R + } + } + end. + +%%-------------------------------------------------------------------- +%% @hidden +%% @doc loop over the arguments and check them. +%% @end +%%-------------------------------------------------------------------- +-spec parse(Args, State, Opts) -> Return when + Args :: [binary()], + State :: map(), + Opts :: map(), + Return :: {ok, [{Spec, Values}]} | {error, Reason}, + Spec :: map(), + Values :: [term()], + Reason :: map(). + +parse([], #{ actions := Buffer }, _Opts) -> + {ok, lists:reverse(Buffer)}; +parse([Arg = <<"---",_/binary>>|_], State, _Opts) -> + Pos = maps:get(pos, State), + {error, #{ + reason => <<"bad_argument">>, + argument => Arg, + position => Pos + } + }; +parse([Arg = <<"--",_/binary>>|Rest], State = #{la := LA}, Opts) + when is_map_key(Arg, LA) -> + % by default, we assume the argument is a long + % arguments and we try to find it. + Spec = maps:get(Arg, LA), + Pos = maps:get(pos, State), + case apply_spec(Rest, Spec, State#{ pos => Pos+1 }) of + {ok, NewRest, NewState} -> + parse(NewRest, NewState, Opts); + Else -> + Else + end; +parse([<<"-", Arg>>|Rest], State = #{sa := SA}, Opts) + when is_map_key(Arg, SA), + Arg =/= $- -> + Spec = maps:get(Arg, SA), + Pos = maps:get(pos, State), + case apply_spec(Rest, Spec, State#{ pos => Pos+1 }) of + {ok, NewRest, NewState} -> + parse(NewRest, NewState, Opts); + Else -> + Else + end; +parse([Unknown|_], #{ pos := Pos }, _Opts) -> + {error, #{ + reason => <<"unknown argument">>, + argument => Unknown, + position => Pos + } + }. + +%%-------------------------------------------------------------------- +%% @hidden +%% @doc Take a value and check its type. +%% @end +%%-------------------------------------------------------------------- +apply_spec([], Spec = #{type := boolean}, State) -> + Buffer = maps:get(actions, State), + NewBuffer = [{Spec, [true]}|Buffer], + NewState = State#{ + actions => NewBuffer + }, + {ok, [], NewState}; +apply_spec([Value|Rest], Spec = #{type := boolean}, State) -> + Buffer = maps:get(actions, State), + case arweave_config_type:boolean(Value) of + {ok, Return} -> + Pos = maps:get(pos, State), + NewBuffer = [{Spec, [Return]}|Buffer], + NewState = State#{ + actions => NewBuffer, + pos => Pos+1 + }, + {ok, Rest, NewState}; + _ -> + NewBuffer = [{Spec, [true]}|Buffer], + NewState = State#{ + actions => NewBuffer + }, + {ok, [Value|Rest], NewState} + end; +apply_spec([Value|Rest], Spec = #{type := Type}, State) -> + Buffer = maps:get(actions, State), + Pos = maps:get(pos, State), + case arweave_config_type:Type(Value) of + {ok, Return} -> + NewBuffer = [{Spec, [Return]}|Buffer], + NewState = State#{ + actions => NewBuffer, + pos => Pos+1 + }, + {ok, Rest, NewState}; + _ -> + {error, #{ + reason => <<"bad value">>, + value => Value, + type => Type, + position => Pos + } + } + end. + +%%-------------------------------------------------------------------- +%% @doc starts arweave_config_arguments server. +%% @end +%%-------------------------------------------------------------------- +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, #{}, []). + +%%-------------------------------------------------------------------- +%% @doc loads arguments from `init:get_plain_arguments/0'. +%% @see init:get_plain_arguments/0 +%% @end +%%-------------------------------------------------------------------- +load() -> + gen_server:cast(?MODULE, load). + +%%-------------------------------------------------------------------- +%% @doc loads arguments from command line. +%% @end +%%-------------------------------------------------------------------- +load(Args) -> + gen_server:call(?MODULE, {load, Args}, 10_000). + +%%-------------------------------------------------------------------- +%% @doc returns parsed arguments from process state. +%% @end +%%-------------------------------------------------------------------- +get() -> + gen_server:call(?MODULE, get, 10_000). + +%%-------------------------------------------------------------------- +%% @hidden +%%-------------------------------------------------------------------- +init(Opts) -> + RawArgs = maps:get(raw_args, Opts, []), + State = #{ + raw_args => RawArgs + }, + {ok, State}. + +%%-------------------------------------------------------------------- +%% @hidden +%%-------------------------------------------------------------------- +handle_call(get, _From, State) -> + Args = maps:get(args, State, []), + {reply, Args, State}; +handle_call({load, Args}, _From, State) -> + try + parse(Args) + of + Result = {ok, _Parsed} -> + NewState = State#{ args => Args }, + {reply, Result, NewState}; + Else -> + {reply, Else, State} + catch + Error:Reason -> + {reply, {Error, Reason}} + end; +handle_call(_, _, State) -> + {reply, ok, State}. + +%%-------------------------------------------------------------------- +%% @hidden +%%-------------------------------------------------------------------- +handle_cast(load, State) -> + Args = init:get_plain_arguments(), + case parse(Args) of + {ok, Parsed} -> + NewState = State#{ + args => Parsed, + raw_args => Args + }, + {noreply, NewState}; + _Else -> + {noreply, State} + end; +handle_cast(_, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @hidden +%%-------------------------------------------------------------------- +handle_info(_, State) -> + {noreply, State}. diff --git a/apps/arweave_config/src/arweave_config_environment.erl b/apps/arweave_config/src/arweave_config_environment.erl index 6e0c2bf7d..8536b9e08 100644 --- a/apps/arweave_config/src/arweave_config_environment.erl +++ b/apps/arweave_config/src/arweave_config_environment.erl @@ -49,11 +49,12 @@ %%%=================================================================== -module(arweave_config_environment). -behavior(gen_server). +-compile(warnings_as_errors). +-compile({no_auto_import,[get/0]}). -export([load/0, get/0, get/1, reset/0]). -export([start_link/0]). -export([init/1, terminate/2]). -export([handle_call/3, handle_cast/2, handle_info/2]). --compile({no_auto_import,[get/0]}). -include_lib("kernel/include/logger.hrl"). -include_lib("eunit/include/eunit.hrl"). diff --git a/apps/arweave_config/src/arweave_config_parameters.erl b/apps/arweave_config/src/arweave_config_parameters.erl index 01ce9ed39..d1753c1e1 100644 --- a/apps/arweave_config/src/arweave_config_parameters.erl +++ b/apps/arweave_config/src/arweave_config_parameters.erl @@ -105,6 +105,7 @@ init() -> default => "./logs", type => path, environment => true, + long_argument => true, runtime => false, handle_set => fun (_K, Path, _S, _) when is_list(Path) -> @@ -122,6 +123,7 @@ init() -> default => 8128, type => pos_integer, environment => true, + long_argument => true, runtime => true, handle_set => { fun logger_set/4, @@ -137,6 +139,7 @@ init() -> default => 256, type => pos_integer, environment => true, + long_argument => true, runtime => true, handle_set => { fun logger_set/4, @@ -152,6 +155,7 @@ init() -> default => 16256, type => pos_integer, environment => true, + long_argument => true, runtime => true, handle_set => { fun logger_set/4, @@ -165,6 +169,7 @@ init() -> default => 20, type => pos_integer, environment => true, + long_argument => true, runtime => true, handle_set => { fun logger_set/4, @@ -180,6 +185,7 @@ init() -> default => 51418800, type => pos_integer, environment => true, + long_argument => true, runtime => true, handle_set => { fun logger_set/4, @@ -193,6 +199,7 @@ init() -> default => false, type => boolean, environment => true, + long_argument => true, runtime => true, handle_set => { fun logger_set/4, @@ -206,6 +213,7 @@ init() -> type => pos_integer, default => 10, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_info,config,sync_mode_qlen] @@ -218,6 +226,7 @@ init() -> type => pos_integer, default => 200, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_info,config,drop_mode_qlen] @@ -230,6 +239,7 @@ init() -> type => pos_integer, default => 1000, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_info,config,flush_qlen] @@ -242,6 +252,7 @@ init() -> type => boolean, default => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_info,config,burst_limit_enable] @@ -254,6 +265,7 @@ init() -> type => pos_integer, default => 500, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_info,config,burst_limit_max_count] @@ -266,6 +278,7 @@ init() -> type => pos_integer, default => 1000, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_info,config,burst_limit_window_time] @@ -278,6 +291,7 @@ init() -> type => boolean, default => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_info,config,overload_kill_enable] @@ -290,6 +304,7 @@ init() -> type => pos_integer, default => 20_000, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_info,config,overload_kill_qlen] @@ -302,6 +317,7 @@ init() -> type => pos_integer, default => 3_000_000, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_info,config,overload_kill_mem_size] @@ -314,6 +330,7 @@ init() -> type => pos_integer, default => 5000, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_info,config,overload_kill_restart_after] @@ -327,6 +344,7 @@ init() -> default => false, type => boolean, environment => true, + long_argument => true, runtime => true, handle_set => fun (_,true,_,_) -> @@ -343,6 +361,7 @@ init() -> inherit => [logging,max_no_files], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,config,max_no_files] @@ -354,6 +373,7 @@ init() -> inherit => [logging,max_no_bytes], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,config,max_no_bytes] @@ -365,6 +385,7 @@ init() -> inherit => [logging,sync_mode_qlen], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,config,sync_mode_qlen] @@ -376,6 +397,7 @@ init() -> inherit => [logging,drop_mode_qlen], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,config,drop_mode_qlen] @@ -387,6 +409,7 @@ init() -> inherit => [logging,flush_qlen], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,config,flush_qlen] @@ -398,6 +421,7 @@ init() -> inherit => [logging,burst_limit_enable], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,config,burst_limit_enable] @@ -409,6 +433,7 @@ init() -> inherit => [logging,burst_limit_max_count], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,config,burst_limit_max_count] @@ -420,6 +445,7 @@ init() -> inherit => [logging,burst_limit_window_time], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,config,burst_limit_window_time] @@ -431,6 +457,7 @@ init() -> inherit => [logging,overload_kill_enable], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,config,overload_kill_enable] @@ -442,6 +469,7 @@ init() -> inherit => [logging,overload_kill_qlen], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,config,overload_kill_qlen] @@ -453,6 +481,7 @@ init() -> inherit => [logging,overload_kill_mem_size], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,config,overload_kill_mem_size] @@ -464,6 +493,7 @@ init() -> inherit => [logging,overload_kill_restart_after], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,config,overload_kill_restart_after] @@ -475,6 +505,7 @@ init() -> inherit => [logging,formatter,chars_limit], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,formatter,chars_limit] @@ -486,6 +517,7 @@ init() -> inherit => [logging,formatter,depth], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,formatter,depth] @@ -497,6 +529,7 @@ init() -> inherit => [logging,formatter,max_size], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,formatter,max_size] @@ -508,6 +541,7 @@ init() -> inherit => [logging,formatter,template], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_debug,formatter,template] @@ -521,6 +555,7 @@ init() -> default => false, type => boolean, environment => true, + long_argument => true, runtime => true, handle_set => fun (_K,true,_S,_) -> @@ -537,6 +572,7 @@ init() -> inherit => [logging,max_no_files], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,config,max_no_files] @@ -548,6 +584,7 @@ init() -> inherit => [logging,max_no_bytes], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,config,max_no_bytes] @@ -559,6 +596,7 @@ init() -> inherit => [logging,sync_mode_qlen], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,config,sync_mode_qlen] @@ -570,6 +608,7 @@ init() -> inherit => [logging,drop_mode_qlen], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,config,drop_mode_qlen] @@ -581,6 +620,7 @@ init() -> inherit => [logging,flush_qlen], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,config,flush_qlen] @@ -592,6 +632,7 @@ init() -> inherit => [logging,burst_limit_enable], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,config,burst_limit_enable] @@ -603,6 +644,7 @@ init() -> inherit => [logging,burst_limit_max_count], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,config,burst_limit_max_count] @@ -614,6 +656,7 @@ init() -> inherit => [logging,burst_limit_window_time], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,config,burst_limit_window_time] @@ -625,6 +668,7 @@ init() -> inherit => [logging,overload_kill_enable], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,config,overload_kill_enable] @@ -636,6 +680,7 @@ init() -> inherit => [logging,overload_kill_qlen], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,config,overload_kill_qlen] @@ -647,6 +692,7 @@ init() -> inherit => [logging,overload_kill_mem_size], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,config,overload_kill_mem_size] @@ -658,6 +704,7 @@ init() -> inherit => [logging,overload_kill_restart_after], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,config,overload_kill_restart_after] @@ -669,6 +716,7 @@ init() -> inherit => {[logging,compress_on_rotate], [type, default]}, runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,config,compress_on_rotate] @@ -680,6 +728,7 @@ init() -> inherit => [logging,formatter,chars_limit], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,formatter,chars_limit] @@ -691,6 +740,7 @@ init() -> inherit => [logging,formatter,max_size], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,formatter,max_size] @@ -702,6 +752,7 @@ init() -> inherit => [logging,formatter,depth], runtime => true, environment => true, + long_argument => true, handle_set => { fun logger_set/4, [arweave_http_api,formatter,depth] @@ -713,7 +764,8 @@ init() -> %----------------------------------------------------- #{ parameter_key => [config,http,api,enabled], - environment => <<"AR_CONFIG_HTTP_API_ENABLED">>, + environment => true, + long_argument => true, short_description => <<"enable arweave configuration http api interface">>, % @todo enable it by default after testing default => false, @@ -723,7 +775,8 @@ init() -> }, #{ parameter_key => [config,http,api,listen,port], - environment => <<"AR_CONFIG_HTTP_API_LISTEN_PORT">>, + environment => true, + long_argument => true, short_description => "set arweave configuration http api interface port", default => 4891, type => tcp_port, @@ -732,7 +785,8 @@ init() -> }, #{ parameter_key => [config,http,api,listen,address], - environment => <<"AR_CONFIG_HTTP_API_LISTEN_ADDRESS">>, + environment => true, + long_argument => true, short_description => "set arweave configuration http api listen address", type => [ipv4, file], required => false, diff --git a/apps/arweave_config/src/arweave_config_specs/arweave_config_spec_long_argument.erl b/apps/arweave_config/src/arweave_config_specs/arweave_config_spec_long_argument.erl index c673fa3ef..a804ff633 100644 --- a/apps/arweave_config/src/arweave_config_specs/arweave_config_spec_long_argument.erl +++ b/apps/arweave_config/src/arweave_config_specs/arweave_config_spec_long_argument.erl @@ -52,6 +52,8 @@ fetch(Module, State) -> check(Module, false, State) -> {ok, State}; +check(Module, true, State = #{ parameter_key := CK }) -> + {ok, State#{ long_argument => convert(CK) }}; check(Module, undefined, State = #{ parameter_key := CK }) -> {ok, State#{ long_argument => convert(CK) }}; check(Module, LA, State) when is_binary(LA) orelse is_list(LA) -> @@ -72,7 +74,7 @@ convert(Binary) when is_binary(Binary) -> <<"-", Binary/binary>>. convert([], Buffer) -> Sep = application:get_env(arweave_config, long_argument_separator, "."), Bin = list_to_binary(lists:join(Sep, lists:reverse(Buffer))), - <<"-", Bin/binary>>; + <<"--", Bin/binary>>; convert([H|T], Buffer) when is_integer(H) -> convert([integer_to_binary(H)|T], Buffer); convert([H|T], Buffer) when is_atom(H) -> diff --git a/apps/arweave_config/src/arweave_config_sup.erl b/apps/arweave_config/src/arweave_config_sup.erl index 461a4cc01..b3ee3d1df 100644 --- a/apps/arweave_config/src/arweave_config_sup.erl +++ b/apps/arweave_config/src/arweave_config_sup.erl @@ -59,6 +59,14 @@ children() -> }, type => worker }, + #{ + id => arweave_config_arguments, + start => { + arweave_config_arguments, + start_link, + [] + } + }, #{ id => arweave_config_store, start => { diff --git a/apps/arweave_config/src/arweave_config_type.erl b/apps/arweave_config/src/arweave_config_type.erl index d272e3870..af441b0d5 100644 --- a/apps/arweave_config/src/arweave_config_type.erl +++ b/apps/arweave_config/src/arweave_config_type.erl @@ -68,19 +68,38 @@ atom(V) when is_atom(V) -> {ok, V}; atom(V) -> {error, V}. %%-------------------------------------------------------------------- -%% @doc check booleans from binary, list, integer and atoms. +%% @doc check booleans from binary, list, integer and atoms. When a +%% string is used, a regexp is being used and ignore the case of the +%% word. +%% +%% == Examples == +%% +%% ``` +%% {ok, true} = boolean(true). +%% {ok, true} = boolean(<<"true">>). +%% {ok, true} = boolean("true"). +%% {ok, true} = boolean("on"). +%% {ok, true} = boolean(<<"TruE">>). +%% ''' +%% %% @end %%-------------------------------------------------------------------- -spec boolean(Input) -> Return when Input :: string() | binary() | boolean(), Return :: {ok, boolean()} | {error, Input}. -boolean(<<"true">>) -> {ok, true}; -boolean(<<"false">>) -> {ok, false}; -boolean("true") -> {ok, true}; -boolean("false") -> {ok, false}; boolean(true) -> {ok, true}; +boolean(on) -> {ok, true}; boolean(false) -> {ok, false}; +boolean(off) -> {ok, false}; +boolean(String) when is_list(String); is_binary(String) -> + Regexp = "^(?false|off)|(?true|on)$", + Opts = [extended, caseless, {capture, all_names, binary}], + case re:run(String, Regexp, Opts) of + {match, [<<>>, _True]} -> {ok, true}; + {match, [_False, <<>>]} -> {ok, false}; + _ -> {error, String} + end; boolean(V) -> {error, V}. %%-------------------------------------------------------------------- diff --git a/apps/arweave_config/test/arweave_config_arguments_SUITE.erl b/apps/arweave_config/test/arweave_config_arguments_SUITE.erl new file mode 100644 index 000000000..c748c63a8 --- /dev/null +++ b/apps/arweave_config/test/arweave_config_arguments_SUITE.erl @@ -0,0 +1,169 @@ +%%%=================================================================== +%%% GNU General Public License, version 2 (GPL-2.0) +%%% The GNU General Public License (GPL-2.0) +%%% Version 2, June 1991 +%%% +%%% ------------------------------------------------------------------ +%%% +%%% @author Arweave Team +%%% @author Mathieu Kerjouan +%%% @copyright 2025 (c) Arweave +%%% @doc +%%% @end +%%%=================================================================== +-module(arweave_config_arguments_SUITE). +-export([suite/0, description/0]). +-export([init_per_suite/1, end_per_suite/1]). +-export([init_per_testcase/2, end_per_testcase/2]). +-export([all/0]). +-export([ + default/1, + parser/1 +]). +-include("arweave_config.hrl"). +-include_lib("common_test/include/ct.hrl"). + +%%-------------------------------------------------------------------- +%% @hidden +%%-------------------------------------------------------------------- +suite() -> [{userdata, [description()]}]. + +%%-------------------------------------------------------------------- +%% @hidden +%%-------------------------------------------------------------------- +description() -> {description, "arweave config cli arguments interface"}. + +%%-------------------------------------------------------------------- +%% @hidden +%%-------------------------------------------------------------------- +init_per_suite(Config) -> Config. + +%%-------------------------------------------------------------------- +%% @hidden +%%-------------------------------------------------------------------- +end_per_suite(_Config) -> ok. + +%%-------------------------------------------------------------------- +%% @hidden +%%-------------------------------------------------------------------- +init_per_testcase(_TestCase, Config) -> + ct:pal(info, 1, "start arweave_config"), + ok = arweave_config:start(), + []. + +%%-------------------------------------------------------------------- +%% @hidden +%%-------------------------------------------------------------------- +end_per_testcase(_TestCase, _Config) -> + ct:pal(info, 1, "stop arweave_config"), + ok = arweave_config:stop(). + +%%-------------------------------------------------------------------- +%% @hidden +%%-------------------------------------------------------------------- +all() -> + [ + default, + parser + ]. + +%%-------------------------------------------------------------------- +%% @doc test `arweave_config_arguments' main interface. +%% @end +%%-------------------------------------------------------------------- +default(_Config) -> + {comment, "arguments process tested"}. + +%%-------------------------------------------------------------------- +%% @doc test `arweave_config_arguments' parser. +%% @end +%%-------------------------------------------------------------------- +parser(_Config) -> + % Create a custom long arguments map + LongArguments = #{ + <<"--boolean">> => #{ + type => boolean + }, + <<"--integer">> => #{ + type => integer + }, + <<"--integer.pos">> => #{ + type => pos_integer + } + }, + + % create a custom short argmuents map + ShortArguments = #{ + $b => #{ + type => boolean + }, + $i => #{ + type => integer + }, + $I => #{ + type => pos_integer + } + }, + Arguments = [ + % long arguments + <<"--boolean">>, + <<"--boolean">>, <<"true">>, + <<"--boolean">>, <<"false">>, + <<"--boolean">>, <<"True">>, + <<"--boolean">>, <<"TRUE">>, + <<"--boolean">>, <<"FALSE">>, + <<"--integer">>, <<"-65535">>, + <<"--integer">>, <<"-0">>, + <<"--integer">>, <<"-65535">>, + <<"--integer.pos">>, <<"0">>, + <<"--integer.pos">>, <<"65535">>, + + % short arguments + <<"-b">>, + <<"-b">>, <<"true">>, + <<"-b">>, <<"on">>, + <<"-b">>, <<"off">>, + <<"-b">>, <<"false">>, + <<"-i">>, <<"65535">>, + <<"-i">>, <<"0">>, + <<"-i">>, <<"-65535">>, + <<"-I">>, <<"0">>, + <<"-I">>, <<"65535">> + ], + + % --peer 127.0.0.1 --vdf --trusted + % --storage.module 1.unpacked --enabled + + Opts = #{ + long_arguments => LongArguments, + short_arguments => ShortArguments + }, + + ct:pal(test, 1, "parse ~p", [Arguments]), + Result = [ + {#{type => boolean},[true]}, + {#{type => boolean},[true]}, + {#{type => boolean},[false]}, + {#{type => boolean},[true]}, + {#{type => boolean},[true]}, + {#{type => boolean},[false]}, + {#{type => integer},[-65535]}, + {#{type => integer},[0]}, + {#{type => integer},[-65535]}, + {#{type => pos_integer},[0]}, + {#{type => pos_integer},[65535]}, + {#{type => boolean},[true]}, + {#{type => boolean},[true]}, + {#{type => boolean},[true]}, + {#{type => boolean},[false]}, + {#{type => boolean},[false]}, + {#{type => integer},[65535]}, + {#{type => integer},[0]}, + {#{type => integer},[-65535]}, + {#{type => pos_integer},[0]}, + {#{type => pos_integer},[65535]} + ], + {ok, Result} = arweave_config_arguments:parse(Arguments, Opts), + + {comment, "arguments parser tested"}. +