Merge pull request #193 from matthiasbeyer/imag-counter/init

Imag counter/init
This commit is contained in:
Matthias Beyer 2016-03-16 16:35:46 +01:00
commit 8c220af300
13 changed files with 542 additions and 4 deletions

View file

@ -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.

19
imag-counter/Cargo.toml Normal file
View file

@ -0,0 +1,19 @@
[package]
name = "imag-counter"
version = "0.1.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[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"

View file

@ -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),
}
});
}

View file

@ -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");
});
}

134
imag-counter/src/main.rs Normal file
View file

@ -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
},
};
})
}

58
imag-counter/src/ui.rs Normal file
View file

@ -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")))
}

13
libimagcounter/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "libimagcounter"
version = "0.1.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies]
log = "0.3.5"
toml = "0.1.25"
semver = "0.2"
[dependencies.libimagstore]
path = "../libimagstore"

View file

@ -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<Counter> {
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<CounterName> {
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<i64> {
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<Counter> {
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))))
}
}

View file

@ -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<Box<Error>>,
}
impl CounterError {
/**
* Build a new CounterError from an CounterErrorKind, optionally with cause
*/
pub fn new(errtype: CounterErrorKind, cause: Option<Box<Error>>)
-> 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)
}
}

12
libimagcounter/src/lib.rs Normal file
View file

@ -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;

View file

@ -0,0 +1,6 @@
use std::result::Result as RResult;
use error::CounterError;
pub type Result<T> = RResult<T, CounterError>;

View file

@ -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))
}

View file

@ -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()