Merge pull request #174 from matthiasbeyer/libimagtag/init
Libimagtag/init
This commit is contained in:
commit
8ba96591c2
8 changed files with 345 additions and 0 deletions
14
libimagtag/Cargo.toml
Normal file
14
libimagtag/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "libimagtag"
|
||||
version = "0.1.0"
|
||||
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
||||
|
||||
[dependencies]
|
||||
clap = "2.1.1"
|
||||
log = "0.3.5"
|
||||
regex = "0.1.47"
|
||||
toml = "0.1.25"
|
||||
|
||||
[dependencies.libimagstore]
|
||||
path = "../libimagstore"
|
||||
|
70
libimagtag/src/error.rs
Normal file
70
libimagtag/src/error.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::Error as FmtError;
|
||||
use std::clone::Clone;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TagErrorKind {
|
||||
TagTypeError,
|
||||
HeaderReadError,
|
||||
HeaderWriteError,
|
||||
NotATag,
|
||||
}
|
||||
|
||||
fn tag_error_type_as_str(e: &TagErrorKind) -> &'static str {
|
||||
match e {
|
||||
&TagErrorKind::TagTypeError => "Entry Header Tag Type wrong",
|
||||
&TagErrorKind::HeaderReadError => "Error while reading entry header",
|
||||
&TagErrorKind::HeaderWriteError => "Error while writing entry header",
|
||||
&TagErrorKind::NotATag => "String is not a tag",
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TagErrorKind {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
|
||||
try!(write!(fmt, "{}", tag_error_type_as_str(self)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TagError {
|
||||
kind: TagErrorKind,
|
||||
cause: Option<Box<Error>>,
|
||||
}
|
||||
|
||||
impl TagError {
|
||||
|
||||
pub fn new(errtype: TagErrorKind, cause: Option<Box<Error>>) -> TagError {
|
||||
TagError {
|
||||
kind: errtype,
|
||||
cause: cause,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Display for TagError {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
|
||||
try!(write!(fmt, "[{}]", tag_error_type_as_str(&self.kind.clone())));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Error for TagError {
|
||||
|
||||
fn description(&self) -> &str {
|
||||
tag_error_type_as_str(&self.kind.clone())
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
self.cause.as_ref().map(|e| &**e)
|
||||
}
|
||||
|
||||
}
|
||||
|
14
libimagtag/src/lib.rs
Normal file
14
libimagtag/src/lib.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
extern crate clap;
|
||||
#[macro_use] extern crate log;
|
||||
extern crate regex;
|
||||
extern crate toml;
|
||||
|
||||
extern crate libimagstore;
|
||||
|
||||
pub mod error;
|
||||
pub mod result;
|
||||
pub mod tag;
|
||||
pub mod tagable;
|
||||
pub mod util;
|
||||
pub mod ui;
|
||||
|
6
libimagtag/src/result.rs
Normal file
6
libimagtag/src/result.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
use std::result::Result as RResult;
|
||||
|
||||
use error::TagError;
|
||||
|
||||
pub type Result<T> = RResult<T, TagError>;
|
||||
|
1
libimagtag/src/tag.rs
Normal file
1
libimagtag/src/tag.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub type Tag = String;
|
165
libimagtag/src/tagable.rs
Normal file
165
libimagtag/src/tagable.rs
Normal file
|
@ -0,0 +1,165 @@
|
|||
use libimagstore::store::{Entry, EntryHeader};
|
||||
|
||||
use error::{TagError, TagErrorKind};
|
||||
use result::Result;
|
||||
use tag::Tag;
|
||||
use util::is_tag;
|
||||
|
||||
use toml::Value;
|
||||
|
||||
pub trait Tagable {
|
||||
|
||||
fn get_tags(&self) -> Result<Vec<Tag>>;
|
||||
fn set_tags(&mut self, ts: Vec<Tag>) -> Result<()>;
|
||||
|
||||
fn add_tag(&mut self, t: Tag) -> Result<()>;
|
||||
fn remove_tag(&mut self, t: Tag) -> Result<()>;
|
||||
|
||||
fn has_tag(&self, t: &Tag) -> Result<bool>;
|
||||
fn has_tags(&self, ts: &Vec<Tag>) -> Result<bool>;
|
||||
|
||||
}
|
||||
|
||||
impl Tagable for EntryHeader {
|
||||
|
||||
fn get_tags(&self) -> Result<Vec<Tag>> {
|
||||
let tags = self.read("imag.tags");
|
||||
if tags.is_err() {
|
||||
let kind = TagErrorKind::HeaderReadError;
|
||||
return Err(TagError::new(kind, Some(Box::new(tags.err().unwrap()))));
|
||||
}
|
||||
let tags = tags.unwrap();
|
||||
|
||||
match tags {
|
||||
Some(Value::Array(tags)) => {
|
||||
if !tags.iter().all(|t| match t { &Value::String(_) => true, _ => false }) {
|
||||
return Err(TagError::new(TagErrorKind::TagTypeError, None));
|
||||
}
|
||||
if tags.iter().any(|t| match t {
|
||||
&Value::String(ref s) => !is_tag(&s),
|
||||
_ => unreachable!()})
|
||||
{
|
||||
return Err(TagError::new(TagErrorKind::NotATag, None));
|
||||
}
|
||||
|
||||
Ok(tags.iter()
|
||||
.cloned()
|
||||
.map(|t| {
|
||||
match t {
|
||||
Value::String(s) => s,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
},
|
||||
None => Ok(vec![]),
|
||||
_ => Err(TagError::new(TagErrorKind::TagTypeError, None)),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_tags(&mut self, ts: Vec<Tag>) -> Result<()> {
|
||||
if ts.iter().any(|tag| !is_tag(tag)) {
|
||||
debug!("Not a tag: '{}'", ts.iter().filter(|t| !is_tag(t)).next().unwrap());
|
||||
return Err(TagError::new(TagErrorKind::NotATag, None));
|
||||
}
|
||||
|
||||
let a = ts.iter().map(|t| Value::String(t.clone())).collect();
|
||||
self.set("imag.tags", Value::Array(a))
|
||||
.map(|_| ())
|
||||
.map_err(|e| TagError::new(TagErrorKind::HeaderWriteError, Some(Box::new(e))))
|
||||
}
|
||||
|
||||
fn add_tag(&mut self, t: Tag) -> Result<()> {
|
||||
if !is_tag(&t) {
|
||||
debug!("Not a tag: '{}'", t);
|
||||
return Err(TagError::new(TagErrorKind::NotATag, None));
|
||||
}
|
||||
|
||||
self.get_tags()
|
||||
.map(|mut tags| {
|
||||
tags.push(t);
|
||||
self.set_tags(tags)
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
fn remove_tag(&mut self, t: Tag) -> Result<()> {
|
||||
if !is_tag(&t) {
|
||||
debug!("Not a tag: '{}'", t);
|
||||
return Err(TagError::new(TagErrorKind::NotATag, None));
|
||||
}
|
||||
|
||||
self.get_tags()
|
||||
.map(|mut tags| {
|
||||
tags.retain(|tag| tag.clone() != t);
|
||||
self.set_tags(tags)
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
fn has_tag(&self, t: &Tag) -> Result<bool> {
|
||||
let tags = self.read("imag.tags");
|
||||
if tags.is_err() {
|
||||
let kind = TagErrorKind::HeaderReadError;
|
||||
return Err(TagError::new(kind, Some(Box::new(tags.err().unwrap()))));
|
||||
}
|
||||
let tags = tags.unwrap();
|
||||
|
||||
if !tags.iter().all(|t| match t { &Value::String(_) => true, _ => false }) {
|
||||
return Err(TagError::new(TagErrorKind::TagTypeError, None));
|
||||
}
|
||||
|
||||
Ok(tags
|
||||
.iter()
|
||||
.any(|tag| {
|
||||
match tag {
|
||||
&Value::String(ref s) => { s == t },
|
||||
_ => unreachable!()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn has_tags(&self, tags: &Vec<Tag>) -> Result<bool> {
|
||||
let mut result = true;
|
||||
for tag in tags {
|
||||
let check = self.has_tag(tag);
|
||||
if check.is_err() {
|
||||
return Err(check.err().unwrap());
|
||||
}
|
||||
let check = check.unwrap();
|
||||
|
||||
result = result && check;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Tagable for Entry {
|
||||
|
||||
fn get_tags(&self) -> Result<Vec<Tag>> {
|
||||
self.get_header().get_tags()
|
||||
}
|
||||
|
||||
fn set_tags(&mut self, ts: Vec<Tag>) -> Result<()> {
|
||||
self.get_header_mut().set_tags(ts)
|
||||
}
|
||||
|
||||
fn add_tag(&mut self, t: Tag) -> Result<()> {
|
||||
self.get_header_mut().add_tag(t)
|
||||
}
|
||||
|
||||
fn remove_tag(&mut self, t: Tag) -> Result<()> {
|
||||
self.get_header_mut().remove_tag(t)
|
||||
}
|
||||
|
||||
fn has_tag(&self, t: &Tag) -> Result<bool> {
|
||||
self.get_header().has_tag(t)
|
||||
}
|
||||
|
||||
fn has_tags(&self, ts: &Vec<Tag>) -> Result<bool> {
|
||||
self.get_header().has_tags(ts)
|
||||
}
|
||||
|
||||
}
|
70
libimagtag/src/ui.rs
Normal file
70
libimagtag/src/ui.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use clap::{Arg, ArgMatches, App, SubCommand};
|
||||
|
||||
use tag::Tag;
|
||||
|
||||
/// Generates a clap::SubCommand to be integrated in the commandline-ui builder for building a
|
||||
/// "tags --add foo --remove bar" subcommand to do tagging action.
|
||||
pub fn tag_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name("tags")
|
||||
.author("Matthias Beyer <mail@beyermatthias.de>")
|
||||
.version("0.1")
|
||||
.about("Add or remove tags")
|
||||
|
||||
.arg(Arg::with_name("add-tags")
|
||||
.short("a")
|
||||
.long("add")
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.help("Add tags, seperated by comma or by specifying multiple times"))
|
||||
|
||||
.arg(Arg::with_name("remove-tags")
|
||||
.short("r")
|
||||
.long("remove")
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.help("Remove tags, seperated by comma or by specifying multiple times"))
|
||||
}
|
||||
|
||||
/// Generates a clap::Arg which can be integrated into the commandline-ui builder for building a
|
||||
/// "-t" or "--tags" argument which takes values for tagging actions (add, remove)
|
||||
pub fn tag_argument<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name("specify-tags")
|
||||
.short("t")
|
||||
.long("tags")
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.help("Add or remove tags, prefixed by '+' (for adding) or '-' (for removing)")
|
||||
}
|
||||
|
||||
/// Get the tags which should be added from the commandline
|
||||
///
|
||||
/// Returns none if the argument was not specified
|
||||
pub fn get_add_tags(matches: &ArgMatches) -> Option<Vec<Tag>> {
|
||||
extract_tags(matches, "add-tags", '+')
|
||||
}
|
||||
|
||||
/// Get the tags which should be removed from the commandline
|
||||
///
|
||||
/// Returns none if the argument was not specified
|
||||
pub fn get_remove_tags(matches: &ArgMatches) -> Option<Vec<Tag>> {
|
||||
extract_tags(matches, "remove-tags", '-')
|
||||
}
|
||||
|
||||
fn extract_tags(matches: &ArgMatches, specifier: &str, specchar: char) -> Option<Vec<Tag>> {
|
||||
if let Some(submatch) = matches.subcommand_matches("tags") {
|
||||
submatch.values_of(specifier)
|
||||
.map(|values| values.map(String::from).collect())
|
||||
} else {
|
||||
matches.values_of("specify-tags")
|
||||
.map(|argmatches| {
|
||||
argmatches
|
||||
.map(String::from)
|
||||
.filter(|s| s.chars().next() == Some(specchar))
|
||||
.map(|s| {
|
||||
String::from(s.split_at(1).1)
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
5
libimagtag/src/util.rs
Normal file
5
libimagtag/src/util.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use regex::Regex;
|
||||
|
||||
pub fn is_tag(s: &String) -> bool {
|
||||
Regex::new("^[a-zA-Z]([a-zA-Z0-9_-]*)$").unwrap().captures(&s[..]).is_some()
|
||||
}
|
Loading…
Reference in a new issue