@@ -5,8 +5,11 @@ use anyhow::Context;
55use cap:: Cap ;
66use clap:: error:: ErrorFormatter ;
77use clap:: error:: RichFormatter ;
8+ use clap:: CommandFactory ;
9+ use clap:: FromArgMatches ;
810use clap:: Parser ;
911use std:: alloc;
12+ use std:: ffi:: OsString ;
1013use std:: future:: Future ;
1114use std:: io:: IsTerminal ;
1215use std:: path:: PathBuf ;
@@ -15,8 +18,11 @@ use tedge::command::BuildCommand;
1518use tedge:: command:: BuildContext ;
1619use tedge:: log:: MaybeFancy ;
1720use tedge:: Component ;
21+ use tedge:: ComponentOpt ;
22+ use tedge:: TEdgeOpt ;
1823use tedge:: TEdgeOptMulticall ;
1924use tedge_apt_plugin:: AptCli ;
25+ use tedge_config:: cli:: CommonArgs ;
2026use tedge_config:: system_services:: log_init;
2127use tracing:: log;
2228
@@ -26,12 +32,7 @@ static ALLOCATOR: Cap<alloc::System> = Cap::new(alloc::System, usize::MAX);
2632fn main ( ) -> anyhow:: Result < ( ) > {
2733 let executable_name = executable_name ( ) ;
2834
29- if matches ! ( executable_name. as_deref( ) , Some ( "apt" | "tedge-apt-plugin" ) ) {
30- let try_opt = AptCli :: try_parse ( ) ;
31- tedge_apt_plugin:: run_and_exit ( try_opt) ;
32- }
33-
34- let opt = parse_multicall_if_known ( & executable_name) ;
35+ let opt = parse_multicall ( & executable_name, std:: env:: args_os ( ) ) ;
3536 match opt {
3637 TEdgeOptMulticall :: Component ( Component :: TedgeMapper ( opt) ) => {
3738 let tedge_config = tedge_config:: TEdgeConfig :: load ( & opt. common . config_dir ) ?;
@@ -58,6 +59,9 @@ fn main() -> anyhow::Result<()> {
5859 block_on ( tedge_watchdog:: run ( opt) )
5960 }
6061 TEdgeOptMulticall :: Component ( Component :: TedgeWrite ( opt) ) => tedge_write:: bin:: run ( opt) ,
62+ TEdgeOptMulticall :: Component ( Component :: TedgeAptPlugin ( opt) ) => {
63+ tedge_apt_plugin:: run_and_exit ( opt)
64+ }
6165 TEdgeOptMulticall :: Tedge { cmd, common } => {
6266 let tedge_config_location =
6367 tedge_config:: TEdgeConfigLocation :: from_custom_root ( & common. config_dir ) ;
@@ -120,20 +124,94 @@ fn executable_name() -> Option<String> {
120124 )
121125}
122126
123- fn parse_multicall_if_known < T : Parser > ( executable_name : & Option < String > ) -> T {
124- let cmd = T :: command ( ) ;
127+ fn parse_multicall < Arg , Args > ( executable_name : & Option < String > , args : Args ) -> TEdgeOptMulticall
128+ where
129+ Args : IntoIterator < Item = Arg > ,
130+ Arg : Into < OsString > + Clone ,
131+ {
132+ if matches ! ( executable_name. as_deref( ) , Some ( "apt" | "tedge-apt-plugin" ) ) {
133+ // the apt plugin must be treated apart
134+ // as we want to exit 1 and not 2 when the command line cannot be parsed
135+ match AptCli :: try_parse ( ) {
136+ Ok ( apt) => return TEdgeOptMulticall :: Component ( Component :: TedgeAptPlugin ( apt) ) ,
137+ Err ( e) => {
138+ eprintln ! ( "{}" , RichFormatter :: format_error( & e) ) ;
139+ std:: process:: exit ( 1 ) ;
140+ }
141+ }
142+ }
143+
144+ let cmd = TEdgeOptMulticall :: command ( ) ;
125145
126146 let is_known_subcommand = executable_name
127147 . as_deref ( )
128148 . map_or ( false , |name| cmd. find_subcommand ( name) . is_some ( ) ) ;
129149 let cmd = cmd. multicall ( is_known_subcommand) ;
130150
131151 let cmd2 = cmd. clone ( ) ;
132- match T :: from_arg_matches ( & cmd. get_matches ( ) ) {
152+ match TEdgeOptMulticall :: from_arg_matches ( & cmd. get_matches_from ( args) ) {
153+ Ok ( TEdgeOptMulticall :: Tedge { cmd, common } ) => redirect_if_multicall ( cmd, common) ,
133154 Ok ( t) => t,
134155 Err ( e) => {
135156 eprintln ! ( "{}" , RichFormatter :: format_error( & e. with_cmd( & cmd2) ) ) ;
136157 std:: process:: exit ( 1 ) ;
137158 }
138159 }
139160}
161+
162+ // Transform `tedge mapper|agent|write` commands into multicalls
163+ //
164+ // This method has to be kept in sync with TEdgeOpt::build_command
165+ fn redirect_if_multicall ( cmd : TEdgeOpt , common : CommonArgs ) -> TEdgeOptMulticall {
166+ match cmd {
167+ TEdgeOpt :: Run ( ComponentOpt { component } ) => TEdgeOptMulticall :: Component ( component) ,
168+ cmd => TEdgeOptMulticall :: Tedge { cmd, common } ,
169+ }
170+ }
171+
172+ #[ cfg( test) ]
173+ mod tests {
174+ use crate :: parse_multicall;
175+ use crate :: Component ;
176+ use crate :: TEdgeOptMulticall ;
177+ use test_case:: test_case;
178+
179+ #[ test]
180+ fn launching_a_mapper ( ) {
181+ let exec = Some ( "tedge-mapper" . to_string ( ) ) ;
182+ let cmd = parse_multicall ( & exec, [ "tedge-mapper" , "c8y" ] ) ;
183+ assert ! ( matches!(
184+ cmd,
185+ TEdgeOptMulticall :: Component ( Component :: TedgeMapper ( _) )
186+ ) )
187+ }
188+
189+ #[ test]
190+ fn using_tedge_to_launch_a_mapper ( ) {
191+ let exec = Some ( "tedge" . to_string ( ) ) ;
192+ let cmd = parse_multicall ( & exec, [ "tedge" , "run" , "tedge-mapper" , "c8y" ] ) ;
193+ assert ! ( matches!(
194+ cmd,
195+ TEdgeOptMulticall :: Component ( Component :: TedgeMapper ( _) )
196+ ) )
197+ }
198+
199+ #[ test_case( "tedge-mapper c8y --config-dir /some/dir" ) ]
200+ #[ test_case( "tedge-mapper --config-dir /some/dir c8y" ) ]
201+ #[ test_case( "tedge run tedge-mapper c8y --config-dir /some/dir" ) ]
202+ #[ test_case( "tedge run tedge-mapper --config-dir /some/dir c8y" ) ]
203+ #[ test_case( "tedge --config-dir /some/dir run tedge-mapper c8y" ) ]
204+ // clap fails to raise an error here and takes the inner value for all global args
205+ #[ test_case( "tedge --config-dir /oops run tedge-mapper c8y --config-dir /some/dir" ) ]
206+ fn setting_config_dir ( cmd_line : & ' static str ) {
207+ let args: Vec < & str > = cmd_line. split ( ' ' ) . collect ( ) ;
208+ let exec = Some ( args. get ( 0 ) . unwrap ( ) . to_string ( ) ) ;
209+ let cmd = parse_multicall ( & exec, args) ;
210+ match cmd {
211+ TEdgeOptMulticall :: Component ( Component :: TedgeMapper ( mapper) ) => {
212+ assert_eq ! ( mapper. common. config_dir, "/some/dir" )
213+ }
214+ _ => panic ! ( ) ,
215+ }
216+ }
217+ }
0 commit comments