From 59a3662ac47b0c657781bd1db34bbf9a5a692326 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 13 Aug 2017 21:48:17 +0000 Subject: Reorganize code in subdirs --- lib/etc/libimaginteraction/src/ask.rs | 350 +++++++++++++++++++++++++++++ lib/etc/libimaginteraction/src/error.rs | 39 ++++ lib/etc/libimaginteraction/src/filter.rs | 20 ++ lib/etc/libimaginteraction/src/lib.rs | 53 +++++ lib/etc/libimaginteraction/src/readline.rs | 129 +++++++++++ lib/etc/libimaginteraction/src/result.rs | 24 ++ lib/etc/libimaginteraction/src/ui.rs | 83 +++++++ 7 files changed, 698 insertions(+) create mode 100644 lib/etc/libimaginteraction/src/ask.rs create mode 100644 lib/etc/libimaginteraction/src/error.rs create mode 100644 lib/etc/libimaginteraction/src/filter.rs create mode 100644 lib/etc/libimaginteraction/src/lib.rs create mode 100644 lib/etc/libimaginteraction/src/readline.rs create mode 100644 lib/etc/libimaginteraction/src/result.rs create mode 100644 lib/etc/libimaginteraction/src/ui.rs (limited to 'lib/etc/libimaginteraction/src') diff --git a/lib/etc/libimaginteraction/src/ask.rs b/lib/etc/libimaginteraction/src/ask.rs new file mode 100644 index 00000000..2a393d0e --- /dev/null +++ b/lib/etc/libimaginteraction/src/ask.rs @@ -0,0 +1,350 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +// functions to ask the user for data, with crate:spinner + +use std::io::stdin; +use std::io::BufRead; +use std::io::BufReader; +use std::result::Result as RResult; + +use error::InteractionError; +use error::InteractionErrorKind; +use result::Result; + +use regex::Regex; +use ansi_term::Colour::*; +use interactor::*; + +/// Ask the user for a Yes/No answer. Optionally provide a default value. If none is provided, this +/// keeps loop{}ing +pub fn ask_bool(s: &str, default: Option) -> bool { + ask_bool_(s, default, &mut BufReader::new(stdin())) +} + +fn ask_bool_(s: &str, default: Option, input: &mut R) -> bool { + lazy_static! { + static ref R_YES: Regex = Regex::new(r"^[Yy](\n?)$").unwrap(); + static ref R_NO: Regex = Regex::new(r"^[Nn](\n?)$").unwrap(); + } + + loop { + ask_question(s, false); + if match default { Some(s) => s, _ => true } { + println!(" [Yn]: "); + } else { + println!(" [yN]: "); + } + + let mut s = String::new(); + let _ = input.read_line(&mut s); + + if R_YES.is_match(&s[..]) { + return true + } else if R_NO.is_match(&s[..]) { + return false + } else if default.is_some() { + return default.unwrap(); + } + // else again... + } +} + +/// Ask the user for an unsigned number. Optionally provide a default value. If none is provided, +/// this keeps loop{}ing +pub fn ask_uint(s: &str, default: Option) -> u64 { + ask_uint_(s, default, &mut BufReader::new(stdin())) +} + +fn ask_uint_(s: &str, default: Option, input: &mut R) -> u64 { + use std::str::FromStr; + + loop { + ask_question(s, false); + + let mut s = String::new(); + let _ = input.read_line(&mut s); + + let u : RResult = FromStr::from_str(&s[..]); + match u { + Ok(u) => { return u; }, + Err(_) => { + if default.is_some() { + return default.unwrap(); + } // else keep looping + } + } + } +} + +/// Ask the user for a String. +/// +/// If `permit_empty` is set to false, the default value will be returned if the user inserts an +/// empty string. +/// +/// If the `permit_empty` value is true, the `default` value is never returned. +/// +/// If the `permit_multiline` is set to true, the `prompt` will be displayed before each input line. +/// +/// If the `eof` parameter is `None`, the input ends as soon as there is an empty line input from +/// the user. If the parameter is `Some(text)`, the input ends if the input line is equal to `text`. +pub fn ask_string(s: &str, + default: Option, + permit_empty: bool, + permit_multiline: bool, + eof: Option<&str>, + prompt: &str) + -> String +{ + ask_string_(s, + default, + permit_empty, + permit_multiline, + eof, + prompt, + &mut BufReader::new(stdin())) +} + +fn ask_string_(s: &str, + default: Option, + permit_empty: bool, + permit_multiline: bool, + eof: Option<&str>, + prompt: &str, + input: &mut R) + -> String +{ + let mut v = vec![]; + loop { + ask_question(s, true); + print!("{}", prompt); + + let mut s = String::new(); + let _ = input.read_line(&mut s); + + if permit_multiline { + if permit_multiline && eof.map_or(false, |e| e == s) { + return v.join("\n"); + } + + if permit_empty || !v.is_empty() { + v.push(s); + } + print!("{}", prompt); + } else if s.is_empty() && permit_empty { + return s; + } else if s.is_empty() && !permit_empty { + if default.is_some() { + return default.unwrap(); + } else { + continue; + } + } else { + return s; + } + } +} + +pub fn ask_select_from_list(list: &[&str]) -> Result { + pick_from_list(default_menu_cmd().as_mut(), list, "Selection: ") + .map_err(|e| InteractionError::new(InteractionErrorKind::Unknown, Some(Box::new(e)))) +} + +/// Helper function to print a imag question string. The `question` argument may not contain a +/// trailing questionmark. +/// +/// The `nl` parameter can be used to configure whether a newline character should be printed +pub fn ask_question(question: &str, nl: bool) { + if nl { + println!("[imag]: {}?", Yellow.paint(question)); + } else { + print!("[imag]: {}?", Yellow.paint(question)); + } +} + +#[cfg(test)] +mod test { + use std::io::BufReader; + + use super::ask_bool_; + use super::ask_uint_; + + #[test] + fn test_ask_bool_nodefault_yes() { + let question = "Is this true"; + let default = None; + let answers = "\n\n\n\n\ny"; + + assert!(ask_bool_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_bool_nodefault_yes_nl() { + let question = "Is this true"; + let default = None; + let answers = "\n\n\n\n\ny\n"; + + assert!(ask_bool_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_bool_nodefault_no() { + let question = "Is this true"; + let default = None; + let answers = "n"; + + assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_bool_nodefault_no_nl() { + let question = "Is this true"; + let default = None; + let answers = "n\n"; + + assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_bool_default_no() { + let question = "Is this true"; + let default = Some(false); + let answers = "n"; + + assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_bool_default_no_nl() { + let question = "Is this true"; + let default = Some(false); + let answers = "n\n"; + + assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_bool_default_yes() { + let question = "Is this true"; + let default = Some(true); + let answers = "y"; + + assert!(true == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_bool_default_yes_nl() { + let question = "Is this true"; + let default = Some(true); + let answers = "y\n"; + + assert!(true == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_bool_default_yes_answer_no() { + let question = "Is this true"; + let default = Some(true); + let answers = "n"; + + assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_bool_default_no_answer_yes() { + let question = "Is this true"; + let default = Some(false); + let answers = "y"; + + assert!(true == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_bool_default_no_without_answer() { + let question = "Is this true"; + let default = Some(false); + let answers = "\n"; + + assert!(false == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_bool_default_yes_without_answer() { + let question = "Is this true"; + let default = Some(true); + let answers = "\n"; + + assert!(true == ask_bool_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_uint_nodefault() { + let question = "Is this 1"; + let default = None; + let answers = "1"; + + assert!(1 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_uint_default() { + let question = "Is this 1"; + let default = Some(1); + let answers = "1"; + + assert!(1 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_uint_default_2_input_1() { + let question = "Is this 1"; + let default = Some(2); + let answers = "1"; + + assert!(1 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_uint_default_2_noinput() { + let question = "Is this 1"; + let default = Some(2); + let answers = "\n"; + + assert!(2 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_uint_default_2_several_noinput() { + let question = "Is this 1"; + let default = Some(2); + let answers = "\n\n\n\n"; + + assert!(2 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + + #[test] + fn test_ask_uint_default_2_wrong_input() { + let question = "Is this 1"; + let default = Some(2); + let answers = "\n\n\nasfb\nsakjf\naskjf\n-2"; + + assert!(2 == ask_uint_(question, default, &mut BufReader::new(answers.as_bytes()))); + } + +} diff --git a/lib/etc/libimaginteraction/src/error.rs b/lib/etc/libimaginteraction/src/error.rs new file mode 100644 index 00000000..1a4be7df --- /dev/null +++ b/lib/etc/libimaginteraction/src/error.rs @@ -0,0 +1,39 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +generate_error_module!( + generate_error_types!(InteractionError, InteractionErrorKind, + Unknown => "Unknown Error", + CLIError => "Error on commandline", + IdMissingError => "Commandline: ID missing", + StoreIdParsingError => "Error while parsing StoreId", + IdSelectingError => "Error while selecting id", + ConfigError => "Configuration error", + ConfigMissingError => "Configuration missing", + ConfigTypeError => "Config Type Error", + NoConfigError => "No configuration", + ReadlineHistoryFileCreationError => "Could not create history file for readline", + ReadlineError => "Readline error" + ); +); + +pub use self::error::InteractionError; +pub use self::error::InteractionErrorKind; +pub use self::error::MapErrInto; + diff --git a/lib/etc/libimaginteraction/src/filter.rs b/lib/etc/libimaginteraction/src/filter.rs new file mode 100644 index 00000000..5869a529 --- /dev/null +++ b/lib/etc/libimaginteraction/src/filter.rs @@ -0,0 +1,20 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +// A filter which uses crate:interactor to filter entries diff --git a/lib/etc/libimaginteraction/src/lib.rs b/lib/etc/libimaginteraction/src/lib.rs new file mode 100644 index 00000000..67c61cf4 --- /dev/null +++ b/lib/etc/libimaginteraction/src/lib.rs @@ -0,0 +1,53 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +#![deny( + non_camel_case_types, + non_snake_case, + path_statements, + trivial_numeric_casts, + unstable_features, + unused_allocation, + unused_import_braces, + unused_imports, + unused_mut, + unused_qualifications, + while_true, +)] + +extern crate spinner; +extern crate interactor; +extern crate ansi_term; +#[macro_use] extern crate lazy_static; +extern crate regex; +extern crate clap; +extern crate toml; +extern crate rustyline; + +extern crate libimagentryfilter; +extern crate libimagstore; +extern crate libimagutil; +#[macro_use] extern crate libimagerror; + +pub mod ask; +pub mod error; +pub mod filter; +pub mod result; +pub mod ui; + diff --git a/lib/etc/libimaginteraction/src/readline.rs b/lib/etc/libimaginteraction/src/readline.rs new file mode 100644 index 00000000..7b998dd0 --- /dev/null +++ b/lib/etc/libimaginteraction/src/readline.rs @@ -0,0 +1,129 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use error::InteractionErrorKind as IEK; +use error::MapErrInto; + +use toml::Value; + +use rustyline::{Config, Editor}; + +pub struct Readline { + editor: Editor, + history_file: PathBuf, + prompt: String, +} + +impl Readline { + + pub fn new(rt: &Runtime) -> Result { + let cfg = try!(rt.config().ok_or(IEK::NoConfigError)); + + let c = cfg.config(); + let histfile = try!(c.lookup("ui.cli.readline_history_file").ok_or(IEK::ConfigError)); + let histsize = try!(c.lookup("ui.cli.readline_history_size").ok_or(IEK::ConfigError)); + let histigndups = try!(c.lookup("ui.cli.readline_history_ignore_dups").ok_or(IEK::ConfigError)); + let histignspace = try!(c.lookup("ui.cli.readline_history_ignore_space").ok_or(IEK::ConfigError)); + let prompt = try!(c.lookup("ui.cli.readline_prompt").ok_or(IEK::ConfigError)); + + let histfile = try!(match histfile { + Value::String(s) => PathBuf::from(s), + _ => Err(IEK::ConfigTypeError.into_error()) + .map_err_into(IEK::ConfigError) + .map_err_into(IEK::ReadlineError) + }); + + let histsize = try!(match histsize { + Value::Integer(i) => i, + _ => Err(IEK::ConfigTypeError.into_error()) + .map_err_into(IEK::ConfigError) + .map_err_into(IEK::ReadlineError) + }); + + let histigndups = try!(match histigndups { + Value::Boolean(b) => b, + _ => Err(IEK::ConfigTypeError.into_error()) + .map_err_into(IEK::ConfigError) + .map_err_into(IEK::ReadlineError) + }); + + let histignspace = try!(match histignspace { + Value::Boolean(b) => b, + _ => Err(IEK::ConfigTypeError.into_error()) + .map_err_into(IEK::ConfigError) + .map_err_into(IEK::ReadlineError) + }); + + let prompt = try!(match prompt { + Value::String(s) => s, + _ => Err(IEK::ConfigTypeError.into_error()) + .map_err_into(IEK::ConfigError) + .map_err_into(IEK::ReadlineError) + }); + + let config = Config::builder(). + .max_history_size(histsize) + .history_ignore_dups(histigndups) + .history_ignore_space(histignspace) + .build(); + + let mut editor = Editor::new(config); + + if !histfile.exists() { + let _ = try!(File::create(histfile.clone()) + .map_err_into(IEK::ReadlineHistoryFileCreationError)); + } + + let _ = try!(editor.load_history(&histfile).map_err_into(ReadlineError)); + + Ok(Readline { + editor: editor, + history_file: histfile, + prompt: prompt, + }) + } + + pub fn read_line(&mut self) -> Result> { + use rustyline::ReadlineError; + use libimagutil::warn_result::*; + + match self.editor.readline(&self.prompt) { + Ok(line) => { + self.editor.add_history_line(&line); + self.editor + .save_history(&self.history_file) + .map_warn_err_str(|e| format!("Could not save history file {} -> {:?}", + self.history_file.display(), e)); + return line; + }, + Err(ReadlineError::Interrupted) => { + info!("CTRL-C"); + Ok(None) + }, + Err(ReadlineError::Eof) => { + info!("CTRL-D"); + Ok(None) + }, + Err(err) => Err(err).map_err_into(ReadlineError), + + } + } + +} + diff --git a/lib/etc/libimaginteraction/src/result.rs b/lib/etc/libimaginteraction/src/result.rs new file mode 100644 index 00000000..8eeaf47b --- /dev/null +++ b/lib/etc/libimaginteraction/src/result.rs @@ -0,0 +1,24 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::result::Result as RResult; + +use error::InteractionError; + +pub type Result = RResult; diff --git a/lib/etc/libimaginteraction/src/ui.rs b/lib/etc/libimaginteraction/src/ui.rs new file mode 100644 index 00000000..49c4619d --- /dev/null +++ b/lib/etc/libimaginteraction/src/ui.rs @@ -0,0 +1,83 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::path::PathBuf; + +use clap::{Arg, ArgMatches}; + +use libimagstore::storeid::StoreId; +use libimagerror::into::IntoError; + +use result::Result; +use error::MapErrInto; +use error::InteractionErrorKind as IEK; + +pub fn id_argument<'a, 'b>() -> Arg<'a, 'b> { + Arg::with_name(id_argument_name()) + .short(id_argument_short()) + .long(id_argument_long()) + .takes_value(true) + .multiple(true) + .help("Specify the Store-Id") +} + +pub fn id_argument_name() -> &'static str { + "id-argument" +} + +pub fn id_argument_short() -> &'static str { + "i" +} + +pub fn id_argument_long() -> &'static str { + "id" +} + +pub fn get_id(matches: &ArgMatches) -> Result> { + matches + .values_of(id_argument_name()) + .ok_or(IEK::IdMissingError.into_error()) + .map_err_into(IEK::CLIError) + .and_then(|vals| { + vals.into_iter() + .fold(Ok(vec![]), |acc, elem| { + acc.and_then(|mut v| { + let elem = StoreId::new_baseless(PathBuf::from(String::from(elem))); + let elem = try!(elem.map_err_into(IEK::StoreIdParsingError)); + v.push(elem); + Ok(v) + }) + }) + }) +} + +pub fn get_or_select_id(matches: &ArgMatches, store_path: &PathBuf) -> Result> { + use interactor::{pick_file, default_menu_cmd}; + + match get_id(matches).map_err_into(IEK::IdSelectingError) { + Ok(v) => Ok(v), + Err(_) => { + let path = store_path.clone(); + let p = try!(pick_file(default_menu_cmd, path).map_err_into(IEK::IdSelectingError)); + let id = try!(StoreId::new_baseless(p).map_err_into(IEK::StoreIdParsingError)); + Ok(vec![id]) + }, + } +} + -- cgit v1.2.3