diff --git a/imag-tag/Cargo.toml b/imag-tag/Cargo.toml new file mode 100644 index 00000000..d5946ba0 --- /dev/null +++ b/imag-tag/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "imag-tag" +version = "0.1.0" +authors = ["Matthias Beyer "] + +[dependencies] +clap = "2.1.1" +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" + +[dependencies.libimagtag] +path = "../libimagtag" + diff --git a/imag-tag/src/main.rs b/imag-tag/src/main.rs new file mode 100644 index 00000000..87b9919d --- /dev/null +++ b/imag-tag/src/main.rs @@ -0,0 +1,163 @@ +extern crate clap; +#[macro_use] extern crate log; +extern crate semver; +extern crate toml; +#[macro_use] extern crate version; + +extern crate libimagstore; +extern crate libimagrt; +extern crate libimagtag; +extern crate libimagutil; + +use std::process::exit; + +use libimagrt::runtime::Runtime; +use libimagtag::tagable::Tagable; + +mod ui; +mod util; + +use ui::build_ui; +use util::build_entry_path; + +use libimagutil::trace::trace_error; + +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' ..."); + + let id = rt.cli().value_of("id").unwrap(); // enforced by clap + rt.cli() + .subcommand_name() + .map_or_else( + || { + let add = rt.cli().value_of("add"); + let rem = rt.cli().value_of("remove"); + let set = rt.cli().value_of("set"); + + alter(&rt, id, add, rem, set); + }, + |name| { + debug!("Call: {}", name); + match name { + "list" => list(id, &rt), + _ => { + warn!("Unknown command"); + // More error handling + }, + }; + }); +} + +fn alter(rt: &Runtime, id: &str, add: Option<&str>, rem: Option<&str>, set: Option<&str>) { + let path = build_entry_path(rt, id); + debug!("path = {:?}", path); + rt.store() + // "id" must be present, enforced via clap spec + .retrieve(path) + .map(|mut e| { + add.map(|tags| { + let tags = tags.split(","); + for tag in tags { + info!("Adding tag '{}'", tag); + if let Err(e) = e.add_tag(String::from(tag)) { + trace_error(&e); + } + } + }); + + rem.map(|tags| { + let tags = tags.split(","); + for tag in tags { + info!("Removing tag '{}'", tag); + if let Err(e) = e.remove_tag(String::from(tag)) { + trace_error(&e); + } + } + }); + + set.map(|tags| { + info!("Setting tags '{}'", tags); + let tags = tags.split(",").map(String::from).collect(); + if let Err(e) = e.set_tags(tags) { + trace_error(&e); + } + }); + }) + .map_err(|e| { + info!("No entry."); + trace_error(&e); + }) + .ok(); +} + +fn list(id: &str, rt: &Runtime) { + let path = build_entry_path(rt, id); + debug!("path = {:?}", path); + + let entry = rt.store().retrieve(path.clone()); + if entry.is_err() { + debug!("Could not retrieve '{:?}' => {:?}", id, path); + warn!("Could not retrieve entry '{}'", id); + trace_error(&entry.err().unwrap()); + exit(1); + } + let entry = entry.unwrap(); + + let scmd = rt.cli().subcommand_matches("list").unwrap(); // safe, we checked in main() + + let json_out = scmd.is_present("json"); + let line_out = scmd.is_present("linewise"); + let sepp_out = scmd.is_present("sep"); + let mut comm_out = scmd.is_present("commasep"); + + if !vec![json_out, line_out, comm_out, sepp_out].iter().any(|v| *v) { + // None of the flags passed, go to default + comm_out = true; + } + + let tags = entry.get_tags(); + if tags.is_err() { + trace_error(&tags.err().unwrap()); + exit(1); + } + let tags = tags.unwrap(); + + if json_out { + unimplemented!() + } + + if line_out { + for tag in &tags { + println!("{}", tag); + } + } + + if sepp_out { + let sepp = scmd.value_of("sep").unwrap(); // we checked before + println!("{}", tags.join(sepp)); + } + + if comm_out { + println!("{}", tags.join(", ")); + } +} + diff --git a/imag-tag/src/ui.rs b/imag-tag/src/ui.rs new file mode 100644 index 00000000..4f78bf1f --- /dev/null +++ b/imag-tag/src/ui.rs @@ -0,0 +1,75 @@ +use clap::{Arg, App, ArgGroup, SubCommand}; + +pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { + app.arg(Arg::with_name("id") + .long("id") + .short("i") + .takes_value(true) + .required(true) + .help("Use this entry")) + + .arg(Arg::with_name("add") + .long("add") + .short("a") + .takes_value(true) + .required(false) + .multiple(true) + .help("Add this tag")) + + .arg(Arg::with_name("remove") + .long("remove") + .short("r") + .takes_value(true) + .required(false) + .multiple(true) + .help("Remove this tag")) + + .arg(Arg::with_name("set") + .long("set") + .short("s") + .takes_value(true) + .required(false) + .multiple(true) + .help("Set these tags")) + + .subcommand(SubCommand::with_name("list") + .about("List tags (default)") + .version("0.1") + .arg(Arg::with_name("json") + .long("json") + .short("j") + .takes_value(false) + .required(false) + .help("List as JSON")) + .arg(Arg::with_name("linewise") + .long("linewise") + .short("l") + .takes_value(false) + .required(false) + .help("One tag per line")) + .arg(Arg::with_name("commasep") + .long("comma") + .short("c") + .takes_value(false) + .required(false) + .help("Commaseperated (default)")) + .arg(Arg::with_name("sep") + .long("sep") + .short("s") + .takes_value(true) + .required(false) + .help("Seperated by string")) + + .group(ArgGroup::with_name("list-group") + .args(&[ + "json", + "linewise", + "commasep", + "sep", + ]) + .required(true)) + ) + +} + + diff --git a/imag-tag/src/util.rs b/imag-tag/src/util.rs new file mode 100644 index 00000000..b33e045d --- /dev/null +++ b/imag-tag/src/util.rs @@ -0,0 +1,36 @@ +use std::path::PathBuf; + +use semver::Version; + +use libimagrt::runtime::Runtime; + +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 +} + diff --git a/libimagtag/src/tagable.rs b/libimagtag/src/tagable.rs index f38f49d6..70eaec02 100644 --- a/libimagtag/src/tagable.rs +++ b/libimagtag/src/tagable.rs @@ -1,4 +1,7 @@ -use libimagstore::store::{Entry, EntryHeader}; +use std::ops::Deref; +use std::ops::DerefMut; + +use libimagstore::store::{Entry, EntryHeader, FileLockEntry}; use error::{TagError, TagErrorKind}; use result::Result; @@ -163,3 +166,32 @@ impl Tagable for Entry { } } + +impl<'a> Tagable for FileLockEntry<'a> { + + fn get_tags(&self) -> Result> { + self.deref().get_tags() + } + + fn set_tags(&mut self, ts: Vec) -> Result<()> { + self.deref_mut().set_tags(ts) + } + + fn add_tag(&mut self, t: Tag) -> Result<()> { + self.deref_mut().add_tag(t) + } + + fn remove_tag(&mut self, t: Tag) -> Result<()> { + self.deref_mut().remove_tag(t) + } + + fn has_tag(&self, t: &Tag) -> Result { + self.deref().has_tag(t) + } + + fn has_tags(&self, ts: &Vec) -> Result { + self.deref().has_tags(ts) + } + +} +