1- //! Sub-command dispatch .
1+ //! Sub-command handling .
22//!
33//! `node` is the default sub-command: it can be named explicitly
44//! (`ethlambda node --genesis ...`) or left out entirely
55//! (`ethlambda --genesis ...`). Leaving it out is what the Dockerfile,
66//! lean-quickstart, the hive shim and the devnet skills all do, so that form
77//! stays the one this module is careful about: the token is simply removed
88//! before parsing, and the very same [`CliOptions`] parser then sees the very
9- //! same arguments it saw before this module existed. Help text, error
10- //! messages, exit codes and `--version` are therefore unchanged for it, by
11- //! construction rather than by test.
9+ //! same arguments it saw before this module existed. Its error messages, exit
10+ //! codes and `--version` output are therefore unchanged by construction rather
11+ //! than by test; only `--help` differs, by the [`HELP_NOTE`] it appends .
1212
1313use std:: ffi:: OsString ;
1414
@@ -23,23 +23,18 @@ use crate::cli::CliOptions;
2323/// lands there and is never mistaken for it.
2424const NODE : & str = "node" ;
2525
26- /// What the command line asked the binary to do.
27- #[ derive( Debug ) ]
28- pub ( crate ) enum Invocation {
29- /// Run the consensus node.
30- Node ( CliOptions ) ,
31- }
26+ /// Appended to `--help` by `CliOptions`. The token never reaches clap, so
27+ /// without this the sub-command would be undiscoverable from the help output.
28+ pub ( crate ) const HELP_NOTE : & str = "Sub-commands:\n node \
29+ Run the consensus node (assumed when omitted)";
3230
3331/// Parse the process arguments, exiting the way clap does on a parse error,
3432/// `--help` or `--version`.
35- pub ( crate ) fn parse ( ) -> Invocation {
36- match try_parse_from ( std:: env:: args_os ( ) ) {
37- Ok ( invocation) => invocation,
38- Err ( err) => err. exit ( ) ,
39- }
33+ pub ( crate ) fn parse ( ) -> CliOptions {
34+ try_parse_from ( std:: env:: args_os ( ) ) . unwrap_or_else ( |err| err. exit ( ) )
4035}
4136
42- fn try_parse_from < I > ( args : I ) -> Result < Invocation , clap:: Error >
37+ fn try_parse_from < I > ( args : I ) -> Result < CliOptions , clap:: Error >
4338where
4439 I : IntoIterator ,
4540 I :: Item : Into < OsString > ,
4843 if args. get ( 1 ) . is_some_and ( |arg| arg == NODE ) {
4944 args. remove ( 1 ) ;
5045 }
51- CliOptions :: try_parse_from ( args) . map ( Invocation :: Node )
46+ CliOptions :: try_parse_from ( args)
5247}
5348
5449#[ cfg( test) ]
@@ -90,9 +85,7 @@ mod tests {
9085 }
9186
9287 fn node_options ( args : & [ & str ] ) -> CliOptions {
93- let Invocation :: Node ( options) =
94- try_parse_from ( args. iter ( ) . map ( OsString :: from) ) . expect ( "invocation parses" ) ;
95- options
88+ try_parse_from ( args. iter ( ) . map ( OsString :: from) ) . expect ( "invocation parses" )
9689 }
9790
9891 #[ test]
@@ -109,6 +102,9 @@ mod tests {
109102 fn node_sub_command_accepts_the_same_flags_as_the_flat_form ( ) {
110103 let flat = node_options ( FLAT ) ;
111104 let scoped = node_options ( & with_node_token ( ) ) ;
105+ // Compared through `Debug`, which the derive prints field by field,
106+ // because `CliOptions` derives no `PartialEq` — and deriving one for a
107+ // test would touch the parser this module deliberately leaves alone.
112108 assert_eq ! ( format!( "{flat:?}" ) , format!( "{scoped:?}" ) ) ;
113109 }
114110
@@ -134,6 +130,17 @@ mod tests {
134130 assert_eq ! ( err. kind( ) , ErrorKind :: UnknownArgument ) ;
135131 }
136132
133+ #[ test]
134+ fn only_the_leading_node_token_is_stripped ( ) {
135+ // `ethlambda node node --genesis ...`: the second token is left for
136+ // clap, which rejects it like any other stray positional.
137+ let mut args = with_node_token ( ) ;
138+ args. insert ( 1 , NODE ) ;
139+ let err = try_parse_from ( args. iter ( ) . map ( OsString :: from) )
140+ . expect_err ( "only one leading token is a sub-command" ) ;
141+ assert_eq ! ( err. kind( ) , ErrorKind :: UnknownArgument ) ;
142+ }
143+
137144 #[ test]
138145 fn missing_required_flag_keeps_the_clap_error_in_both_forms ( ) {
139146 // `--genesis config.yaml` dropped from the front of the flag list.
@@ -176,4 +183,11 @@ mod tests {
176183 assert_eq ! ( err. kind( ) , expected) ;
177184 }
178185 }
186+
187+ #[ test]
188+ fn help_documents_the_node_sub_command ( ) {
189+ let err = try_parse_from ( [ "ethlambda" , "--help" ] . iter ( ) . map ( OsString :: from) )
190+ . expect_err ( "--help short-circuits parsing" ) ;
191+ assert ! ( err. to_string( ) . contains( NODE ) , "{err}" ) ;
192+ }
179193}
0 commit comments