Merge pull request #136 from matthiasbeyer/bin/imag-store/init
bin/imag-store/init
This commit is contained in:
commit
e9528fdd33
15 changed files with 1104 additions and 8 deletions
29
.travis.yml
29
.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
|
||||
|
|
21
imag-store/Cargo.toml
Normal file
21
imag-store/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "imag-store"
|
||||
version = "0.1.0"
|
||||
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
||||
|
||||
[dependencies]
|
||||
clap = "1.5.5"
|
||||
log = "0.3.5"
|
||||
version = "2.0.1"
|
||||
semver = "0.2.1"
|
||||
toml = "0.1.25"
|
||||
|
||||
[dependencies.libimagstore]
|
||||
path = "../libimagstore"
|
||||
|
||||
[dependencies.libimagrt]
|
||||
path = "../libimagrt"
|
||||
|
||||
[dependencies.libimagutil]
|
||||
path = "../libimagutil"
|
||||
|
149
imag-store/src/create.rs
Normal file
149
imag-store/src/create.rs
Normal file
|
@ -0,0 +1,149 @@
|
|||
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::io::Write;
|
||||
use std::io::stderr;
|
||||
use std::process::exit;
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagstore::store::Entry;
|
||||
use libimagstore::store::EntryHeader;
|
||||
|
||||
use error::StoreError;
|
||||
use error::StoreErrorKind;
|
||||
use util::build_entry_path;
|
||||
use util::build_toml_header;
|
||||
|
||||
type Result<T> = RResult<T, StoreError>;
|
||||
|
||||
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 = 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() {
|
||||
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))
|
||||
});
|
||||
}
|
||||
|
||||
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| 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| string_from_raw_src(raw_src))
|
||||
.unwrap_or(String::new())
|
||||
});
|
||||
|
||||
debug!("Got content with len = {}", content.len());
|
||||
|
||||
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<()> {
|
||||
let content = matches
|
||||
.value_of("from-raw")
|
||||
.ok_or(StoreError::new(StoreErrorKind::NoCommandlineCall, None))
|
||||
.map(|raw_src| string_from_raw_src(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 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 == "-" {
|
||||
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
|
||||
}
|
33
imag-store/src/delete.rs
Normal file
33
imag-store/src/delete.rs
Normal file
|
@ -0,0 +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);
|
||||
});
|
||||
}
|
||||
|
81
imag-store/src/error.rs
Normal file
81
imag-store/src/error.rs
Normal file
|
@ -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<Box<Error>>,
|
||||
}
|
||||
|
||||
impl StoreError {
|
||||
|
||||
/**
|
||||
* Build a new StoreError from an StoreErrorKind, optionally with cause
|
||||
*/
|
||||
pub fn new(errtype: StoreErrorKind, cause: Option<Box<Error>>)
|
||||
-> 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
72
imag-store/src/main.rs
Normal file
72
imag-store/src/main.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
extern crate clap;
|
||||
#[macro_use] extern crate log;
|
||||
extern crate semver;
|
||||
extern crate toml;
|
||||
#[macro_use] extern crate version;
|
||||
|
||||
extern crate libimagrt;
|
||||
extern crate libimagstore;
|
||||
extern crate libimagutil;
|
||||
|
||||
use libimagrt::runtime::Runtime;
|
||||
use std::process::exit;
|
||||
|
||||
mod error;
|
||||
mod ui;
|
||||
mod create;
|
||||
mod retrieve;
|
||||
mod update;
|
||||
mod delete;
|
||||
mod util;
|
||||
|
||||
use ui::build_ui;
|
||||
use create::create;
|
||||
use retrieve::retrieve;
|
||||
use update::update;
|
||||
use delete::delete;
|
||||
|
||||
fn main() {
|
||||
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),
|
||||
"retrieve" => retrieve(&rt),
|
||||
"update" => update(&rt),
|
||||
"delete" => delete(&rt),
|
||||
_ => {
|
||||
debug!("Unknown command");
|
||||
// More error handling
|
||||
},
|
||||
};
|
||||
}
|
||||
)
|
||||
}
|
||||
|
84
imag-store/src/retrieve.rs
Normal file
84
imag-store/src/retrieve.rs
Normal file
|
@ -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()
|
||||
}
|
||||
|
120
imag-store/src/ui.rs
Normal file
120
imag-store/src/ui.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
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(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)
|
||||
.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 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")
|
||||
.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"))
|
||||
)
|
||||
}
|
||||
|
29
imag-store/src/update.rs
Normal file
29
imag-store/src/update.rs
Normal file
|
@ -0,0 +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");
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
132
imag-store/src/util.rs
Normal file
132
imag-store/src/util.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::path::PathBuf;
|
||||
use std::str::Split;
|
||||
|
||||
use clap::ArgMatches;
|
||||
use semver::Version;
|
||||
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!("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('/') {
|
||||
path.push(&path_elem[1..path_elem.len()]);
|
||||
} else {
|
||||
path.push(path_elem);
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
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 = header.toml_mut();
|
||||
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(".");
|
||||
let current = split.next();
|
||||
if current.is_some() {
|
||||
insert_key_into(String::from(current.unwrap()), &mut split, value, &mut main);
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("Header = {:?}", header);
|
||||
header
|
||||
}
|
||||
|
||||
fn insert_key_into(current: String,
|
||||
rest_path: &mut Split<&str>,
|
||||
value: String,
|
||||
map: &mut BTreeMap<String, Value>) {
|
||||
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) => {
|
||||
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);
|
||||
debug!("Inserting submap = {:?}", submap);
|
||||
map.insert(current, Value::Table(submap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
174
imag-store/tests/001-create_test.sh
Normal file
174
imag-store/tests/001-create_test.sh
Normal file
|
@ -0,0 +1,174 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
source $(dirname ${BASH_SOURCE[0]})/utils.sh
|
||||
|
||||
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;
|
||||
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
|
||||
out "Store exists."
|
||||
else
|
||||
err "No store created"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_std_header() {
|
||||
local expected=$(cat <<EOS
|
||||
---
|
||||
[imag]
|
||||
links = []
|
||||
version = "0.1.0"
|
||||
---
|
||||
|
||||
EOS
|
||||
)
|
||||
|
||||
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
|
||||
err "${STORE}/test-std-header~0.1.0 differs from expected"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_std_header_plus_custom() {
|
||||
local expected=$(cat <<EOS
|
||||
---
|
||||
[imag]
|
||||
links = []
|
||||
version = "0.1.0"
|
||||
|
||||
[zzz]
|
||||
zzz = "z"
|
||||
---
|
||||
|
||||
EOS
|
||||
)
|
||||
|
||||
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
|
||||
err "${STORE}/test differs from expected"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_std_header_plus_custom_multiheader() {
|
||||
local expected=$(cat <<EOS
|
||||
---
|
||||
[foo]
|
||||
bar = "baz"
|
||||
|
||||
[imag]
|
||||
links = []
|
||||
version = "0.1.0"
|
||||
|
||||
[zzz]
|
||||
zzz = "z"
|
||||
---
|
||||
|
||||
EOS
|
||||
)
|
||||
|
||||
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
|
||||
out "Expected store entry == result"
|
||||
else
|
||||
err "${STORE}/$filename differs from expected"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
test_std_header_plus_custom_multiheader_same_section() {
|
||||
local expected=$(cat <<EOS
|
||||
---
|
||||
[imag]
|
||||
links = []
|
||||
version = "0.1.0"
|
||||
|
||||
[zzz]
|
||||
bar = "baz"
|
||||
zzz = "z"
|
||||
---
|
||||
|
||||
EOS
|
||||
)
|
||||
|
||||
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
|
||||
out "Expected store entry == result"
|
||||
else
|
||||
err "${STORE}/$filename differs from expected"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_std_header_plus_custom_and_content() {
|
||||
local expected=$(cat <<EOS
|
||||
---
|
||||
[imag]
|
||||
links = []
|
||||
version = "0.1.0"
|
||||
|
||||
[zzz]
|
||||
zzz = "z"
|
||||
---
|
||||
content
|
||||
EOS
|
||||
)
|
||||
|
||||
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
|
||||
out "Expected store entry == result"
|
||||
else
|
||||
err "${STORE}/test differs from expected"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
invoke_tests \
|
||||
test_call \
|
||||
test_call_id \
|
||||
test_call_no_id \
|
||||
test_mkstore \
|
||||
test_std_header \
|
||||
test_std_header_plus_custom \
|
||||
test_std_header_plus_custom_multiheader \
|
||||
test_std_header_plus_custom_multiheader_same_section \
|
||||
test_std_header_plus_custom_and_content
|
||||
|
84
imag-store/tests/002-retrieve_test.sh
Normal file
84
imag-store/tests/002-retrieve_test.sh
Normal file
|
@ -0,0 +1,84 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
source $(dirname ${BASH_SOURCE[0]})/utils.sh
|
||||
|
||||
std_header() {
|
||||
cat <<EOS
|
||||
---
|
||||
[imag]
|
||||
links = []
|
||||
version = "0.1.0"
|
||||
---
|
||||
EOS
|
||||
}
|
||||
|
||||
retrieve() {
|
||||
silent imag-store retrieve $*
|
||||
}
|
||||
|
||||
test_retrieve_nothing() {
|
||||
local id="test-retrieve_nothing~0.1.0"
|
||||
|
||||
imag-store create -p /${id} || { err "create failed"; return 1; }
|
||||
|
||||
out "Going to test the retrieve functionality now"
|
||||
local zero_out="$(retrieve --id /${id})"
|
||||
out "Retrieving for zero_out finished"
|
||||
|
||||
if [[ ! -z "$zero_out" ]]; then
|
||||
err "Expected zero output, got '$zero_out'"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_retrieve_content() {
|
||||
local id="test-retrieve_simple~0.1.0"
|
||||
|
||||
imag-store create -p /${id} || { err "create failed"; return 1; }
|
||||
|
||||
out "Going to test the retrieve functionality now"
|
||||
|
||||
local content_out="$(retrieve --id /${id} --content)"
|
||||
out "Retrieving for content_out finished"
|
||||
|
||||
if [[ ! -z "$content_out" ]]; then
|
||||
err "Expected content output == zero output, got '$content_out'"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_retrieve_header() {
|
||||
local id="test-retrieve_simple~0.1.0"
|
||||
|
||||
imag-store create -p /${id} || { err "create failed"; return 1; }
|
||||
|
||||
out "Going to test the retrieve functionality now"
|
||||
local header_out="$(retrieve --id /${id} --header)"
|
||||
out "Retrieving for header_out finished"
|
||||
|
||||
if [[ ! "$header_out" != "$(std_header)" ]]; then
|
||||
err "Expected header as output, got '$header_out'"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
test_retrieve_raw() {
|
||||
local id="test-retrieve_simple~0.1.0"
|
||||
|
||||
imag-store create -p /${id} || { err "create failed"; return 1; }
|
||||
|
||||
out "Going to test the retrieve functionality now"
|
||||
local both_out="$(retrieve --id /${id} --raw)"
|
||||
out "Retrieving for both_out finished"
|
||||
|
||||
if [[ "$both_out" != "$(std_header)" ]]; then
|
||||
err "Expected "$(std_header)" as output, got '$both_out'"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
invoke_tests \
|
||||
test_retrieve_nothing \
|
||||
test_retrieve_content \
|
||||
test_retrieve_header \
|
||||
test_retrieve_raw
|
29
imag-store/tests/003-delete_test.sh
Normal file
29
imag-store/tests/003-delete_test.sh
Normal file
|
@ -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
|
||||
|
65
imag-store/tests/utils.sh
Normal file
65
imag-store/tests/utils.sh
Normal file
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
COLOR_OFF='\e[0m' # Text Reset
|
||||
RED='\e[0;31m' # Red
|
||||
YELLOW='\e[0;33m' # Yellow
|
||||
GREEN='\e[0;32m' # Green
|
||||
|
||||
RUNTIME="/tmp"
|
||||
STORE="${RUNTIME}/store"
|
||||
|
||||
out() {
|
||||
[[ -z "$DEBUG_OUTPUT_OFF" ]] && echo -e "${YELLOW}:: $*${COLOR_OFF}"
|
||||
}
|
||||
|
||||
success() {
|
||||
[[ -z "$DEBUG_OUTPUT_OFF" ]] && echo -e "${GREEN}>> $*${COLOR_OFF}"
|
||||
}
|
||||
|
||||
err() {
|
||||
[[ -z "$DEBUG_OUTPUT_OFF" ]] && echo -e "${RED}!! $*${COLOR_OFF}"
|
||||
}
|
||||
|
||||
silent() {
|
||||
DEBUG_OUTPUT_OFF=1 $*
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -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<FileLockEntry<'a>> {
|
||||
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<FileLockEntry<'a>> {
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -238,9 +241,14 @@ 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
|
||||
}
|
||||
|
||||
/// Gets the path where this store is on the disk
|
||||
pub fn path(&self) -> &PathBuf {
|
||||
&self.location
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Store {
|
||||
|
|
Loading…
Reference in a new issue