@@ -5,8 +5,11 @@ use anyhow::Context;
5
5
use cap:: Cap ;
6
6
use clap:: error:: ErrorFormatter ;
7
7
use clap:: error:: RichFormatter ;
8
+ use clap:: CommandFactory ;
9
+ use clap:: FromArgMatches ;
8
10
use clap:: Parser ;
9
11
use std:: alloc;
12
+ use std:: ffi:: OsString ;
10
13
use std:: future:: Future ;
11
14
use std:: io:: IsTerminal ;
12
15
use std:: path:: PathBuf ;
@@ -15,8 +18,11 @@ use tedge::command::BuildCommand;
15
18
use tedge:: command:: BuildContext ;
16
19
use tedge:: log:: MaybeFancy ;
17
20
use tedge:: Component ;
21
+ use tedge:: ComponentOpt ;
22
+ use tedge:: TEdgeOpt ;
18
23
use tedge:: TEdgeOptMulticall ;
19
24
use tedge_apt_plugin:: AptCli ;
25
+ use tedge_config:: cli:: CommonArgs ;
20
26
use tedge_config:: system_services:: log_init;
21
27
use tracing:: log;
22
28
@@ -26,12 +32,7 @@ static ALLOCATOR: Cap<alloc::System> = Cap::new(alloc::System, usize::MAX);
26
32
fn main ( ) -> anyhow:: Result < ( ) > {
27
33
let executable_name = executable_name ( ) ;
28
34
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 ( ) ) ;
35
36
match opt {
36
37
TEdgeOptMulticall :: Component ( Component :: TedgeMapper ( opt) ) => {
37
38
let tedge_config = tedge_config:: TEdgeConfig :: load ( & opt. common . config_dir ) ?;
@@ -58,6 +59,9 @@ fn main() -> anyhow::Result<()> {
58
59
block_on ( tedge_watchdog:: run ( opt) )
59
60
}
60
61
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
+ }
61
65
TEdgeOptMulticall :: Tedge { cmd, common } => {
62
66
let tedge_config_location =
63
67
tedge_config:: TEdgeConfigLocation :: from_custom_root ( & common. config_dir ) ;
@@ -120,20 +124,94 @@ fn executable_name() -> Option<String> {
120
124
)
121
125
}
122
126
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 ( ) ;
125
145
126
146
let is_known_subcommand = executable_name
127
147
. as_deref ( )
128
148
. map_or ( false , |name| cmd. find_subcommand ( name) . is_some ( ) ) ;
129
149
let cmd = cmd. multicall ( is_known_subcommand) ;
130
150
131
151
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) ,
133
154
Ok ( t) => t,
134
155
Err ( e) => {
135
156
eprintln ! ( "{}" , RichFormatter :: format_error( & e. with_cmd( & cmd2) ) ) ;
136
157
std:: process:: exit ( 1 ) ;
137
158
}
138
159
}
139
160
}
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