diff --git a/doc/src/06020-module-counter.md b/doc/src/06020-module-counter.md index d4e33596..a6272e40 100644 --- a/doc/src/06020-module-counter.md +++ b/doc/src/06020-module-counter.md @@ -1,4 +1,15 @@ ## Counter {#sec:modules:counter} -The Counter module. +The Counter module helps you counting things. + +In its current state it is capable of simple counting. You can create, list and +delete counters which are simply numbers and incremet, decrement, set and reset +them. + +Future plans include counting functionality which is able to save date and +possibly timestamp of your increments/decrements, so you can export this and use +(for example) R to visualize this data. + +Filters for selecting only certain time ranges when listing/exporting your +counters will be added as well. diff --git a/imag-counter/Cargo.toml b/imag-counter/Cargo.toml new file mode 100644 index 00000000..5ef71d51 --- /dev/null +++ b/imag-counter/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "imag-counter" +version = "0.1.0" +authors = ["Matthias Beyer "] + +[dependencies] +clap = "2.1.1" +log = "0.3.5" +version = "2.0.1" + +[dependencies.libimagrt] +path = "../libimagrt" + +[dependencies.libimagutil] +path = "../libimagutil" + +[dependencies.libimagcounter] +path = "../libimagcounter" + diff --git a/imag-counter/src/create.rs b/imag-counter/src/create.rs new file mode 100644 index 00000000..66f6590d --- /dev/null +++ b/imag-counter/src/create.rs @@ -0,0 +1,29 @@ +use std::str::FromStr; +use std::process::exit; + +use libimagrt::runtime::Runtime; +use libimagutil::trace::trace_error; +use libimagcounter::counter::Counter; + +pub fn create(rt: &Runtime) { + rt.cli() + .subcommand_matches("create") + .map(|scmd| { + debug!("Found 'create' subcommand..."); + + let name = scmd.value_of("name").unwrap(); // safe because clap enforces + let init : i64 = scmd + .value_of("initval") + .and_then(|i| FromStr::from_str(i).ok()) + .unwrap_or(0); + + match Counter::new(rt.store(), String::from(name.clone()), init) { + Err(e) => { + warn!("Could not create Counter '{}' with initial value '{}'", name, init); + trace_error(&e); + exit(1); + }, + Ok(_) => info!("Created Counter '{}' with initial value '{}'", name, init), + } + }); +} diff --git a/imag-counter/src/delete.rs b/imag-counter/src/delete.rs new file mode 100644 index 00000000..bb68f2f1 --- /dev/null +++ b/imag-counter/src/delete.rs @@ -0,0 +1,23 @@ +use std::process::exit; + +use libimagrt::runtime::Runtime; +use libimagutil::trace::trace_error; +use libimagcounter::counter::Counter; + +pub fn delete(rt: &Runtime) { + rt.cli() + .subcommand_matches("delete") + .map(|scmd| { + debug!("Found 'delete' subcommand..."); + + let name = String::from(scmd.value_of("name").unwrap()); // safe because clap enforces + + if let Err(e) = Counter::delete(name, rt.store()) { + trace_error(&e); + exit(1); + } + + info!("Ok"); + }); +} + diff --git a/imag-counter/src/main.rs b/imag-counter/src/main.rs new file mode 100644 index 00000000..cfc93142 --- /dev/null +++ b/imag-counter/src/main.rs @@ -0,0 +1,134 @@ +#[macro_use] extern crate log; +#[macro_use] extern crate version; +extern crate clap; + +extern crate libimagcounter; +extern crate libimagrt; +extern crate libimagutil; + +use std::process::exit; +use std::str::FromStr; + +use libimagrt::runtime::Runtime; +use libimagcounter::counter::Counter; +use libimagutil::trace::trace_error; +use libimagutil::key_value_split::IntoKeyValue; + +mod create; +mod delete; +mod ui; + +use ui::build_ui; +use create::create; +use delete::delete; + +enum Action { + Inc, + Dec, + Reset, + Set, +} + +fn main() { + let name = "imag-counter"; + let version = &version!()[..]; + let about = "Counter tool to count things"; + let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); + let rt = { + let rt = Runtime::new(ui); + if rt.is_ok() { + rt.unwrap() + } else { + println!("Could not set up Runtime"); + println!("{:?}", rt.err().unwrap()); + exit(1); + } + }; + + rt.init_logger(); + + debug!("Hello. Logging was just enabled"); + debug!("I already set up the Runtime object and build the commandline interface parser."); + debug!("Lets get rollin' ..."); + + rt.cli() + .subcommand_name() + .map_or_else(|| { + let (action, name) = { + if rt.cli().is_present("increment") { + (Action::Inc, rt.cli().value_of("increment").unwrap()) + } else if rt.cli().is_present("decrement") { + (Action::Dec, rt.cli().value_of("decrement").unwrap()) + } else if rt.cli().is_present("reset") { + (Action::Reset, rt.cli().value_of("reset").unwrap()) + } else /* rt.cli().is_present("set") */ { + (Action::Set, rt.cli().value_of("set").unwrap()) + } + }; + + match action { + Action::Inc => { + Counter::load(String::from(name), rt.store()) + .map(|mut counter| { + match counter.inc() { + Err(e) => { trace_error(&e); exit(1); }, + Ok(_) => info!("Ok"), + } + }) + }, + Action::Dec => { + Counter::load(String::from(name), rt.store()) + .map(|mut counter| { + match counter.dec() { + Err(e) => { trace_error(&e); exit(1); }, + Ok(_) => info!("Ok"), + } + }) + }, + Action::Reset => { + Counter::load(String::from(name), rt.store()) + .map(|mut counter| { + match counter.reset() { + Err(e) => { trace_error(&e); exit(1); }, + Ok(_) => info!("Ok"), + } + }) + }, + Action::Set => { + let kv = String::from(name).into_kv(); + if kv.is_none() { + warn!("Not a key-value pair: '{}'", name); + exit(1); + } + let (key, value) = kv.unwrap().into(); + let value = FromStr::from_str(&value[..]); + if value.is_err() { + warn!("Not a integer: '{:?}'", value); + exit(1); + } + let value : i64 = value.unwrap(); + Counter::load(String::from(key), rt.store()) + .map(|mut counter| { + match counter.set(value) { + Err(e) => { trace_error(&e); exit(1); }, + Ok(_) => info!("Ok"), + } + }) + }, + } + .map_err(|e| trace_error(&e)); + }, + |name| { + debug!("Call: {}", name); + match name { + "create" => create(&rt), + "delete" => delete(&rt), + _ => { + debug!("Unknown command"); // More error handling + }, + }; + }) + + + +} diff --git a/imag-counter/src/ui.rs b/imag-counter/src/ui.rs new file mode 100644 index 00000000..610a4241 --- /dev/null +++ b/imag-counter/src/ui.rs @@ -0,0 +1,58 @@ +use clap::{Arg, App, SubCommand}; + +pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { + app + .arg(Arg::with_name("increment") + .long("inc") + .short("i") + .takes_value(true) + .required(false) + .help("Increment a counter")) + + .arg(Arg::with_name("decrement") + .long("dec") + .short("d") + .takes_value(true) + .required(false) + .help("Decrement a counter")) + + .arg(Arg::with_name("reset") + .long("reset") + .takes_value(true) + .required(false) + .help("Reset a counter")) + + .arg(Arg::with_name("set") + .long("set") + .takes_value(true) + .required(false) + .help("Set a counter")) + + .subcommand(SubCommand::with_name("create") + .about("Create a counter") + .version("0.1") + .arg(Arg::with_name("name") + .long("name") + .short("n") + .takes_value(true) + .required(true) + .help("Create counter with this name")) + .arg(Arg::with_name("initval") + .long("init") + .short("i") + .takes_value(true) + .required(false) + .help("Initial value"))) + + .subcommand(SubCommand::with_name("delete") + .about("Delete a counter") + .version("0.1") + .arg(Arg::with_name("name") + .long("name") + .short("n") + .takes_value(true) + .required(true) + .help("Create counter with this name"))) +} + + diff --git a/libimagcounter/Cargo.toml b/libimagcounter/Cargo.toml new file mode 100644 index 00000000..15ec9add --- /dev/null +++ b/libimagcounter/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "libimagcounter" +version = "0.1.0" +authors = ["Matthias Beyer "] + +[dependencies] +log = "0.3.5" +toml = "0.1.25" +semver = "0.2" + +[dependencies.libimagstore] +path = "../libimagstore" + diff --git a/libimagcounter/src/counter.rs b/libimagcounter/src/counter.rs new file mode 100644 index 00000000..d233e80d --- /dev/null +++ b/libimagcounter/src/counter.rs @@ -0,0 +1,144 @@ +use std::convert::From; +use std::convert::Into; +use std::ops::DerefMut; +use std::ops::Deref; + +use toml::Value; + +use std::collections::BTreeMap; + +use libimagstore::store::Store; +use libimagstore::store::FileLockEntry; +use libimagstore::storeid::StoreId; +use libimagstore::error::StoreError; +use libimagstore::store::Entry; +use libimagstore::storeid::IntoStoreId; + +use module_path::ModuleEntryPath; +use result::Result; +use error::CounterError as CE; +use error::CounterErrorKind as CEK; + +pub type CounterName = String; + +pub struct Counter<'a> { + fle: FileLockEntry<'a>, +} + +impl<'a> Counter<'a> { + + pub fn new(store: &Store, name: CounterName, init: i64) -> Result { + use std::ops::DerefMut; + + debug!("Creating new counter: '{}' with value: {}", name, init); + let fle = { + let mut lockentry = store.create(ModuleEntryPath::new(name.clone()).into_storeid()); + if lockentry.is_err() { + return Err(CE::new(CEK::StoreWriteError, Some(Box::new(lockentry.err().unwrap())))); + } + let mut lockentry = lockentry.unwrap(); + + { + let mut entry = lockentry.deref_mut(); + let mut header = entry.get_header_mut(); + let setres = header.set("counter", Value::Table(BTreeMap::new())); + if setres.is_err() { + return Err(CE::new(CEK::StoreWriteError, Some(Box::new(setres.err().unwrap())))); + } + + let setres = header.set("counter.name", Value::String(name)); + if setres.is_err() { + return Err(CE::new(CEK::StoreWriteError, Some(Box::new(setres.err().unwrap())))); + } + + let setres = header.set("counter.value", Value::Integer(init)); + if setres.is_err() { + return Err(CE::new(CEK::StoreWriteError, Some(Box::new(setres.err().unwrap())))); + } + } + + lockentry + }; + + Ok(Counter { fle: fle }) + } + + pub fn inc(&mut self) -> Result<()> { + let mut header = self.fle.deref_mut().get_header_mut(); + match header.read("counter.value") { + Ok(Some(Value::Integer(i))) => { + header.set("counter.value", Value::Integer(i + 1)) + .map_err(|e| CE::new(CEK::StoreWriteError, Some(Box::new(e)))) + .map(|_| ()) + }, + Err(e) => Err(CE::new(CEK::StoreReadError, Some(Box::new(e)))), + _ => Err(CE::new(CEK::StoreReadError, None)), + } + } + + pub fn dec(&mut self) -> Result<()> { + let mut header = self.fle.deref_mut().get_header_mut(); + match header.read("counter.value") { + Ok(Some(Value::Integer(i))) => { + header.set("counter.value", Value::Integer(i - 1)) + .map_err(|e| CE::new(CEK::StoreWriteError, Some(Box::new(e)))) + .map(|_| ()) + }, + Err(e) => Err(CE::new(CEK::StoreReadError, Some(Box::new(e)))), + _ => Err(CE::new(CEK::StoreReadError, None)), + } + } + + pub fn reset(&mut self) -> Result<()> { + let mut header = self.fle.deref_mut().get_header_mut(); + header.set("counter.value", Value::Integer(0)) + .map_err(|e| CE::new(CEK::StoreWriteError, Some(Box::new(e)))) + .map(|_| ()) + } + + pub fn set(&mut self, v: i64) -> Result<()> { + let mut header = self.fle.deref_mut().get_header_mut(); + header.set("counter.value", Value::Integer(v)) + .map_err(|e| CE::new(CEK::StoreWriteError, Some(Box::new(e)))) + .map(|_| ()) + } + + pub fn name(&self) -> Result { + let mut header = self.fle.deref().get_header(); + header.read("counter.name") + .map_err(|e| CE::new(CEK::StoreWriteError, Some(Box::new(e)))) + .and_then(|v| { + match v { + Some(Value::String(s)) => Ok(s), + _ => Err(CE::new(CEK::HeaderTypeError, None)), + } + }) + } + + pub fn value(&self) -> Result { + let mut header = self.fle.deref().get_header(); + header.read("counter.value") + .map_err(|e| CE::new(CEK::StoreWriteError, Some(Box::new(e)))) + .and_then(|v| { + match v { + Some(Value::Integer(i)) => Ok(i), + _ => Err(CE::new(CEK::HeaderTypeError, None)), + } + }) + } + + pub fn load(name: CounterName, store: &Store) -> Result { + debug!("Loading counter: '{}'", name); + match store.retrieve(ModuleEntryPath::new(name).into_storeid()) { + Err(e) => Err(CE::new(CEK::StoreReadError, Some(Box::new(e)))), + Ok(c) => Ok(Counter { fle: c }), + } + } + + pub fn delete(name: CounterName, store: &Store) -> Result<()> { + debug!("Deleting counter: '{}'", name); + store.delete(ModuleEntryPath::new(name).into_storeid()) + .map_err(|e| CE::new(CEK::StoreWriteError, Some(Box::new(e)))) + } +} + diff --git a/libimagcounter/src/error.rs b/libimagcounter/src/error.rs new file mode 100644 index 00000000..78d0cf46 --- /dev/null +++ b/libimagcounter/src/error.rs @@ -0,0 +1,89 @@ +use std::error::Error; +use std::fmt::Error as FmtError; +use std::clone::Clone; +use std::fmt::{Debug, Display, Formatter}; +use std::fmt; +use std::convert::From; + +/** + * Kind of error + */ +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum CounterErrorKind { + StoreReadError, + StoreWriteError, + HeaderTypeError, + HeaderFieldMissingError, +} + +fn counter_error_type_as_str(e: &CounterErrorKind) -> &'static str { + match e { + &CounterErrorKind::StoreReadError => "Store read error", + &CounterErrorKind::StoreWriteError => "Store write error", + &CounterErrorKind::HeaderTypeError => "Header type error", + &CounterErrorKind::HeaderFieldMissingError => "Header field missing error", + } +} + +impl Display for CounterErrorKind { + + fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { + try!(write!(fmt, "{}", counter_error_type_as_str(self))); + Ok(()) + } + +} + +/** + * Store error type + */ +#[derive(Debug)] +pub struct CounterError { + err_type: CounterErrorKind, + cause: Option>, +} + +impl CounterError { + + /** + * Build a new CounterError from an CounterErrorKind, optionally with cause + */ + pub fn new(errtype: CounterErrorKind, cause: Option>) + -> CounterError + { + CounterError { + err_type: errtype, + cause: cause, + } + } + + /** + * Get the error type of this CounterError + */ + pub fn err_type(&self) -> CounterErrorKind { + self.err_type.clone() + } + +} + +impl Display for CounterError { + + fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { + try!(write!(fmt, "[{}]", counter_error_type_as_str(&self.err_type.clone()))); + Ok(()) + } + +} + +impl Error for CounterError { + + fn description(&self) -> &str { + counter_error_type_as_str(&self.err_type.clone()) + } + + fn cause(&self) -> Option<&Error> { + self.cause.as_ref().map(|e| &**e) + } + +} + diff --git a/libimagcounter/src/lib.rs b/libimagcounter/src/lib.rs new file mode 100644 index 00000000..8b7a333a --- /dev/null +++ b/libimagcounter/src/lib.rs @@ -0,0 +1,12 @@ +extern crate toml; +#[macro_use] extern crate log; +#[macro_use] extern crate semver; + +#[macro_use] extern crate libimagstore; + +module_entry_path_mod!("counter", "0.1.0"); + +pub mod counter; +pub mod error; +pub mod result; + diff --git a/libimagcounter/src/result.rs b/libimagcounter/src/result.rs new file mode 100644 index 00000000..91a26599 --- /dev/null +++ b/libimagcounter/src/result.rs @@ -0,0 +1,6 @@ +use std::result::Result as RResult; + +use error::CounterError; + +pub type Result = RResult; + diff --git a/libimagstore/src/store.rs b/libimagstore/src/store.rs index 7a11b8c2..50ba51fe 100644 --- a/libimagstore/src/store.rs +++ b/libimagstore/src/store.rs @@ -230,7 +230,7 @@ impl Store { /// Delete an entry pub fn delete(&self, id: StoreId) -> Result<()> { let id = self.storify_id(id); - let entries_lock = self.entries.write(); + let mut entries_lock = self.entries.write(); if entries_lock.is_err() { return Err(StoreError::new(StoreErrorKind::LockPoisoned, None)) } diff --git a/libimagutil/src/trace.rs b/libimagutil/src/trace.rs index cdb65aa5..58a009a5 100644 --- a/libimagutil/src/trace.rs +++ b/libimagutil/src/trace.rs @@ -29,7 +29,7 @@ pub fn trace_error(e: &Error) { /// Output is the same as for `trace_error()`, though there are only `max` levels printed. pub fn trace_error_maxdepth(e: &Error, max: u64) { let n = count_error_causes(e); - write!(stderr(), "{}/{} Levels of errors will be printed", (if max > n { n } else { max }), n); + write!(stderr(), "{}/{} Levels of errors will be printed\n", (if max > n { n } else { max }), n); print_trace_maxdepth(n, e, max); write!(stderr(), ""); } @@ -48,7 +48,7 @@ pub fn trace_error_dbg(e: &Error) { fn print_trace_maxdepth(idx: u64, e: &Error, max: u64) -> Option<&Error> { if e.cause().is_some() && idx > 0 { print_trace_maxdepth(idx - 1, e.cause().unwrap(), max); - write!(stderr(), " -- caused:"); + write!(stderr(), " -- caused:\n"); } write!(stderr(), "Error {:>4} : {}", idx, e.description()); e.cause()