diff --git a/imag-counter/src/interactive.rs b/imag-counter/src/interactive.rs new file mode 100644 index 00000000..fc7116c8 --- /dev/null +++ b/imag-counter/src/interactive.rs @@ -0,0 +1,154 @@ +use std::collections::BTreeMap; +use std::fmt::{Display, Formatter, Error}; +use std::io::Write; +use std::io::stderr; +use std::io::stdin; +use std::process::exit; +use std::result::Result as RResult; + +use libimagcounter::counter::Counter; +use libimagcounter::error::CounterError; +use libimagrt::runtime::Runtime; +use libimagutil::key_value_split::IntoKeyValue; +use libimagutil::key_value_split::KeyValue; +use libimagutil::trace::trace_error; + +type Result = RResult; + +pub fn interactive(rt: &Runtime) { + let scmd = rt.cli().subcommand_matches("interactive"); + if scmd.is_none() { + write!(stderr(), "No subcommand"); + exit(1); + } + let scmd = scmd.unwrap(); + debug!("Found 'interactive' command"); + + let mut pairs : BTreeMap = BTreeMap::new(); + + for spec in scmd.values_of("spec").unwrap() { + match compute_pair(rt, &spec) { + Ok((k, v)) => { pairs.insert(k, v); }, + Err(e) => { trace_error(&e); }, + } + } + + if !has_quit_binding(&pairs) { + pairs.insert('q', Binding::Function(String::from("quit"), Box::new(quit))); + } + + stderr().flush(); + loop { + println!("---"); + for (k, v) in &pairs { + println!("\t[{}] => {}", k, v); + } + println!("---"); + print!("counter > "); + + let mut input = String::new(); + if let Err(e) = stdin().read_line(&mut input) { + trace_error(&e); + exit(1); + } + + let cont = if input.len() > 0 { + let increment = match input.chars().next() { Some('-') => false, _ => true }; + input.chars().all(|chr| { + match pairs.get_mut(&chr) { + Some(&mut Binding::Counter(ref mut ctr)) => { + if increment { + debug!("Incrementing"); + ctr.inc(); + } else { + debug!("Decrementing"); + ctr.dec(); + } + true + }, + Some(&mut Binding::Function(ref name, ref f)) => { + debug!("Calling {}", name); + f() + }, + None => true, + } + }) + } else { + println!("No input..."); + println!("\tUse a single character to increment the counter which is bound to it"); + println!("\tUse 'q' (or the character bound to quit()) to exit"); + println!("\tPrefix the line with '-' to decrement instead of increment the counters"); + println!(""); + true + }; + + if !cont { + break; + } + } +} + +fn has_quit_binding(pairs: &BTreeMap) -> bool { + pairs.iter() + .any(|(_, bind)| { + match bind { + &Binding::Function(ref name, _) => name == "quit", + _ => false, + } + }) +} + +enum Binding<'a> { + Counter(Counter<'a>), + Function(String, Box bool>), +} + +impl<'a> Display for Binding<'a> { + + fn fmt(&self, fmt: &mut Formatter) -> RResult<(), Error> { + match self { + &Binding::Counter(ref c) => { + match c.name() { + Ok(name) => { + try!(write!(fmt, "{}", name)); + Ok(()) + }, + Err(e) => { + trace_error(&e); + Ok(()) // TODO: Find a better way to escalate here. + }, + } + }, + &Binding::Function(ref name, _) => write!(fmt, "{}()", name), + } + } + +} + +fn compute_pair<'a>(rt: &'a Runtime, spec: &str) -> Result<(char, Binding<'a>)> { + let kv = String::from(spec).into_kv(); + if kv.is_none() { + write!(stderr(), "Key-Value parsing failed!"); + exit(1); + } + let kv = kv.unwrap(); + + let (k, v) = kv.into(); + if !k.len() == 1 { + // We have a key which is not only a single character! + exit(1); + } + + if v == "quit" { + // TODO uncaught unwrap() + Ok((k.chars().next().unwrap(), Binding::Function(String::from("quit"), Box::new(quit)))) + } else { + // TODO uncaught unwrap() + Counter::load(v, rt.store()).and_then(|ctr| Ok((k.chars().next().unwrap(), Binding::Counter(ctr)))) + } +} + +fn quit() -> bool { + false +} + diff --git a/imag-counter/src/main.rs b/imag-counter/src/main.rs index cfc93142..961db48c 100644 --- a/imag-counter/src/main.rs +++ b/imag-counter/src/main.rs @@ -16,11 +16,13 @@ use libimagutil::key_value_split::IntoKeyValue; mod create; mod delete; +mod interactive; mod ui; use ui::build_ui; use create::create; use delete::delete; +use interactive::interactive; enum Action { Inc, @@ -121,14 +123,13 @@ fn main() { |name| { debug!("Call: {}", name); match name { - "create" => create(&rt), - "delete" => delete(&rt), + "create" => create(&rt), + "delete" => delete(&rt), + "interactive" => interactive(&rt), _ => { debug!("Unknown command"); // More error handling }, }; }) - - - } + diff --git a/imag-counter/src/ui.rs b/imag-counter/src/ui.rs index 610a4241..994bb65e 100644 --- a/imag-counter/src/ui.rs +++ b/imag-counter/src/ui.rs @@ -53,6 +53,18 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .takes_value(true) .required(true) .help("Create counter with this name"))) + + .subcommand(SubCommand::with_name("interactive") + .about("Interactively count things") + .version("0.1") + .arg(Arg::with_name("spec") + .long("spec") + .short("s") + .takes_value(true) + .multiple(true) + .required(true) + .help("Specification for key-bindings. Use = where KEY is the + key to bind (single character) and VALUE is the path to the counter to bind + to."))) } -