From a41614fd007c4ba8a3af9ba36bceee05e628a50d Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 23 Jan 2016 15:35:31 +0100 Subject: [PATCH 01/35] Init: imag-store binary --- imag-store/Cargo.toml | 6 ++++++ imag-store/src/main.rs | 3 +++ 2 files changed, 9 insertions(+) create mode 100644 imag-store/Cargo.toml create mode 100644 imag-store/src/main.rs diff --git a/imag-store/Cargo.toml b/imag-store/Cargo.toml new file mode 100644 index 00000000..13632323 --- /dev/null +++ b/imag-store/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "imag-store" +version = "0.1.0" +authors = ["Matthias Beyer "] + +[dependencies] diff --git a/imag-store/src/main.rs b/imag-store/src/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/imag-store/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 86e810cc43d307ebcd6b75da03545de4ed02d0a4 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 23 Jan 2016 15:38:14 +0100 Subject: [PATCH 02/35] Add dependencies --- imag-store/Cargo.toml | 14 ++++++++++++++ imag-store/src/main.rs | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/imag-store/Cargo.toml b/imag-store/Cargo.toml index 13632323..cd448864 100644 --- a/imag-store/Cargo.toml +++ b/imag-store/Cargo.toml @@ -4,3 +4,17 @@ version = "0.1.0" authors = ["Matthias Beyer "] [dependencies] +clap = "1.5.5" +log = "0.3.5" +version = "1.1.0" +toml = "0.1.25" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagrt] +path = "../libimagrt" + +[dependencies.libimagutil] +path = "../libimagutil" + diff --git a/imag-store/src/main.rs b/imag-store/src/main.rs index e7a11a96..3b8165e9 100644 --- a/imag-store/src/main.rs +++ b/imag-store/src/main.rs @@ -1,3 +1,12 @@ +extern crate clap; +#[macro_use] extern crate log; +extern crate toml; +#[macro_use] extern crate version; + +extern crate libimagrt; +extern crate libimagstore; +extern crate libimagutil; + fn main() { println!("Hello, world!"); } From 2c876a371491a4d5f19187c287ffe0f13bf9d0d9 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 23 Jan 2016 16:01:17 +0100 Subject: [PATCH 03/35] Add interface builder function --- imag-store/src/main.rs | 4 ++ imag-store/src/ui.rs | 109 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 imag-store/src/ui.rs diff --git a/imag-store/src/main.rs b/imag-store/src/main.rs index 3b8165e9..f8e382c5 100644 --- a/imag-store/src/main.rs +++ b/imag-store/src/main.rs @@ -7,6 +7,10 @@ extern crate libimagrt; extern crate libimagstore; extern crate libimagutil; +mod ui; + +use ui::build_ui; + fn main() { println!("Hello, world!"); } diff --git a/imag-store/src/ui.rs b/imag-store/src/ui.rs new file mode 100644 index 00000000..b7ac4e16 --- /dev/null +++ b/imag-store/src/ui.rs @@ -0,0 +1,109 @@ +use clap::{Arg, App, SubCommand}; + +pub fn build_ui<'a>(app: App<'a, 'a, 'a, 'a, 'a, 'a>) -> App<'a, 'a, 'a, 'a, 'a, 'a> { + app.subcommand(SubCommand::with_name("create") + .about("Create an entry from the store") + .version("0.1") + .arg(Arg::with_name("path") + .long("path") + .short("p") + .takes_value(true) + .required(true) + .help("Create at this store path")) + .arg(Arg::with_name("from-raw") + .long("from-raw") + .takes_value(true) + .help("Create a new entry by reading this file ('-' for stdin)")) + .subcommand(SubCommand::with_name("entry") + .about("Create an entry via commandline") + .version("0.1") + .arg(Arg::with_name("content") + .long("content") + .short("c") + .takes_value(true) + .help("Content for the Entry from this file ('-' for stdin)")) + .arg(Arg::with_name("header") + .long("header") + .short("h") + .takes_value(true) + .multiple(true) + .help("Set a header field. Specify as 'header.field.value=value', multiple allowed")) + ) + ) + + .subcommand(SubCommand::with_name("retrieve") + .about("Get an entry from the store") + .version("0.1") + .arg(Arg::with_name("id") + .long("id") + .short("i") + .takes_value(true) + .required(true) + .help("Retreive by Store Path, where root (/) is the store itself")) + .arg(Arg::with_name("content") + .long("content") + .short("c") + .help("Print content")) + .arg(Arg::with_name("header") + .long("header") + .short("h") + .help("Print header")) + .arg(Arg::with_name("header-json") + .long("header-json") + .short("j") + .help("Print header as json")) + .arg(Arg::with_name("raw") + .long("raw") + .short("r") + .help("Print Entries as they are in the store")) + .subcommand(SubCommand::with_name("filter-header") + .about("Retrieve Entries by filtering") + .version("0.1") + .arg(Arg::with_name("header-field-where") + .long("where") + .short("w") + .takes_value(true) + .help("Filter with 'header.field=foo' where the header field 'header.field' equals 'foo'") + ) + .arg(Arg::with_name("header-field-grep") + .long("grep") + .short("g") + .takes_value(true) + .help("Filter with 'header.field=[a-zA-Z0-9]*' where the header field 'header.field' matches '[a-zA-Z0-9]*'")) + ) + ) + + .subcommand(SubCommand::with_name("update") + .about("Get an entry from the store") + .version("0.1") + .arg(Arg::with_name("id") + .long("id") + .short("i") + .takes_value(true) + .required(true) + .help("Update Store Entry with this path. Root (/) is the store itself")) + .arg(Arg::with_name("content") + .long("content") + .short("c") + .takes_value(true) + .help("Take the content for the new Entry from this file ('-' for stdin)")) + .arg(Arg::with_name("header") + .long("header") + .short("h") + .takes_value(true) + .multiple(true) + .help("Set a header field. Specify as 'header.field.value=value', multiple allowed")) + ) + + .subcommand(SubCommand::with_name("delete") + .about("Delete an entry from the store") + .version("0.1") + .arg(Arg::with_name("id") + .long("id") + .short("i") + .takes_value(true) + .required(true) + .help("Remove Store Entry with this path. Root (/) is the store itself")) + ) +} + From fa4beeebcdf48a275ef5111ff87767a853113f13 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 23 Jan 2016 16:29:04 +0100 Subject: [PATCH 04/35] Add initial codebase --- imag-store/src/create.rs | 5 ++++ imag-store/src/delete.rs | 5 ++++ imag-store/src/main.rs | 55 +++++++++++++++++++++++++++++++++++++++- imag-store/src/read.rs | 5 ++++ imag-store/src/update.rs | 5 ++++ 5 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 imag-store/src/create.rs create mode 100644 imag-store/src/delete.rs create mode 100644 imag-store/src/read.rs create mode 100644 imag-store/src/update.rs diff --git a/imag-store/src/create.rs b/imag-store/src/create.rs new file mode 100644 index 00000000..16b26948 --- /dev/null +++ b/imag-store/src/create.rs @@ -0,0 +1,5 @@ +use libimagrt::runtime::Runtime; + +pub fn create(rt: &Runtime) { +} + diff --git a/imag-store/src/delete.rs b/imag-store/src/delete.rs new file mode 100644 index 00000000..df13b32b --- /dev/null +++ b/imag-store/src/delete.rs @@ -0,0 +1,5 @@ +use libimagrt::runtime::Runtime; + +pub fn delete(rt: &Runtime) { +} + diff --git a/imag-store/src/main.rs b/imag-store/src/main.rs index f8e382c5..1f087636 100644 --- a/imag-store/src/main.rs +++ b/imag-store/src/main.rs @@ -7,10 +7,63 @@ extern crate libimagrt; extern crate libimagstore; extern crate libimagutil; +use libimagrt::runtime::Runtime; +use std::process::exit; + mod ui; +mod create; +mod read; +mod update; +mod delete; use ui::build_ui; +use create::create; +use read::read; +use update::update; +use delete::delete; fn main() { - println!("Hello, world!"); + let name = "imag-store"; + let version = &version!()[..]; + let about = "Direct interface to the store. Use with great care!"; + 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( + || { + debug!("No command"); + // More error handling + }, + |name| { + debug!("Call: {}", name); + match name { + "create" => create(&rt), + "read" => read(&rt), + "update" => update(&rt), + "delete" => delete(&rt), + _ => { + debug!("Unknown command"); + // More error handling + }, + }; + } + ) } + diff --git a/imag-store/src/read.rs b/imag-store/src/read.rs new file mode 100644 index 00000000..af0cc5c3 --- /dev/null +++ b/imag-store/src/read.rs @@ -0,0 +1,5 @@ +use libimagrt::runtime::Runtime; + +pub fn read(rt: &Runtime) { +} + diff --git a/imag-store/src/update.rs b/imag-store/src/update.rs new file mode 100644 index 00000000..71cdcd69 --- /dev/null +++ b/imag-store/src/update.rs @@ -0,0 +1,5 @@ +use libimagrt::runtime::Runtime; + +pub fn update(rt: &Runtime) { +} + From bd0d44d4b2ca193b98a70793b2f68f378f17e4d0 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 24 Jan 2016 20:19:56 +0100 Subject: [PATCH 05/35] Add error module for imag-store --- imag-store/src/error.rs | 81 +++++++++++++++++++++++++++++++++++++++++ imag-store/src/main.rs | 1 + 2 files changed, 82 insertions(+) create mode 100644 imag-store/src/error.rs diff --git a/imag-store/src/error.rs b/imag-store/src/error.rs new file mode 100644 index 00000000..a9884250 --- /dev/null +++ b/imag-store/src/error.rs @@ -0,0 +1,81 @@ +use std::error::Error; +use std::fmt::Error as FmtError; +use std::clone::Clone; +use std::fmt::{Display, Formatter}; + +/** + * Kind of store error + */ +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum StoreErrorKind { + BackendError, + NoCommandlineCall, + // maybe more +} + +fn store_error_type_as_str(e: &StoreErrorKind) -> &'static str { + match e { + &StoreErrorKind::BackendError => "Backend Error", + &StoreErrorKind::NoCommandlineCall => "No commandline call", + } +} + +impl Display for StoreErrorKind { + + fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { + try!(write!(fmt, "{}", store_error_type_as_str(self))); + Ok(()) + } + +} + +#[derive(Debug)] +pub struct StoreError { + err_type: StoreErrorKind, + cause: Option>, +} + +impl StoreError { + + /** + * Build a new StoreError from an StoreErrorKind, optionally with cause + */ + pub fn new(errtype: StoreErrorKind, cause: Option>) + -> StoreError + { + StoreError { + err_type: errtype, + cause: cause, + } + } + + /** + * Get the error type of this StoreError + */ + pub fn err_type(&self) -> StoreErrorKind { + self.err_type.clone() + } + +} + +impl Display for StoreError { + + fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { + try!(write!(fmt, "[{}]", store_error_type_as_str(&self.err_type.clone()))); + Ok(()) + } + +} + +impl Error for StoreError { + + fn description(&self) -> &str { + store_error_type_as_str(&self.err_type.clone()) + } + + fn cause(&self) -> Option<&Error> { + self.cause.as_ref().map(|e| &**e) + } + +} + diff --git a/imag-store/src/main.rs b/imag-store/src/main.rs index 1f087636..62188035 100644 --- a/imag-store/src/main.rs +++ b/imag-store/src/main.rs @@ -10,6 +10,7 @@ extern crate libimagutil; use libimagrt::runtime::Runtime; use std::process::exit; +mod error; mod ui; mod create; mod read; From dbd6388946abe429568bd6fd3cb9ecce13ab622e Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 24 Jan 2016 20:20:04 +0100 Subject: [PATCH 06/35] Implement Store::create() interface --- imag-store/src/create.rs | 181 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/imag-store/src/create.rs b/imag-store/src/create.rs index 16b26948..8752c84a 100644 --- a/imag-store/src/create.rs +++ b/imag-store/src/create.rs @@ -1,5 +1,186 @@ +use std::collections::BTreeMap; +use std::path::PathBuf; +use std::io::stdin; +use std::fs::OpenOptions; +use std::result::Result as RResult; +use std::io::Read; +use std::ops::DerefMut; +use std::str::Split; + +use clap::ArgMatches; +use toml::Table; +use toml::Value; + use libimagrt::runtime::Runtime; +use libimagstore::store::Entry; +use libimagstore::store::EntryHeader; +use libimagutil::key_value_split::IntoKeyValue; + +use error::StoreError; +use error::StoreErrorKind; +use util::build_entry_path; + +type Result = RResult; pub fn create(rt: &Runtime) { + rt.cli() + .subcommand_matches("create") + .map(|scmd| { + debug!("Found 'create' subcommand..."); + + // unwrap is safe as value is required + let path = build_entry_path(rt, scmd.value_of("path").unwrap()); + debug!("path = {:?}", path); + + scmd.subcommand_matches("entry") + .map(|entry| create_from_cli_spec(rt, scmd, &path)) + .ok_or(()) // hackythehackhack + .map_err(|_| { + create_from_source(rt, scmd, &path) + .unwrap_or_else(|e| debug!("Error building Entry: {:?}", e)) + }); + }); +} + +fn create_from_cli_spec(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Result<()> { + let content = matches.subcommand_matches("entry") + .map(|entry_subcommand| { + debug!("Found entry subcommand, parsing content"); + entry_subcommand + .value_of("content") + .map(String::from) + .unwrap_or_else(|| { + entry_subcommand + .value_of("content-from") + .map(|src| entry_from_raw(src)) + .unwrap_or(String::new()) + }) + }) + .unwrap_or_else(|| { + debug!("Didn't find entry subcommand, getting raw content"); + matches.value_of("from-raw") + .map(|raw_src| entry_from_raw(raw_src)) + .unwrap_or(String::new()) + }); + + debug!("Got content with len = {}", content.len()); + + rt.store() + .create(PathBuf::from(path)) + .map(|mut element| { + { + let mut e_content = element.get_content_mut(); + *e_content = content; + debug!("New content set"); + } + { + let mut e_header = element.get_header_mut(); + matches.subcommand_matches("entry") + .map(|entry_matches| { + *e_header = build_toml_header(entry_matches, EntryHeader::new()); + debug!("New header set"); + }); + } + }) + .map_err(|e| StoreError::new(StoreErrorKind::BackendError, Some(Box::new(e)))) +} + +fn create_from_source(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Result<()> { + let content = matches + .value_of("from-raw") + .ok_or(StoreError::new(StoreErrorKind::NoCommandlineCall, None)) + .map(|raw_src| entry_from_raw(raw_src)); + + if content.is_err() { + return content.map(|_| ()); + } + let content = content.unwrap(); + debug!("Content with len = {}", content.len()); + + Entry::from_str(path.clone(), &content[..]) + .map(|mut new_e| { + rt.store() + .create(path.clone()) + .map(|mut old_e| { + *old_e.deref_mut() = new_e; + }); + + debug!("Entry build"); + }) + .map_err(|serr| StoreError::new(StoreErrorKind::BackendError, Some(Box::new(serr)))) +} + +fn entry_from_raw(raw_src: &str) -> String { + let mut content = String::new(); + if raw_src == "-" { + debug!("Reading entry from stdin"); + let res = stdin().read_to_string(&mut content); + debug!("Read {:?} bytes", res); + } else { + debug!("Reading entry from file at {:?}", raw_src); + OpenOptions::new() + .read(true) + .write(false) + .create(false) + .open(raw_src) + .and_then(|mut f| f.read_to_string(&mut content)); + } + content +} + +fn build_toml_header(matches: &ArgMatches, header: EntryHeader) -> EntryHeader { + if let Some(headerspecs) = matches.values_of("header") { + let mut main = BTreeMap::new(); + for tpl in headerspecs.into_iter().filter_map(|hs| String::from(hs).into_kv()) { + let (key, value) = tpl.into(); + let mut split = key.split("."); + let current = split.next(); + if current.is_some() { + insert_key_into(String::from(current.unwrap()), &mut split, value, &mut main); + } + } + } + header +} + +fn insert_key_into(current: String, + rest_path: &mut Split<&str>, + value: String, + map: &mut BTreeMap) { + let next = rest_path.next(); + + if next.is_none() { + map.insert(current, parse_value(value)); + } else { + if map.contains_key(¤t) { + match map.get_mut(¤t).unwrap() { + &mut Value::Table(ref mut t) => { + insert_key_into(String::from(next.unwrap()), rest_path, value, t); + }, + _ => unreachable!(), + } + } else { + let mut submap = BTreeMap::new(); + insert_key_into(String::from(next.unwrap()), rest_path, value, &mut submap); + map.insert(current, Value::Table(submap)); + } + } +} + +fn parse_value(value: String) -> Value { + fn is_ary(v: &String) -> bool { + v.chars().next() == Some('[') && v.chars().last() == Some(']') && v.len() >= 3 + } + + if value == "true" { + Value::Boolean(true) + } else if value == "false" { + Value::Boolean(false) + } else if is_ary(&value) { + let sub = &value[1..(value.len()-1)]; + Value::Array(sub.split(",").map(|v| parse_value(String::from(v))).collect()) + } else { + Value::String(value) + } } From 9c0796d7b3c0fef71d688794a3e5fadf8577b9c6 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Mon, 25 Jan 2016 20:09:48 +0100 Subject: [PATCH 07/35] Implement Store::retrieve() interface Rename: read.rs -> retrieve.rs --- imag-store/src/main.rs | 6 +-- imag-store/src/read.rs | 5 --- imag-store/src/retrieve.rs | 84 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 8 deletions(-) delete mode 100644 imag-store/src/read.rs create mode 100644 imag-store/src/retrieve.rs diff --git a/imag-store/src/main.rs b/imag-store/src/main.rs index 62188035..950228e2 100644 --- a/imag-store/src/main.rs +++ b/imag-store/src/main.rs @@ -13,13 +13,13 @@ use std::process::exit; mod error; mod ui; mod create; -mod read; +mod retrieve; mod update; mod delete; use ui::build_ui; use create::create; -use read::read; +use retrieve::retrieve; use update::update; use delete::delete; @@ -56,7 +56,7 @@ fn main() { debug!("Call: {}", name); match name { "create" => create(&rt), - "read" => read(&rt), + "retrieve" => retrieve(&rt), "update" => update(&rt), "delete" => delete(&rt), _ => { diff --git a/imag-store/src/read.rs b/imag-store/src/read.rs deleted file mode 100644 index af0cc5c3..00000000 --- a/imag-store/src/read.rs +++ /dev/null @@ -1,5 +0,0 @@ -use libimagrt::runtime::Runtime; - -pub fn read(rt: &Runtime) { -} - diff --git a/imag-store/src/retrieve.rs b/imag-store/src/retrieve.rs new file mode 100644 index 00000000..311f6bd1 --- /dev/null +++ b/imag-store/src/retrieve.rs @@ -0,0 +1,84 @@ +use std::path::PathBuf; +use std::ops::Deref; +use std::fmt::Display; + +use clap::ArgMatches; +use toml::Value; + +use libimagstore::store::FileLockEntry; +use libimagrt::runtime::Runtime; + +use util::build_entry_path; + +pub fn retrieve(rt: &Runtime) { + rt.cli() + .subcommand_matches("retrieve") + .map(|scmd| { + let path = scmd.value_of("id").map(|id| build_entry_path(rt, id)).unwrap(); + debug!("path = {:?}", path); + rt.store() + // "id" must be present, enforced via clap spec + .retrieve(path) + .map(|e| print_entry(rt, scmd, e)) + .map_err(|e| { + debug!("No entry."); + debug!("{}", e); + }) + + }); +} + +fn print_entry(rt: &Runtime, scmd: &ArgMatches, e: FileLockEntry) { + if do_print_raw(scmd) { + debug!("Printing raw content..."); + println!("{}", e.deref().to_str()); + } else if do_filter(scmd) { + debug!("Filtering..."); + warn!("Filtering via header specs is currently now supported."); + warn!("Will fail now!"); + unimplemented!() + } else { + debug!("Printing structured..."); + let entry = e.deref(); + if do_print_header(scmd) { + debug!("Printing header..."); + if do_print_header_as_json(rt.cli()) { + debug!("Printing header as json..."); + warn!("Printing as JSON currently not supported."); + warn!("Will fail now!"); + unimplemented!() + } else { + debug!("Printing header as TOML..."); + // We have to Value::Table() for Display + println!("{}", Value::Table(entry.get_header().toml().clone())) + } + } + + if do_print_content(scmd) { + debug!("Printing content..."); + println!("{}", entry.get_content()); + } + + } +} + +fn do_print_header(m: &ArgMatches) -> bool { + m.is_present("header") +} + +fn do_print_header_as_json(m: &ArgMatches) -> bool { + m.is_present("header-json") +} + +fn do_print_content(m: &ArgMatches) -> bool { + m.is_present("content") +} + +fn do_print_raw(m: &ArgMatches) -> bool { + m.is_present("raw") +} + +fn do_filter(m: &ArgMatches) -> bool { + m.subcommand_matches("filter-header").is_some() +} + From 1fb29a11c8ba7b9e9b80f0578d26f91cdb497628 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 28 Jan 2016 19:41:09 +0100 Subject: [PATCH 08/35] Implement Store::update() interface --- imag-store/src/update.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/imag-store/src/update.rs b/imag-store/src/update.rs index 71cdcd69..25f1cd92 100644 --- a/imag-store/src/update.rs +++ b/imag-store/src/update.rs @@ -1,5 +1,29 @@ +use std::path::PathBuf; +use std::ops::DerefMut; + use libimagrt::runtime::Runtime; +use util::build_toml_header; +use util::build_entry_path; pub fn update(rt: &Runtime) { + rt.cli() + .subcommand_matches("update") + .map(|scmd| { + rt.store() + .retrieve(scmd.value_of("id").map(|id| build_entry_path(rt, id)).unwrap()) + .map(|mut locked_e| { + let mut e = locked_e.deref_mut(); + + scmd.value_of("content") + .map(|new_content| { + *e.get_content_mut() = String::from(new_content); + debug!("New content set"); + }); + + *e.get_header_mut() = build_toml_header(scmd, e.get_header().clone()); + debug!("New header set"); + }) + }); + } From 947d7900bf48a724aa7717e067ea4e81d4aa82ce Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 26 Jan 2016 19:59:55 +0100 Subject: [PATCH 09/35] Implement Store::delete() interface --- imag-store/src/delete.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/imag-store/src/delete.rs b/imag-store/src/delete.rs index df13b32b..d645f743 100644 --- a/imag-store/src/delete.rs +++ b/imag-store/src/delete.rs @@ -1,5 +1,33 @@ +use std::path::PathBuf; + use libimagrt::runtime::Runtime; +use util::build_entry_path; + pub fn delete(rt: &Runtime) { + use std::process::exit; + + rt.cli() + .subcommand_matches("delete") + .map(|sub| { + sub.value_of("id") + .map(|id| { + debug!("Deleting file at {:?}", id); + rt.store() + .delete(build_entry_path(rt, id)) + .map_err(|e| { + warn!("Error: {:?}", e); + exit(1); + }) + }) + .or_else(|| { + warn!("No ID passed. Will exit now"); + exit(1); + }) + }) + .or_else(|| { + warn!("No subcommand 'delete'. Will exit now"); + exit(1); + }); } From 7fec8a1b23e3b94ce98f9b42c442cae9868132dc Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 28 Jan 2016 19:40:58 +0100 Subject: [PATCH 10/35] Move commandline-header parsing code to utility module as the code can be re-used in the update() implementation. --- imag-store/src/create.rs | 62 +----------------------------------- imag-store/src/main.rs | 1 + imag-store/src/util.rs | 68 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 61 deletions(-) create mode 100644 imag-store/src/util.rs diff --git a/imag-store/src/create.rs b/imag-store/src/create.rs index 8752c84a..bb3eaad0 100644 --- a/imag-store/src/create.rs +++ b/imag-store/src/create.rs @@ -1,24 +1,20 @@ -use std::collections::BTreeMap; use std::path::PathBuf; use std::io::stdin; use std::fs::OpenOptions; use std::result::Result as RResult; use std::io::Read; use std::ops::DerefMut; -use std::str::Split; use clap::ArgMatches; -use toml::Table; -use toml::Value; use libimagrt::runtime::Runtime; use libimagstore::store::Entry; use libimagstore::store::EntryHeader; -use libimagutil::key_value_split::IntoKeyValue; use error::StoreError; use error::StoreErrorKind; use util::build_entry_path; +use util::build_toml_header; type Result = RResult; @@ -128,59 +124,3 @@ fn entry_from_raw(raw_src: &str) -> String { content } -fn build_toml_header(matches: &ArgMatches, header: EntryHeader) -> EntryHeader { - if let Some(headerspecs) = matches.values_of("header") { - let mut main = BTreeMap::new(); - for tpl in headerspecs.into_iter().filter_map(|hs| String::from(hs).into_kv()) { - let (key, value) = tpl.into(); - let mut split = key.split("."); - let current = split.next(); - if current.is_some() { - insert_key_into(String::from(current.unwrap()), &mut split, value, &mut main); - } - } - } - header -} - -fn insert_key_into(current: String, - rest_path: &mut Split<&str>, - value: String, - map: &mut BTreeMap) { - let next = rest_path.next(); - - if next.is_none() { - map.insert(current, parse_value(value)); - } else { - if map.contains_key(¤t) { - match map.get_mut(¤t).unwrap() { - &mut Value::Table(ref mut t) => { - insert_key_into(String::from(next.unwrap()), rest_path, value, t); - }, - _ => unreachable!(), - } - } else { - let mut submap = BTreeMap::new(); - insert_key_into(String::from(next.unwrap()), rest_path, value, &mut submap); - map.insert(current, Value::Table(submap)); - } - } -} - -fn parse_value(value: String) -> Value { - fn is_ary(v: &String) -> bool { - v.chars().next() == Some('[') && v.chars().last() == Some(']') && v.len() >= 3 - } - - if value == "true" { - Value::Boolean(true) - } else if value == "false" { - Value::Boolean(false) - } else if is_ary(&value) { - let sub = &value[1..(value.len()-1)]; - Value::Array(sub.split(",").map(|v| parse_value(String::from(v))).collect()) - } else { - Value::String(value) - } -} - diff --git a/imag-store/src/main.rs b/imag-store/src/main.rs index 950228e2..103ef44f 100644 --- a/imag-store/src/main.rs +++ b/imag-store/src/main.rs @@ -16,6 +16,7 @@ mod create; mod retrieve; mod update; mod delete; +mod util; use ui::build_ui; use create::create; diff --git a/imag-store/src/util.rs b/imag-store/src/util.rs new file mode 100644 index 00000000..3a15165b --- /dev/null +++ b/imag-store/src/util.rs @@ -0,0 +1,68 @@ +use std::collections::BTreeMap; +use std::str::Split; + +use clap::ArgMatches; +use toml::Value; + +use libimagstore::store::EntryHeader; +use libimagutil::key_value_split::IntoKeyValue; + +pub fn build_toml_header(matches: &ArgMatches, header: EntryHeader) -> EntryHeader { + debug!("Building header from cli spec"); + if let Some(headerspecs) = matches.values_of("header") { + let mut main = BTreeMap::new(); + for tpl in headerspecs.into_iter().filter_map(|hs| String::from(hs).into_kv()) { + let (key, value) = tpl.into(); + debug!("Splitting: {:?}", key); + let mut split = key.split("."); + let current = split.next(); + if current.is_some() { + insert_key_into(String::from(current.unwrap()), &mut split, value, &mut main); + } + } + } + header +} + +fn insert_key_into(current: String, + rest_path: &mut Split<&str>, + value: String, + map: &mut BTreeMap) { + let next = rest_path.next(); + + if next.is_none() { + map.insert(current, parse_value(value)); + } else { + if map.contains_key(¤t) { + match map.get_mut(¤t).unwrap() { + &mut Value::Table(ref mut t) => { + insert_key_into(String::from(next.unwrap()), rest_path, value, t); + }, + _ => unreachable!(), + } + } else { + let mut submap = BTreeMap::new(); + insert_key_into(String::from(next.unwrap()), rest_path, value, &mut submap); + map.insert(current, Value::Table(submap)); + } + } +} + +fn parse_value(value: String) -> Value { + fn is_ary(v: &String) -> bool { + v.chars().next() == Some('[') && v.chars().last() == Some(']') && v.len() >= 3 + } + + debug!("Building value out of: {:?}", value); + if value == "true" { + Value::Boolean(true) + } else if value == "false" { + Value::Boolean(false) + } else if is_ary(&value) { + let sub = &value[1..(value.len()-1)]; + Value::Array(sub.split(",").map(|v| parse_value(String::from(v))).collect()) + } else { + Value::String(value) + } +} + From ddd54e03ad6186fcb717d9969549d23c70d33b7b Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 29 Jan 2016 16:50:39 +0100 Subject: [PATCH 11/35] Add store path getter --- libimagstore/src/store.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libimagstore/src/store.rs b/libimagstore/src/store.rs index 49427776..4443c690 100644 --- a/libimagstore/src/store.rs +++ b/libimagstore/src/store.rs @@ -241,6 +241,11 @@ impl Store { .unwrap_or(false) // we return false, as fs::canonicalize() returns an Err(..) on filesystem errors } + + /// Gets the path where this store is on the disk + pub fn path(&self) -> &PathBuf { + &self.location + } } impl Drop for Store { From caee76650d1b3b99a4ce36a3bfaf6f829e57b13f Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 29 Jan 2016 16:50:52 +0100 Subject: [PATCH 12/35] Add store entry path builder helper --- imag-store/src/util.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/imag-store/src/util.rs b/imag-store/src/util.rs index 3a15165b..a96fc508 100644 --- a/imag-store/src/util.rs +++ b/imag-store/src/util.rs @@ -1,12 +1,27 @@ use std::collections::BTreeMap; +use std::path::PathBuf; use std::str::Split; use clap::ArgMatches; use toml::Value; use libimagstore::store::EntryHeader; +use libimagrt::runtime::Runtime; use libimagutil::key_value_split::IntoKeyValue; +pub fn build_entry_path(rt: &Runtime, path_elem: &str) -> PathBuf { + debug!("Building path..."); + let mut path = rt.store().path().clone(); + + if path_elem.chars().next() == Some('/') { + path.push(&path_elem[1..path_elem.len()]); + } else { + path.push(path_elem); + } + + path +} + pub fn build_toml_header(matches: &ArgMatches, header: EntryHeader) -> EntryHeader { debug!("Building header from cli spec"); if let Some(headerspecs) = matches.values_of("header") { From 1517d6f3109162ae179cd68ed613218bf92bb9b3 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 29 Jan 2016 20:13:44 +0100 Subject: [PATCH 13/35] Fix: build_toml_header() should get the header object as mutable --- imag-store/src/util.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imag-store/src/util.rs b/imag-store/src/util.rs index a96fc508..58c12533 100644 --- a/imag-store/src/util.rs +++ b/imag-store/src/util.rs @@ -22,10 +22,10 @@ pub fn build_entry_path(rt: &Runtime, path_elem: &str) -> PathBuf { path } -pub fn build_toml_header(matches: &ArgMatches, header: EntryHeader) -> EntryHeader { +pub fn build_toml_header(matches: &ArgMatches, mut header: EntryHeader) -> EntryHeader { debug!("Building header from cli spec"); if let Some(headerspecs) = matches.values_of("header") { - let mut main = BTreeMap::new(); + let mut main = header.toml_mut(); for tpl in headerspecs.into_iter().filter_map(|hs| String::from(hs).into_kv()) { let (key, value) = tpl.into(); debug!("Splitting: {:?}", key); From ef18cb11445bda3c57dfeccfd05a5a0761a876ac Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 29 Jan 2016 20:14:27 +0100 Subject: [PATCH 14/35] Add extensive debugging output in utilities --- imag-store/src/util.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/imag-store/src/util.rs b/imag-store/src/util.rs index 58c12533..0c033535 100644 --- a/imag-store/src/util.rs +++ b/imag-store/src/util.rs @@ -26,7 +26,15 @@ pub fn build_toml_header(matches: &ArgMatches, mut header: EntryHeader) -> Entry debug!("Building header from cli spec"); if let Some(headerspecs) = matches.values_of("header") { let mut main = header.toml_mut(); - for tpl in headerspecs.into_iter().filter_map(|hs| String::from(hs).into_kv()) { + debug!("headerspec = {:?}", headerspecs); + let kvs = headerspecs.into_iter() + .filter_map(|hs| { + debug!("- Processing: '{}'", hs); + let kv = String::from(hs).into_kv(); + debug!("- got: '{:?}'", kv); + kv + }); + for tpl in kvs { let (key, value) = tpl.into(); debug!("Splitting: {:?}", key); let mut split = key.split("."); @@ -36,6 +44,7 @@ pub fn build_toml_header(matches: &ArgMatches, mut header: EntryHeader) -> Entry } } } + debug!("Header = {:?}", header); header } @@ -46,8 +55,10 @@ fn insert_key_into(current: String, let next = rest_path.next(); if next.is_none() { + debug!("Inserting into {:?} = {:?}", current, value); map.insert(current, parse_value(value)); } else { + debug!("Inserting into {:?} ... = {:?}", current, value); if map.contains_key(¤t) { match map.get_mut(¤t).unwrap() { &mut Value::Table(ref mut t) => { @@ -58,6 +69,7 @@ fn insert_key_into(current: String, } else { let mut submap = BTreeMap::new(); insert_key_into(String::from(next.unwrap()), rest_path, value, &mut submap); + debug!("Inserting submap = {:?}", submap); map.insert(current, Value::Table(submap)); } } @@ -68,15 +80,18 @@ fn parse_value(value: String) -> Value { v.chars().next() == Some('[') && v.chars().last() == Some(']') && v.len() >= 3 } - debug!("Building value out of: {:?}", value); if value == "true" { + debug!("Building Boolean out of: {:?}...", value); Value::Boolean(true) } else if value == "false" { + debug!("Building Boolean out of: {:?}...", value); Value::Boolean(false) } else if is_ary(&value) { + debug!("Building Array out of: {:?}...", value); let sub = &value[1..(value.len()-1)]; Value::Array(sub.split(",").map(|v| parse_value(String::from(v))).collect()) } else { + debug!("Building String out of: {:?}...", value); Value::String(value) } } From 2c30d474a9f3c746c9784fc48ddb241f931f5b37 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 31 Jan 2016 12:37:40 +0100 Subject: [PATCH 15/35] UI: Add content-from argument, to get content from CLI _or_ from File/stdin --- imag-store/src/ui.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/imag-store/src/ui.rs b/imag-store/src/ui.rs index b7ac4e16..ae55d8fb 100644 --- a/imag-store/src/ui.rs +++ b/imag-store/src/ui.rs @@ -21,6 +21,11 @@ pub fn build_ui<'a>(app: App<'a, 'a, 'a, 'a, 'a, 'a>) -> App<'a, 'a, 'a, 'a, 'a, .long("content") .short("c") .takes_value(true) + .help("Content for the Entry from commandline")) + .arg(Arg::with_name("content-from") + .long("content-from") + .short("f") + .takes_value(true) .help("Content for the Entry from this file ('-' for stdin)")) .arg(Arg::with_name("header") .long("header") From 5fdbb61ac850d41c5c6cb4618ecec26dadbffc77 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 31 Jan 2016 12:39:47 +0100 Subject: [PATCH 16/35] create() fixup: entry_from_raw() -> string_from_raw_src() --- imag-store/src/create.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/imag-store/src/create.rs b/imag-store/src/create.rs index bb3eaad0..576ea10f 100644 --- a/imag-store/src/create.rs +++ b/imag-store/src/create.rs @@ -48,14 +48,14 @@ fn create_from_cli_spec(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> R .unwrap_or_else(|| { entry_subcommand .value_of("content-from") - .map(|src| entry_from_raw(src)) + .map(|src| string_from_raw_src(src)) .unwrap_or(String::new()) }) }) .unwrap_or_else(|| { debug!("Didn't find entry subcommand, getting raw content"); matches.value_of("from-raw") - .map(|raw_src| entry_from_raw(raw_src)) + .map(|raw_src| string_from_raw_src(raw_src)) .unwrap_or(String::new()) }); @@ -85,7 +85,7 @@ fn create_from_source(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Res let content = matches .value_of("from-raw") .ok_or(StoreError::new(StoreErrorKind::NoCommandlineCall, None)) - .map(|raw_src| entry_from_raw(raw_src)); + .map(|raw_src| string_from_raw_src(raw_src)); if content.is_err() { return content.map(|_| ()); @@ -106,7 +106,7 @@ fn create_from_source(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Res .map_err(|serr| StoreError::new(StoreErrorKind::BackendError, Some(Box::new(serr)))) } -fn entry_from_raw(raw_src: &str) -> String { +fn string_from_raw_src(raw_src: &str) -> String { let mut content = String::new(); if raw_src == "-" { debug!("Reading entry from stdin"); @@ -123,4 +123,3 @@ fn entry_from_raw(raw_src: &str) -> String { } content } - From 10fa3e3dafd3b44623b863344e562e80209e53fb Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 31 Jan 2016 12:54:59 +0100 Subject: [PATCH 17/35] util: Add parsing ints and floats --- imag-store/src/util.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/imag-store/src/util.rs b/imag-store/src/util.rs index 0c033535..d339dc6f 100644 --- a/imag-store/src/util.rs +++ b/imag-store/src/util.rs @@ -76,6 +76,8 @@ fn insert_key_into(current: String, } fn parse_value(value: String) -> Value { + use std::str::FromStr; + fn is_ary(v: &String) -> bool { v.chars().next() == Some('[') && v.chars().last() == Some(']') && v.len() >= 3 } @@ -91,8 +93,22 @@ fn parse_value(value: String) -> Value { let sub = &value[1..(value.len()-1)]; Value::Array(sub.split(",").map(|v| parse_value(String::from(v))).collect()) } else { - debug!("Building String out of: {:?}...", value); - Value::String(value) + FromStr::from_str(&value[..]) + .map(|i: i64| { + debug!("Building Integer out of: {:?}...", value); + Value::Integer(i) + }) + .unwrap_or_else(|_| { + FromStr::from_str(&value[..]) + .map(|f: f64| { + debug!("Building Float out of: {:?}...", value); + Value::Float(f) + }) + .unwrap_or_else(|_| { + debug!("Building String out of: {:?}...", value); + Value::String(value) + }) + }) } } From 7a403c7f93ebaddcfd9f38c7d3ca1ac1014b23cf Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 31 Jan 2016 18:03:49 +0100 Subject: [PATCH 18/35] tests: Add imag-store tests --- imag-store/tests/001-create_test.sh | 49 +++++++++++++++++++++++ imag-store/tests/utils.sh | 61 +++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 imag-store/tests/001-create_test.sh create mode 100644 imag-store/tests/utils.sh diff --git a/imag-store/tests/001-create_test.sh b/imag-store/tests/001-create_test.sh new file mode 100644 index 00000000..b7d641b6 --- /dev/null +++ b/imag-store/tests/001-create_test.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +source $(dirname ${BASH_SOURCE[0]})/utils.sh + +test_call() { + imag-store create -p /test-call + if [[ ! $? -eq 0 ]]; then + err "Return value should be zero, was non-zero" + return 1; + fi +} + +test_mkstore() { + imag-store create -p /test-mkstore || { err "Calling imag failed"; return 1; } + if [[ -d ${STORE} ]]; then + out "Store exists." + else + err "No store created" + return 1 + fi +} + +test_std_header() { + local expected=$(cat <> $*${COLOR_OFF}" +} + +err() { + echo -e "${RED}!! $*${COLOR_OFF}" +} + +imag-store() { + local searchdir=$(dirname ${BASH_SOURCE[0]})/../target/debug/ + [[ -d $searchdir ]] || { err "FATAL: No directory $searchdir"; exit 1; } + local bin=$(find $searchdir -iname imag-store -type f -executable) + local flags="--debug --rtp $RUNTIME" + out "Calling '$bin $flags $*'" + $bin $flags $* +} + +reset_store() { + rm -r "${STORE}" +} + +call_test() { + out "-- TESTING: '$1' --" + $1 + result=$? + if [[ -z "$DONT_RESET_STORE" ]]; then + out "Reseting store" + reset_store + out "Store reset done" + fi + [[ $result -eq 0 ]] || { err "-- FAILED: '$1'. Exiting."; exit 1; } + success "-- SUCCESS: '$1' --" +} + +invoke_tests() { + out "Invoking tests." + if [[ ! -z "$INVOKE_TEST" ]]; then + out "Invoking only $INVOKE_TEST" + call_test "$INVOKE_TEST" + else + out "Invoking $*" + for t in $*; do + call_test "$t" + done + fi +} + From 756bd09a831480f753367a2dcf4985a8750a1e80 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 31 Jan 2016 18:04:01 +0100 Subject: [PATCH 19/35] Integrate tests in travis.yml The tests work only if I call "tree". Really, how fucked up is travis actually? --- .travis.yml | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index fd3a6f6e..5722fe1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ matrix: before_install: - | - c=$(git diff $TRAVIS_BRANCH..$TRAVIS_COMMIT --name-only | cut -d "/" -f 1 | uniq) + c=$(git diff $(git merge-base master $TRAVIS_COMMIT)..$TRAVIS_COMMIT --name-only | cut -d "/" -f 1 | uniq) if [[ "$c" == "doc" ]]; then echo "Only changes in DOC, exiting 0" exit 0 @@ -28,12 +28,13 @@ before_script: script: - | changes_in() { - [[ $(git diff --name-only $TRAVIS_BRANCH..$TRAVIS_COMMIT | \ + [[ $(git diff --name-only $(git merge-base master $TRAVIS_COMMIT)..$TRAVIS_COMMIT | \ cut -d "/" -f 1 | \ grep "$n") ]] > /dev/null } travis_cargo_run_in() { + echo ":: Trying to run cargo in $1" [[ -d "$1" ]] && cd "$1" && { @@ -45,14 +46,27 @@ script: } || exit 1 } + run_sh_test() { + echo "-- Running test script: $1" + bash $1 || { echo "-- Test failed. Exiting"; exit 1; } + echo "-- Test script $1 executed successfully" + } + [[ $(changes_in "doc") ]] && echo "Changes in ./doc are not build by CI" - for d in $(find -name "Cargo.toml" | grep -vE "^.$"); do + for d in $(find -name "Cargo.toml" | grep -vE "^./Cargo.toml$"); do + echo ":: Working on $d" dir=$(dirname $d) - { - changes_in $dir && \ - echo -e "\nRunning in $d\n" && \ - travis_cargo_run_in $dir + { \ + changes_in $dir && \ + echo -e "\nRunning in $d\n" && \ + travis_cargo_run_in $dir && \ + tree -I "*doc*" $dir && \ + echo "-- Running test scripts..." && \ + for testsh in $(find $dir -iname "*test.sh"); do + run_sh_test $testsh + done && \ + echo "-- Done with test scripts..." } || true done @@ -62,6 +76,7 @@ addons: - libcurl4-openssl-dev - libelf-dev - libdw-dev + - tree after_success: - travis-cargo --only stable doc-upload From 67a7f1ab78e8252fb9608cdfd5a541a0e269e0e6 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 31 Jan 2016 19:01:40 +0100 Subject: [PATCH 20/35] Add dedicated function for creating an entry with content --- imag-store/src/create.rs | 64 ++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/imag-store/src/create.rs b/imag-store/src/create.rs index 576ea10f..086b4698 100644 --- a/imag-store/src/create.rs +++ b/imag-store/src/create.rs @@ -28,13 +28,17 @@ pub fn create(rt: &Runtime) { let path = build_entry_path(rt, scmd.value_of("path").unwrap()); debug!("path = {:?}", path); - scmd.subcommand_matches("entry") - .map(|entry| create_from_cli_spec(rt, scmd, &path)) - .ok_or(()) // hackythehackhack - .map_err(|_| { - create_from_source(rt, scmd, &path) - .unwrap_or_else(|e| debug!("Error building Entry: {:?}", e)) - }); + if scmd.subcommand_matches("entry").is_some() { + create_from_cli_spec(rt, scmd, &path) + .or_else(|_| create_from_source(rt, scmd, &path)) + .or_else(|_| create_with_content_and_header(rt, + &path, + String::new(), + EntryHeader::new())) + } else { + create_with_content_and_header(rt, &path, String::new(), EntryHeader::new()) + } + .unwrap_or_else(|e| debug!("Error building Entry: {:?}", e)) }); } @@ -61,24 +65,11 @@ fn create_from_cli_spec(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> R debug!("Got content with len = {}", content.len()); - rt.store() - .create(PathBuf::from(path)) - .map(|mut element| { - { - let mut e_content = element.get_content_mut(); - *e_content = content; - debug!("New content set"); - } - { - let mut e_header = element.get_header_mut(); - matches.subcommand_matches("entry") - .map(|entry_matches| { - *e_header = build_toml_header(entry_matches, EntryHeader::new()); - debug!("New header set"); - }); - } - }) - .map_err(|e| StoreError::new(StoreErrorKind::BackendError, Some(Box::new(e)))) + let header = matches.subcommand_matches("entry") + .map(|entry_matches| build_toml_header(entry_matches, EntryHeader::new())) + .unwrap_or(EntryHeader::new()); + + create_with_content_and_header(rt, path, content, header) } fn create_from_source(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Result<()> { @@ -106,6 +97,29 @@ fn create_from_source(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Res .map_err(|serr| StoreError::new(StoreErrorKind::BackendError, Some(Box::new(serr)))) } +fn create_with_content_and_header(rt: &Runtime, + path: &PathBuf, + content: String, + header: EntryHeader) -> Result<()> +{ + debug!("Creating entry with content"); + rt.store() + .create(PathBuf::from(path)) + .map(|mut element| { + { + let mut e_content = element.get_content_mut(); + *e_content = content; + debug!("New content set"); + } + { + let mut e_header = element.get_header_mut(); + *e_header = header; + debug!("New header set"); + } + }) + .map_err(|e| StoreError::new(StoreErrorKind::BackendError, Some(Box::new(e)))) +} + fn string_from_raw_src(raw_src: &str) -> String { let mut content = String::new(); if raw_src == "-" { From 2b39b8eed744cf23d5256f1d2f13531f3e3886e3 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 31 Jan 2016 19:38:31 +0100 Subject: [PATCH 21/35] Add add test for generating custom header --- imag-store/tests/001-create_test.sh | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/imag-store/tests/001-create_test.sh b/imag-store/tests/001-create_test.sh index b7d641b6..5a65d122 100644 --- a/imag-store/tests/001-create_test.sh +++ b/imag-store/tests/001-create_test.sh @@ -41,6 +41,30 @@ EOS fi } +test_std_header_plus_custom() { + local expected=$(cat < Date: Sun, 31 Jan 2016 19:47:46 +0100 Subject: [PATCH 22/35] Add test for custom header and content --- imag-store/tests/001-create_test.sh | 36 +++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/imag-store/tests/001-create_test.sh b/imag-store/tests/001-create_test.sh index 5a65d122..a944ad3a 100644 --- a/imag-store/tests/001-create_test.sh +++ b/imag-store/tests/001-create_test.sh @@ -65,9 +65,35 @@ EOS fi } -invoke_tests \ - test_call \ - test_mkstore \ - test_std_header \ - test_std_header_plus_custom +test_std_header_plus_custom_and_content() { + local expected=$(cat < Date: Sun, 31 Jan 2016 21:25:47 +0100 Subject: [PATCH 23/35] Add test with multiple headers on commandline --- imag-store/tests/001-create_test.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/imag-store/tests/001-create_test.sh b/imag-store/tests/001-create_test.sh index a944ad3a..71734b0d 100644 --- a/imag-store/tests/001-create_test.sh +++ b/imag-store/tests/001-create_test.sh @@ -65,6 +65,34 @@ EOS fi } +test_std_header_plus_custom_multiheader() { + local expected=$(cat < Date: Sun, 31 Jan 2016 21:49:36 +0100 Subject: [PATCH 24/35] Add test for file with custom header with two keys in one section --- imag-store/tests/001-create_test.sh | 40 ++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/imag-store/tests/001-create_test.sh b/imag-store/tests/001-create_test.sh index 71734b0d..1b41ed01 100644 --- a/imag-store/tests/001-create_test.sh +++ b/imag-store/tests/001-create_test.sh @@ -93,6 +93,33 @@ EOS fi } + +test_std_header_plus_custom_multiheader_same_section() { + local expected=$(cat < Date: Mon, 1 Feb 2016 19:17:00 +0100 Subject: [PATCH 25/35] Add semver = 0.2.1 --- imag-store/Cargo.toml | 1 + imag-store/src/main.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/imag-store/Cargo.toml b/imag-store/Cargo.toml index cd448864..1f43f662 100644 --- a/imag-store/Cargo.toml +++ b/imag-store/Cargo.toml @@ -7,6 +7,7 @@ authors = ["Matthias Beyer "] clap = "1.5.5" log = "0.3.5" version = "1.1.0" +semver = "0.2.1" toml = "0.1.25" [dependencies.libimagstore] diff --git a/imag-store/src/main.rs b/imag-store/src/main.rs index 103ef44f..4533c338 100644 --- a/imag-store/src/main.rs +++ b/imag-store/src/main.rs @@ -1,5 +1,6 @@ extern crate clap; #[macro_use] extern crate log; +extern crate semver; extern crate toml; #[macro_use] extern crate version; From 8c6fc7ef41d9a41f9dee140482afe9d3fce57851 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Mon, 1 Feb 2016 19:26:41 +0100 Subject: [PATCH 26/35] util: Verify that path contains version, else panic!() --- imag-store/src/util.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/imag-store/src/util.rs b/imag-store/src/util.rs index d339dc6f..6ebb3a2d 100644 --- a/imag-store/src/util.rs +++ b/imag-store/src/util.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use std::str::Split; use clap::ArgMatches; +use semver::Version; use toml::Value; use libimagstore::store::EntryHeader; @@ -10,7 +11,24 @@ use libimagrt::runtime::Runtime; use libimagutil::key_value_split::IntoKeyValue; pub fn build_entry_path(rt: &Runtime, path_elem: &str) -> PathBuf { - debug!("Building path..."); + debug!("Checking path element for version"); + { + let contains_version = { + path_elem.split("~") + .last() + .map(|version| Version::parse(version).is_ok()) + .unwrap_or(false) + }; + + if !contains_version { + debug!("Version cannot be parsed inside {:?}", path_elem); + warn!("Path does not contain version. Will panic now!"); + panic!("No version in path"); + } + } + debug!("Version checking succeeded"); + + debug!("Building path from {:?}", path_elem); let mut path = rt.store().path().clone(); if path_elem.chars().next() == Some('/') { From 5f518726ee37546278da23188cd095e8a7fb737c Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Mon, 1 Feb 2016 19:28:35 +0100 Subject: [PATCH 27/35] tests: Add version in filenames for test files --- imag-store/tests/001-create_test.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/imag-store/tests/001-create_test.sh b/imag-store/tests/001-create_test.sh index 1b41ed01..2bcec63a 100644 --- a/imag-store/tests/001-create_test.sh +++ b/imag-store/tests/001-create_test.sh @@ -3,7 +3,7 @@ source $(dirname ${BASH_SOURCE[0]})/utils.sh test_call() { - imag-store create -p /test-call + imag-store create -p /test-call~0.1.0 if [[ ! $? -eq 0 ]]; then err "Return value should be zero, was non-zero" return 1; @@ -11,7 +11,7 @@ test_call() { } test_mkstore() { - imag-store create -p /test-mkstore || { err "Calling imag failed"; return 1; } + imag-store create -p /test-mkstore~0.1.0 || { err "Calling imag failed"; return 1; } if [[ -d ${STORE} ]]; then out "Store exists." else @@ -31,8 +31,8 @@ version = "0.1.0" EOS ) - imag-store create -p /test-std-header - local result=$(cat ${STORE}/test-std-header) + imag-store create -p /test-std-header~0.1.0 + local result=$(cat ${STORE}/test-std-header~0.1.0) if [[ "$expected" == "$result" ]]; then out "Expected store entry == result" else @@ -55,8 +55,8 @@ zzz = "z" EOS ) - imag-store create -p /test-std-header-plus-custom entry -h zzz.zzz=z - local result=$(cat ${STORE}/test-std-header-plus-custom) + imag-store create -p /test-std-header-plus-custom~0.1.0 entry -h zzz.zzz=z + local result=$(cat ${STORE}/test-std-header-plus-custom~0.1.0) if [[ "$expected" == "$result" ]]; then out "Expected store entry == result" else @@ -82,7 +82,7 @@ zzz = "z" EOS ) - local filename="test-std-header-plus-custom-multiheader" + local filename="test-std-header-plus-custom-multiheader~0.1.0" imag-store create -p /$filename entry -h zzz.zzz=z foo.bar=baz local result=$(cat ${STORE}/$filename) if [[ "$expected" == "$result" ]]; then @@ -109,7 +109,7 @@ zzz = "z" EOS ) - local filename="test-std-header-plus-custom-mutliheader-same-section" + local filename="test-std-header-plus-custom-mutliheader-same-section~0.1.0" imag-store create -p /$filename entry -h zzz.zzz=z zzz.bar=baz local result=$(cat ${STORE}/$filename) if [[ "$expected" == "$result" ]]; then @@ -134,7 +134,7 @@ content EOS ) - local name="test-std-header-plus-custom-and-content" + local name="test-std-header-plus-custom-and-content~0.1.0" imag-store create -p /$name entry -h zzz.zzz=z -c content local result=$(cat ${STORE}/$name) if [[ "$expected" == "$result" ]]; then From e8ec335b74bc5b481317bc040544464ae810e3a2 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 4 Feb 2016 17:42:09 +0100 Subject: [PATCH 28/35] imag-store: dependency: version: 1.1.0 -> 2.0.1 --- imag-store/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imag-store/Cargo.toml b/imag-store/Cargo.toml index 1f43f662..07d531b4 100644 --- a/imag-store/Cargo.toml +++ b/imag-store/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Matthias Beyer "] [dependencies] clap = "1.5.5" log = "0.3.5" -version = "1.1.0" +version = "2.0.1" semver = "0.2.1" toml = "0.1.25" From ef8c364ef139a05a7ec6fefe4f61d181c51fd4c6 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 4 Feb 2016 17:57:35 +0100 Subject: [PATCH 29/35] Add test: retrieve --- imag-store/tests/002-retrieve_test.sh | 84 +++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 imag-store/tests/002-retrieve_test.sh diff --git a/imag-store/tests/002-retrieve_test.sh b/imag-store/tests/002-retrieve_test.sh new file mode 100644 index 00000000..33b96780 --- /dev/null +++ b/imag-store/tests/002-retrieve_test.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +source $(dirname ${BASH_SOURCE[0]})/utils.sh + +std_header() { + cat < Date: Thu, 4 Feb 2016 18:15:40 +0100 Subject: [PATCH 30/35] Add silent() helper for calling without debugging output --- imag-store/tests/utils.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/imag-store/tests/utils.sh b/imag-store/tests/utils.sh index 7f12cb55..148eaf87 100644 --- a/imag-store/tests/utils.sh +++ b/imag-store/tests/utils.sh @@ -9,15 +9,19 @@ RUNTIME="/tmp" STORE="${RUNTIME}/store" out() { - echo -e "${YELLOW}:: $*${COLOR_OFF}" + [[ -z "$DEBUG_OUTPUT_OFF" ]] && echo -e "${YELLOW}:: $*${COLOR_OFF}" } success() { - echo -e "${GREEN}>> $*${COLOR_OFF}" + [[ -z "$DEBUG_OUTPUT_OFF" ]] && echo -e "${GREEN}>> $*${COLOR_OFF}" } err() { - echo -e "${RED}!! $*${COLOR_OFF}" + [[ -z "$DEBUG_OUTPUT_OFF" ]] && echo -e "${RED}!! $*${COLOR_OFF}" +} + +silent() { + DEBUG_OUTPUT_OFF=1 $* } imag-store() { From a59ce2003ceaae910a59f0ae515688260e36d0fe Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Fri, 5 Feb 2016 21:44:15 +0100 Subject: [PATCH 31/35] Add test: delete functionality --- imag-store/tests/003-delete_test.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 imag-store/tests/003-delete_test.sh diff --git a/imag-store/tests/003-delete_test.sh b/imag-store/tests/003-delete_test.sh new file mode 100644 index 00000000..794f1ad8 --- /dev/null +++ b/imag-store/tests/003-delete_test.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +source $(dirname ${BASH_SOURCE[0]})/utils.sh + +create() { + imag-store create $* +} + +delete() { + imag-store delete $* +} + +test_delete_simple() { + local name="test~0.1.0" + + create -p /$name + delete --id /$name + + local n=$($(find ${STORE}/ -type f | wc -l)) + if [[ $n -eq 0 ]]; then + success "Deleting worked" + else + err "There are still $n files in the store" + fi +} + +invoke_tests \ + test_delete_simple + From 04a5bb913844d9102f1ab172f01e1f0f38d5dcad Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 6 Feb 2016 11:54:36 +0100 Subject: [PATCH 32/35] Allow --id / -i as path specification, for consistency --- imag-store/src/create.rs | 12 +++++++++++- imag-store/src/ui.rs | 8 +++++++- imag-store/tests/001-create_test.sh | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/imag-store/src/create.rs b/imag-store/src/create.rs index 086b4698..03164581 100644 --- a/imag-store/src/create.rs +++ b/imag-store/src/create.rs @@ -4,6 +4,9 @@ use std::fs::OpenOptions; use std::result::Result as RResult; use std::io::Read; use std::ops::DerefMut; +use std::io::Write; +use std::io::stderr; +use std::process::exit; use clap::ArgMatches; @@ -25,7 +28,14 @@ pub fn create(rt: &Runtime) { debug!("Found 'create' subcommand..."); // unwrap is safe as value is required - let path = build_entry_path(rt, scmd.value_of("path").unwrap()); + let path = scmd.value_of("path").or_else(|| scmd.value_of("id")); + if path.is_none() { + warn!("No ID / Path provided. Exiting now"); + write!(stderr(), "No ID / Path provided. Exiting now"); + exit(1); + } + + let path = build_entry_path(rt, path.unwrap()); debug!("path = {:?}", path); if scmd.subcommand_matches("entry").is_some() { diff --git a/imag-store/src/ui.rs b/imag-store/src/ui.rs index ae55d8fb..da8886f7 100644 --- a/imag-store/src/ui.rs +++ b/imag-store/src/ui.rs @@ -8,8 +8,14 @@ pub fn build_ui<'a>(app: App<'a, 'a, 'a, 'a, 'a, 'a>) -> App<'a, 'a, 'a, 'a, 'a, .long("path") .short("p") .takes_value(true) - .required(true) + .required(false) .help("Create at this store path")) + .arg(Arg::with_name("id") + .long("id") + .short("i") + .takes_value(true) + .required(false) + .help("Same as --path, for consistency")) .arg(Arg::with_name("from-raw") .long("from-raw") .takes_value(true) diff --git a/imag-store/tests/001-create_test.sh b/imag-store/tests/001-create_test.sh index 2bcec63a..65870769 100644 --- a/imag-store/tests/001-create_test.sh +++ b/imag-store/tests/001-create_test.sh @@ -10,6 +10,22 @@ test_call() { fi } +test_call_id() { + imag-store create -i /test-call~0.1.0 + if [[ ! $? -eq 0 ]]; then + err "Return value should be zero, was non-zero" + return 1; + fi +} + +test_call_no_id() { + imag-store create + if [[ ! $? -eq 1 ]]; then + err "Return value should be zero, was non-zero" + return 1; + fi +} + test_mkstore() { imag-store create -p /test-mkstore~0.1.0 || { err "Calling imag failed"; return 1; } if [[ -d ${STORE} ]]; then @@ -147,6 +163,8 @@ EOS invoke_tests \ test_call \ + test_call_id \ + test_call_no_id \ test_mkstore \ test_std_header \ test_std_header_plus_custom \ From d15a5a150b3f406a9a2b05967294ad807b5c31fe Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 9 Feb 2016 17:05:52 +0100 Subject: [PATCH 33/35] Fix error message for failing test --- imag-store/tests/001-create_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imag-store/tests/001-create_test.sh b/imag-store/tests/001-create_test.sh index 65870769..91720c9f 100644 --- a/imag-store/tests/001-create_test.sh +++ b/imag-store/tests/001-create_test.sh @@ -52,7 +52,7 @@ EOS if [[ "$expected" == "$result" ]]; then out "Expected store entry == result" else - err "${STORE}/test differs from expected" + err "${STORE}/test-std-header~0.1.0 differs from expected" return 1 fi } From d85f985694ebbda74fa685cdeea12db4c734abb7 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 9 Feb 2016 17:16:55 +0100 Subject: [PATCH 34/35] Fixup path-includes-path checker --- libimagstore/src/store.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libimagstore/src/store.rs b/libimagstore/src/store.rs index 4443c690..3251220f 100644 --- a/libimagstore/src/store.rs +++ b/libimagstore/src/store.rs @@ -238,7 +238,7 @@ impl Store { .map(|can| { can.starts_with(&self.location) }) - .unwrap_or(false) + .unwrap_or(path.starts_with(&self.location)) // we return false, as fs::canonicalize() returns an Err(..) on filesystem errors } From 00ccc22ef528cb723415686371d4c9510795cacf Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Tue, 9 Feb 2016 17:17:41 +0100 Subject: [PATCH 35/35] Add some debugging output for {create,retrieve,delete} --- libimagstore/src/store.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libimagstore/src/store.rs b/libimagstore/src/store.rs index 3251220f..62518c95 100644 --- a/libimagstore/src/store.rs +++ b/libimagstore/src/store.rs @@ -130,6 +130,7 @@ impl Store { /// Creates the Entry at the given location (inside the entry) pub fn create<'a>(&'a self, id: StoreId) -> Result> { if !self.id_in_store(&id) { + debug!("'{:?}' seems not to be in '{:?}'", id, self.location); return Err(StoreError::new(StoreErrorKind::StorePathOutsideStore, None)); } @@ -153,6 +154,7 @@ impl Store { /// dropped, the new Entry is written to disk pub fn retrieve<'a>(&'a self, id: StoreId) -> Result> { if !self.id_in_store(&id) { + debug!("'{:?}' seems not to be in '{:?}'", id, self.location); return Err(StoreError::new(StoreErrorKind::StorePathOutsideStore, None)); } @@ -213,6 +215,7 @@ impl Store { /// Delete an entry pub fn delete(&self, id: StoreId) -> Result<()> { if !self.id_in_store(&id) { + debug!("'{:?}' seems not to be in '{:?}'", id, self.location); return Err(StoreError::new(StoreErrorKind::StorePathOutsideStore, None)); }