Skip to content

Commit 1d0ec54

Browse files
committed
set up parsing for a REPL inside the CLI
- using "nom" to make parser combinator methods so I can act like a good shell and allow for quoted arguments in case they have spaces - started wiring up a "clap" App to handle the commands from STDIN - run the app with --repl to try it (but it's not very exciting yet)
1 parent 5d98c88 commit 1d0ec54

5 files changed

Lines changed: 307 additions & 3 deletions

File tree

Cargo.lock

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ authors = ["Dylan Halperin <dylan@dylemma.io>"]
66
[dependencies]
77
chrono = "0.4"
88
clap = "2.26.2"
9+
colored = "1.5.3"
910
hyper = "0.11"
1011
maplit = "0.1.4"
1112
url = "1.5.1"
@@ -14,4 +15,8 @@ serde_json = "1.0"
1415
serde_derive = "1.0"
1516

1617
# waiting for https://github.com/seanmonstar/reqwest/pull/190 to be released
17-
reqwest = { git = "https://github.com/seanmonstar/reqwest", branch = "master"}
18+
reqwest = { git = "https://github.com/seanmonstar/reqwest", branch = "master"}
19+
20+
[dependencies.nom]
21+
version = "3.2"
22+
features = ["verbose-errors"]

src/client.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,18 @@ impl <'a> ApiProjectFilter<'a> {
2929
metadata: Some(metadata)
3030
}
3131
}
32+
#[allow(dead_code)]
3233
pub fn empty() -> ApiProjectFilter<'static> {
3334
ApiProjectFilter{ name: None, metadata: None }
3435
}
36+
#[allow(dead_code)]
3537
pub fn with_name(&self, name: &'a str) -> ApiProjectFilter<'a> {
3638
ApiProjectFilter {
3739
name: Some(name),
3840
metadata: self.metadata.clone()
3941
}
4042
}
43+
#[allow(dead_code)]
4144
pub fn with_metadata(&self, metadata: HashMap<&'a str, &'a str>) -> ApiProjectFilter<'a> {
4245
ApiProjectFilter {
4346
name: self.name,
@@ -82,6 +85,7 @@ impl JobStatus {
8285
_ => false
8386
}
8487
}
88+
#[allow(dead_code)]
8589
pub fn is_success(&self) -> bool {
8690
match *self {
8791
JobStatus::Completed => true,
@@ -358,6 +362,7 @@ pub enum ReqBody {
358362
None,
359363
}
360364
impl ReqBody {
365+
#[allow(dead_code)]
361366
pub fn as_json<T: Serialize>(body: T) -> ReqBody {
362367
ReqBody::Json(serde_json::to_value(body).unwrap())
363368
}

src/main.rs

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ extern crate url;
66

77
#[macro_use] extern crate hyper;
88
#[macro_use] extern crate maplit;
9+
#[macro_use] extern crate nom;
910
#[macro_use] extern crate serde_json;
1011
#[macro_use] extern crate serde_derive;
1112

1213
use chrono::Utc;
14+
use clap::{Arg, App, AppSettings, SubCommand};
15+
use std::io;
1316
use std::path::Path;
1417
use std::time::Duration;
1518

@@ -19,7 +22,109 @@ use config::*;
1922
mod client;
2023
use client::*;
2124

22-
fn main() {
25+
mod repl;
26+
use repl::CmdArgs;
27+
28+
fn main(){
29+
let arg1 = ::std::env::args().nth(1);
30+
match arg1 {
31+
Some(ref flag) if flag == "--repl" => {
32+
println!("repl mode!");
33+
run_repl();
34+
},
35+
_ => {
36+
println!("arg was {:?}", arg1);
37+
main2();
38+
},
39+
}
40+
}
41+
42+
fn run_repl() {
43+
loop {
44+
// grab a line from STDIN
45+
let mut input = String::new();
46+
io::stdin().read_line(&mut input)
47+
.expect("failed to read line");
48+
49+
// ignore the trailing \n stuff
50+
let line = input.trim();
51+
52+
if line.is_empty() {
53+
continue;
54+
} else {
55+
if let Ok(args) = line.parse::<CmdArgs>() {
56+
let mut args_vec = args.0;
57+
args_vec.insert(0, "<placeholder>".to_string());
58+
59+
let matches = repl_app().get_matches_from_safe(args_vec);
60+
61+
match matches {
62+
Err(ref e) => {
63+
match e.kind {
64+
clap::ErrorKind::HelpDisplayed => {
65+
repl_app().print_help().unwrap();
66+
println!();
67+
},
68+
clap::ErrorKind::VersionDisplayed => {
69+
repl_app().write_version(&mut io::stdout()).unwrap();
70+
println!();
71+
}
72+
_ => {
73+
e.write_to(&mut io::stdout()).unwrap();
74+
println!();
75+
},
76+
}
77+
},
78+
Ok(arg_matches) => {
79+
if let Some(_) = arg_matches.subcommand_matches("exit") {
80+
break;
81+
}
82+
83+
println!("matches: {:?}", arg_matches);
84+
},
85+
};
86+
}
87+
}
88+
89+
}
90+
println!("bye");
91+
}
92+
93+
/// Get a copy of the "App" for the internal REPL.
94+
///
95+
/// Note that since some of the methods we want to use on it will "consume" it,
96+
/// we need to be able to easily get copies of the whole App for convenience.
97+
fn repl_app() -> App<'static, 'static> {
98+
App::new("My Super Program")
99+
.bin_name("")
100+
.version("2.6.1")
101+
.setting(AppSettings::VersionlessSubcommands)
102+
.setting(AppSettings::ColoredHelp)
103+
.subcommand(SubCommand::with_name("analyze")
104+
.about("Analyze some files")
105+
.arg(Arg::with_name("name")
106+
.short("n")
107+
.long("name")
108+
.value_name("NAME")
109+
.takes_value(true)
110+
.required(false)
111+
)
112+
.arg(Arg::with_name("file")
113+
.short("f")
114+
.long("file")
115+
.value_name("FILE")
116+
.takes_value(true)
117+
.multiple(true)
118+
.required(true)
119+
)
120+
)
121+
.subcommand(SubCommand::with_name("exit")
122+
.alias("quit")
123+
.about("Exit this program ('quit' works too)")
124+
)
125+
}
126+
127+
fn main2() {
23128
match ClientConfig::from_cli_args() {
24129
Ok(config) => {
25130
let client = ApiClient::new(Box::new(config));
@@ -88,4 +193,4 @@ fn main() {
88193

89194
fn format_current_datetime() -> String {
90195
Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()
91-
}
196+
}

0 commit comments

Comments
 (0)