@@ -9,38 +9,77 @@ extern crate url;
99#[ macro_use] extern crate serde_json;
1010#[ macro_use] extern crate serde_derive;
1111
12+ mod client;
13+ mod commands;
14+ mod config;
15+ mod repl;
16+
1217use clap:: { ArgMatches , App , AppSettings } ;
1318use std:: io;
1419use std:: io:: Write ;
1520
16- mod config;
1721use config:: * ;
18-
19- mod commands;
20-
21- mod client;
2222use client:: * ;
23-
24- mod repl;
2523use repl:: CmdArgs ;
2624
2725fn main ( ) {
28- match ClientConfig :: from_cli_args ( ) {
26+ let app = {
27+ let mut base_app = config:: get_base_app ( ) ;
28+ for command in commands:: all ( ) {
29+ base_app = base_app. subcommand ( command. as_subcommand ( ) ) ;
30+ }
31+ base_app
32+ } ;
33+ let matches = app. get_matches ( ) ;
34+
35+ match ClientConfig :: from_matches ( & matches) {
2936 Ok ( config) => {
3037 let client = ApiClient :: new ( Box :: new ( config) ) ;
31- run_repl ( client) ;
38+ if let ( _, Some ( _) ) = matches. subcommand ( ) {
39+ run_oneoff ( client, & matches) ;
40+ } else {
41+ if !client. get_config ( ) . no_prompt {
42+ println ! ( "Welcome to the Code Dx CLI Client REPL." ) ;
43+ println ! ( "In the REPL, you can enter commands without having to provide the Code Dx base url or credentials each time." ) ;
44+ println ! ( "If this wasn't what you expected, make sure to include a command when running this program from the command line." ) ;
45+ println ! ( "For a list of commands, type 'help'. To exit, type 'exit'" ) ;
46+ println ! ( ) ;
47+ }
48+ run_repl ( client) ;
49+ }
3250 } ,
33- Err ( ConfigError :: MissingAuth ) => println ! ( "Authorization info missing or incomplete. Either an API Key or a Username + Password must be provided" ) ,
34- Err ( ConfigError :: MissingUrl ) => println ! ( "Missing the Base URL" ) ,
35- Err ( ConfigError :: InvalidUrl ) => println ! ( "Invalid Base URL. Did you forget 'http://' or 'https://' ?" ) ,
51+ Err ( ConfigError :: MissingAuth ) => eprintln ! ( "Authorization info missing or incomplete. Either an API Key or a Username + Password must be provided" ) ,
52+ Err ( ConfigError :: MissingUrl ) => eprintln ! ( "Missing the Base URL" ) ,
53+ Err ( ConfigError :: InvalidUrl ) => eprintln ! ( "Invalid Base URL. Did you forget 'http://' or 'https://' ?" ) ,
3654 }
3755}
3856
39- /// Main program loop .
57+ /// Run a single command based on the `arg_matches` .
4058///
41- /// Prompts for a command from stdin, then attempts to interpret it as a `ReplCommand` and execute it.
59+ /// This function will be called when the main `App` matches a subcommand.
60+ /// The subcommand will be run, and the program will exit immediately afterward.
61+ fn run_oneoff < ' a > ( client : ApiClient , arg_matches : & ArgMatches < ' a > ) -> ! {
62+ let command_runner = CommandRunner ( commands:: all ( ) ) ;
63+
64+ let exit_code = match command_runner. maybe_run ( & arg_matches, & client) {
65+ CommandRunnerResult :: Done => 0 ,
66+ CommandRunnerResult :: RequestedExit ( code) => code,
67+ CommandRunnerResult :: UnknownCommand => {
68+ eprintln ! ( "Unknown command." ) ;
69+ -1
70+ } ,
71+ CommandRunnerResult :: InvalidArguments ( msg) => {
72+ eprintln ! ( "Invalid arguments for command: {}" , msg) ;
73+ -2
74+ } ,
75+ } ;
76+
77+ std:: process:: exit ( exit_code) ;
78+ }
79+
80+ /// Repeatedly prompt for- and execute- commands.
4281///
43- /// Repeats until an EOF or the "exit" command are encountered .
82+ /// The loop ends when the "exit" command is run, or when STDIN reaches an EOF .
4483fn run_repl ( client : ApiClient ) {
4584 loop {
4685 // friendly prompt
@@ -76,49 +115,31 @@ fn run_repl(client: ApiClient) {
76115 match e. kind {
77116 clap:: ErrorKind :: HelpDisplayed => {
78117 // repl_app().print_help().unwrap();
79- e. write_to ( & mut io:: stdout ( ) ) . unwrap ( ) ;
80- println ! ( "\n " ) ;
118+ e. write_to ( & mut io:: stderr ( ) ) . unwrap ( ) ;
119+ eprintln ! ( "\n " ) ;
81120 } ,
82121 clap:: ErrorKind :: VersionDisplayed => {
83- //repl_app().write_version(&mut io::stdout()).unwrap();
84- println ! ( "\n " ) ;
122+ eprintln ! ( "\n " ) ;
85123 }
86124 _ => {
87- e. write_to ( & mut io:: stdout ( ) ) . unwrap ( ) ;
88- println ! ( "\n " ) ;
125+ e. write_to ( & mut io:: stderr ( ) ) . unwrap ( ) ;
126+ eprintln ! ( "\n " ) ;
89127 } ,
90128 }
91129 } ,
92130 Ok ( arg_matches) => {
93131 let command_runner = CommandRunner ( commands:: all ( ) ) ;
94- let stuff = command_runner. maybe_run ( & arg_matches, & client) ;
95- // TODO: interpret the result (and give it a better name)
96132
97- match stuff {
98- None => {
99- println ! ( "Invalid command." ) ;
100- println ! ( "Try again." ) ;
101- } ,
102- Some ( Err ( msg) ) => {
103- println ! ( "Invalid arguments for command: {}" , msg) ;
104- println ! ( "Try again." ) ;
105- } ,
106- Some ( Ok ( Err ( commands:: Exit ( code) ) ) ) => {
107- std:: process:: exit ( code) ;
108- } ,
109- Some ( Ok ( Ok ( ( ) ) ) ) => {
110- // command ran without issue
111- }
133+ match command_runner. maybe_run ( & arg_matches, & client) {
134+ CommandRunnerResult :: UnknownCommand => eprintln ! ( "Unknown command; try again." ) ,
135+ CommandRunnerResult :: InvalidArguments ( msg) => eprintln ! ( "Invalid arguments for command: {}\n Try again." , msg) ,
136+ CommandRunnerResult :: RequestedExit ( code) => std:: process:: exit ( code) ,
137+ CommandRunnerResult :: Done => ( ) ,
112138 }
113139 } ,
114140 } ;
115141 }
116142 }
117-
118- }
119-
120- if !client. get_config ( ) . no_prompt {
121- println ! ( "bye" ) ;
122143 }
123144}
124145
@@ -139,13 +160,35 @@ fn repl_app() -> App<'static, 'static> {
139160 app
140161}
141162
163+ /// Wrapper for a collection of `Command`s.
164+ ///
165+ /// Its purpose is to run the first command that matches some given `arg_matches`, returning that command's result.
166+ /// It exposes the result as a friendly enum, `CommandRunnerResult`.
142167struct CommandRunner < ' a > ( Vec < Box < commands:: Command < ' a > > > ) ;
143168impl < ' a > CommandRunner < ' a > {
144- fn maybe_run < ' b > ( & self , arg_matches : & ' a ArgMatches , client : & ' b ApiClient ) -> Option < Result < commands :: CommandResult , & ' a str > > {
145- let foo = self . 0 . iter ( ) . filter_map ( |command_box| {
169+ fn maybe_run < ' b > ( & self , arg_matches : & ' a ArgMatches , client : & ' b ApiClient ) -> CommandRunnerResult < ' a > {
170+ let raw_result = self . 0 . iter ( ) . filter_map ( |command_box| {
146171 let cmd = command_box. as_ref ( ) ;
147172 cmd. maybe_run ( arg_matches, client)
148173 } ) . next ( ) ;
149- foo
174+ raw_result. into ( )
175+ }
176+ }
177+
178+ /// Result of attempting to run the first applicable command on some `ArgMatches`.
179+ enum CommandRunnerResult < ' a > {
180+ Done ,
181+ UnknownCommand ,
182+ InvalidArguments ( & ' a str ) ,
183+ RequestedExit ( i32 ) ,
184+ }
185+ impl < ' a > From < Option < Result < commands:: CommandResult , & ' a str > > > for CommandRunnerResult < ' a > {
186+ fn from ( result : Option < Result < commands:: CommandResult , & ' a str > > ) -> Self {
187+ match result {
188+ Some ( Ok ( Ok ( ( ) ) ) ) => CommandRunnerResult :: Done ,
189+ Some ( Ok ( Err ( commands:: Exit ( code) ) ) ) => CommandRunnerResult :: RequestedExit ( code) ,
190+ Some ( Err ( msg) ) => CommandRunnerResult :: InvalidArguments ( msg) ,
191+ None => CommandRunnerResult :: UnknownCommand ,
192+ }
150193 }
151194}
0 commit comments