Merge branch 'master' into libimagerror/integration

This merge solved a _LOT_ of conflicts and was a rather complicated one,
as parts of the conflict-resolution involved rewriting of half the
stuff.

This merge commit fixes all the things so a `cargo check --all`
succeeds, but I did not yet check whether tests run without failure.
This commit is contained in:
Matthias Beyer 2017-09-04 23:02:45 +02:00
commit c115215fa4
67 changed files with 2751 additions and 1354 deletions

View file

@ -29,6 +29,7 @@ members = [
"lib/entry/libimagentrydatetime",
"lib/entry/libimagentryedit",
"lib/entry/libimagentryfilter",
"lib/entry/libimagentrygps",
"lib/entry/libimagentrylink",
"lib/entry/libimagentrylist",
"lib/entry/libimagentrymarkdown",

View file

@ -33,3 +33,9 @@ path = "../../../lib/etc/libimagutil"
default-features = false
features = ["testing"]
[dev-dependencies.libimagrt]
version = "0.4.0"
path = "../../../lib/core/libimagrt"
default-features = false
features = ["testing"]

View file

@ -108,40 +108,39 @@ fn handle_internal_linking(rt: &Runtime) {
}
}
match cmd.value_of("list") {
Some(list) => handle_internal_linking_list_call(rt, cmd, list),
None => {
match cmd.subcommand_name() {
Some("add") => {
let (mut from, to) = get_from_to_entry(&rt, "add");
for mut to_entry in to {
if let Err(e) = to_entry.add_internal_link(&mut from) {
trace_error_exit(&e, 1);
}
}
},
Some("remove") => {
let (mut from, to) = get_from_to_entry(&rt, "remove");
for mut to_entry in to {
if let Err(e) = to_entry.remove_internal_link(&mut from) {
trace_error_exit(&e, 1);
}
}
},
_ => unreachable!(),
match cmd.subcommand_name() {
Some("list") => {
cmd.subcommand_matches("list")
.map(|matches| handle_internal_linking_list_call(rt, cmd, matches));
},
Some("add") => {
let (mut from, to) = get_from_to_entry(&rt, "add");
for mut to_entry in to {
if let Err(e) = to_entry.add_internal_link(&mut from) {
trace_error_exit(&e, 1);
}
};
}
},
Some("remove") => {
let (mut from, to) = get_from_to_entry(&rt, "remove");
for mut to_entry in to {
if let Err(e) = to_entry.remove_internal_link(&mut from) {
trace_error_exit(&e, 1);
}
};
},
_ => unreachable!(),
}
}
#[inline]
fn handle_internal_linking_list_call(rt: &Runtime, cmd: &ArgMatches, list: &str) {
fn handle_internal_linking_list_call(rt: &Runtime, cmd: &ArgMatches, list: &ArgMatches) {
use libimagentrylink::external::is_external_link_storeid;
debug!("List...");
for entry in list.split(',') {
for entry in list.values_of("entries").unwrap() { // clap has our back
debug!("Listing for '{}'", entry);
match get_entry_by_name(rt, entry) {
Ok(Some(e)) => {
@ -362,6 +361,7 @@ mod tests {
with help "imag-link mocking app";
}
use self::mock::generate_test_runtime;
use self::mock::reset_test_runtime;
use libimagutil::testing::DEFAULT_ENTRY;
fn create_test_default_entry<'a, S: AsRef<OsStr>>(rt: &'a Runtime, name: S) -> StoreResult<StoreId> {
@ -392,7 +392,7 @@ mod tests {
#[test]
fn test_link_modificates() {
let rt = generate_test_runtime(vec!["internal", "add", "--from", "test1", "--to", "test2"])
let rt = generate_test_runtime(vec!["internal", "add", "test1", "test2"])
.unwrap();
let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
@ -412,7 +412,7 @@ mod tests {
#[test]
fn test_linking_links() {
let rt = generate_test_runtime(vec!["internal", "add", "--from", "test1", "--to", "test2"])
let rt = generate_test_runtime(vec!["internal", "add", "test1", "test2"])
.unwrap();
let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
@ -432,7 +432,7 @@ mod tests {
#[test]
fn test_multilinking() {
let rt = generate_test_runtime(vec!["internal", "add", "--from", "test1", "--to", "test2"])
let rt = generate_test_runtime(vec!["internal", "add", "test1", "test2"])
.unwrap();
let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
@ -450,4 +450,87 @@ mod tests {
assert_eq!(*test_links1, links_toml_value(vec!["test2"]));
assert_eq!(*test_links2, links_toml_value(vec!["test1"]));
}
#[test]
fn test_linking_more_than_two() {
let rt = generate_test_runtime(vec!["internal", "add", "test1", "test2", "test3"])
.unwrap();
let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
let test_id3 = create_test_default_entry(&rt, "test3").unwrap();
handle_internal_linking(&rt);
handle_internal_linking(&rt);
let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
let test_links1 = get_entry_links(&test_entry1).unwrap();
let test_entry2 = rt.store().get(test_id2).unwrap().unwrap();
let test_links2 = get_entry_links(&test_entry2).unwrap();
let test_entry3 = rt.store().get(test_id3).unwrap().unwrap();
let test_links3 = get_entry_links(&test_entry3).unwrap();
assert_eq!(*test_links1, links_toml_value(vec!["test2", "test3"]));
assert_eq!(*test_links2, links_toml_value(vec!["test1"]));
assert_eq!(*test_links3, links_toml_value(vec!["test1"]));
}
// Remove tests
#[test]
fn test_linking_links_unlinking_removes_links() {
let rt = generate_test_runtime(vec!["internal", "add", "test1", "test2"])
.unwrap();
let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
handle_internal_linking(&rt);
let rt = reset_test_runtime(vec!["internal", "remove", "test1", "test2"], rt)
.unwrap();
handle_internal_linking(&rt);
let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
let test_links1 = get_entry_links(&test_entry1).unwrap();
let test_entry2 = rt.store().get(test_id2).unwrap().unwrap();
let test_links2 = get_entry_links(&test_entry2).unwrap();
assert_eq!(*test_links1, links_toml_value(vec![]));
assert_eq!(*test_links2, links_toml_value(vec![]));
}
#[test]
fn test_linking_and_unlinking_more_than_two() {
let rt = generate_test_runtime(vec!["internal", "add", "test1", "test2", "test3"])
.unwrap();
let test_id1 = create_test_default_entry(&rt, "test1").unwrap();
let test_id2 = create_test_default_entry(&rt, "test2").unwrap();
let test_id3 = create_test_default_entry(&rt, "test3").unwrap();
handle_internal_linking(&rt);
let rt = reset_test_runtime(vec!["internal", "remove", "test1", "test2", "test3"], rt)
.unwrap();
handle_internal_linking(&rt);
let test_entry1 = rt.store().get(test_id1).unwrap().unwrap();
let test_links1 = get_entry_links(&test_entry1).unwrap();
let test_entry2 = rt.store().get(test_id2).unwrap().unwrap();
let test_links2 = get_entry_links(&test_entry2).unwrap();
let test_entry3 = rt.store().get(test_id3).unwrap().unwrap();
let test_links3 = get_entry_links(&test_entry3).unwrap();
assert_eq!(*test_links1, links_toml_value(vec![]));
assert_eq!(*test_links2, links_toml_value(vec![]));
assert_eq!(*test_links3, links_toml_value(vec![]));
}
}

View file

@ -28,15 +28,14 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.about("Add link from one entry to another (and vice-versa)")
.version("0.1")
.arg(Arg::with_name("from")
.long("from")
.short("f")
.index(1)
.takes_value(true)
.required(true)
.multiple(false)
.help("Link from this entry")
.value_name("ENTRY"))
.arg(Arg::with_name("to")
.long("to")
.short("t")
.index(2)
.takes_value(true)
.required(true)
.multiple(true)
@ -48,15 +47,14 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.about("Remove a link between two or more entries")
.version("0.1")
.arg(Arg::with_name("from")
.long("from")
.short("f")
.index(1)
.takes_value(true)
.required(true)
.multiple(false)
.help("Remove Link from this entry")
.value_name("ENTRY"))
.arg(Arg::with_name("to")
.long("to")
.short("t")
.index(2)
.takes_value(true)
.required(true)
.multiple(true)
@ -64,20 +62,23 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.value_name("ENTRIES"))
)
.arg(Arg::with_name("list")
.long("list")
.short("l")
.takes_value(true)
.required(false)
.help("List links to this entry")
.value_name("ENTRY"))
.subcommand(SubCommand::with_name("list")
.about("List links to this entry")
.version("0.1")
.arg(Arg::with_name("entries")
.index(1)
.takes_value(true)
.multiple(true)
.required(true)
.help("List these entries, seperate by comma")
.value_name("ENTRIES"))
.arg(Arg::with_name("list-externals-too")
.long("list-external")
.takes_value(false)
.required(false)
.help("If --list is provided, also list external links (debugging helper that might be removed at some point"))
.arg(Arg::with_name("list-externals-too")
.long("list-external")
.takes_value(false)
.required(false)
.help("If --list is provided, also list external links (debugging helper that might be removed at some point"))
)
.arg(Arg::with_name("check-consistency")
.long("check-consistency")

View file

@ -48,7 +48,7 @@ use ui::build_ui;
use std::path::PathBuf;
use libimagentryref::reference::Ref;
use libimagentryref::refstore::RefStore;
use libimagentryref::flags::RefFlags;
use libimagerror::trace::trace_error;
use libimagrt::setup::generate_runtime_setup;
@ -82,7 +82,7 @@ fn add(rt: &Runtime) {
.with_content_hashing(cmd.is_present("track-content"))
.with_permission_tracking(cmd.is_present("track-permissions"));
match Ref::create(rt.store(), path, flags) {
match RefStore::create(rt.store(), path, flags) {
Ok(r) => {
debug!("Reference created: {:?}", r);
info!("Ok");
@ -102,7 +102,7 @@ fn remove(rt: &Runtime) {
let yes = cmd.is_present("yes");
if yes || ask_bool(&format!("Delete Ref with hash '{}'", hash)[..], None) {
match Ref::delete_by_hash(rt.store(), hash) {
match rt.store().delete_by_hash(hash) {
Err(e) => trace_error(&e),
Ok(_) => info!("Ok"),
}
@ -126,7 +126,7 @@ fn list(rt: &Runtime) {
let iter = match rt.store().retrieve_for_module("ref") {
Ok(iter) => iter.filter_map(|id| {
match Ref::get(rt.store(), id) {
match rt.store().get(id) {
Ok(r) => Some(r),
Err(e) => {
trace_error(&e);
@ -145,7 +145,7 @@ fn list(rt: &Runtime) {
.check_changed(do_check_changed)
.check_changed_content(do_check_changed_content)
.check_changed_permiss(do_check_changed_permiss)
.list(iter.map(|e| e.into()))
.list(iter.filter_map(Into::into))
.ok();
}

View file

@ -28,3 +28,18 @@ libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
[features]
early-panic = [ "libimagstore/early-panic" ]
[dev-dependencies.libimagutil]
version = "0.4.0"
path = "../../../lib/etc/libimagutil"
default-features = false
features = ["testing"]
[dev-dependencies.libimagrt]
version = "0.4.0"
path = "../../../lib/core/libimagrt"
default-features = false
features = ["testing"]
[dev-dependencies.toml-query]
version = "0.3"

View file

@ -178,3 +178,39 @@ fn string_from_raw_src(raw_src: &str) -> String {
}
content
}
#[cfg(test)]
mod tests {
use super::create;
use std::path::PathBuf;
use toml_query::read::TomlValueReadExt;
use toml::Value;
make_mock_app! {
app "imag-link";
modulename mock;
version "0.4.0";
with help "imag-link mocking app";
}
use self::mock::generate_test_runtime;
#[test]
fn test_create_simple() {
let test_name = "test_create_simple";
let rt = generate_test_runtime(vec!["create", "-p", "test_create_simple"]).unwrap();
create(&rt);
let e = rt.store().get(PathBuf::from(test_name));
assert!(e.is_ok());
let e = e.unwrap();
assert!(e.is_some());
let e = e.unwrap();
let version = e.get_header().read("imag.version").map(Option::unwrap).unwrap();
assert_eq!(Value::String(String::from("0.4.0")), *version);
}
}

View file

@ -46,3 +46,38 @@ pub fn delete(rt: &Runtime) {
.or_else(|| warn_exit("No subcommand 'delete'. Will exit now", 1));
}
#[cfg(test)]
mod tests {
use create::create;
use super::delete;
use std::path::PathBuf;
make_mock_app! {
app "imag-link";
modulename mock;
version "0.4.0";
with help "imag-link mocking app";
}
use self::mock::generate_test_runtime;
use self::mock::reset_test_runtime;
#[test]
fn test_create_simple() {
let test_name = "test_create_simple";
let rt = generate_test_runtime(vec!["create", "-p", "test_create_simple"]).unwrap();
create(&rt);
let rt = reset_test_runtime(vec!["delete", "--id", "test_create_simple"], rt).unwrap();
delete(&rt);
let e = rt.store().get(PathBuf::from(test_name));
assert!(e.is_ok());
let e = e.unwrap();
assert!(e.is_none());
}
}

View file

@ -35,14 +35,21 @@
extern crate clap;
#[macro_use] extern crate log;
extern crate toml;
#[cfg(test)] extern crate toml_query;
#[macro_use] extern crate version;
#[macro_use] extern crate error_chain;
extern crate libimagrt;
extern crate libimagstore;
extern crate libimagutil;
extern crate libimagerror;
#[cfg(test)]
#[macro_use]
extern crate libimagutil;
#[cfg(not(test))]
extern crate libimagutil;
use libimagrt::setup::generate_runtime_setup;
mod create;

View file

@ -35,3 +35,9 @@ path = "../../../lib/etc/libimagutil"
default-features = false
features = ["testing"]
[dev-dependencies.libimagrt]
version = "0.4.0"
path = "../../../lib/core/libimagrt"
default-features = false
features = ["testing"]

View file

@ -48,10 +48,11 @@ use libimagrt::setup::generate_runtime_setup;
use libimagentrytag::tagable::Tagable;
use libimagentrytag::tag::Tag;
use libimagerror::trace::{trace_error, trace_error_exit};
use libimagentrytag::ui::{get_add_tags, get_remove_tags};
use libimagstore::storeid::StoreId;
use libimagutil::warn_exit::warn_exit;
use clap::ArgMatches;
mod ui;
use ui::build_ui;
@ -181,6 +182,42 @@ fn list(id: PathBuf, rt: &Runtime) {
}
}
/// Get the tags which should be added from the commandline
///
/// Returns none if the argument was not specified
fn get_add_tags(matches: &ArgMatches) -> Option<Vec<Tag>> {
let a = "add-tags";
extract_tags(matches, a, '+')
.or_else(|| matches.values_of(a).map(|values| values.map(String::from).collect()))
}
/// Get the tags which should be removed from the commandline
///
/// Returns none if the argument was not specified
fn get_remove_tags(matches: &ArgMatches) -> Option<Vec<Tag>> {
let r = "remove-tags";
extract_tags(matches, r, '+')
.or_else(|| matches.values_of(r).map(|values| values.map(String::from).collect()))
}
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.starts_with(specchar))
.map(|s| {
String::from(s.split_at(1).1)
})
.collect()
})
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
@ -190,12 +227,11 @@ mod tests {
use toml_query::read::TomlValueReadExt;
use toml_query::error::Result as TomlQueryResult;
use libimagentrytag::ui::{get_add_tags, get_remove_tags};
use libimagrt::runtime::Runtime;
use libimagstore::storeid::StoreId;
use libimagstore::store::{Result as StoreResult, FileLockEntry};
use super::alter;
use super::*;
make_mock_app! {
app "imag-tag";

View file

@ -19,7 +19,7 @@
use clap::{Arg, App, ArgGroup, SubCommand};
use libimagentrytag::ui::{tag_add_arg, tag_remove_arg};
use libimagentrytag::tag::is_tag;
pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
app.arg(Arg::with_name("id")
@ -30,8 +30,22 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.help("Use this entry")
.value_name("ID"))
.arg(tag_add_arg())
.arg(tag_remove_arg())
.arg(Arg::with_name("add-tags")
.short("a")
.long("add")
.takes_value(true)
.value_name("tags")
.multiple(true)
.validator(is_tag)
.help("Add tags, seperated by comma or by specifying multiple times"))
.arg(Arg::with_name("remove-tags")
.short("r")
.long("remove")
.takes_value(true)
.value_name("tags")
.multiple(true)
.validator(is_tag)
.help("Remove tags, seperated by comma or by specifying multiple times"))
.subcommand(SubCommand::with_name("list")
.about("List tags (default)")

View file

@ -21,5 +21,4 @@ version = "2.0.1"
libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
libimagbookmark = { version = "0.4.0", path = "../../../lib/domain/libimagbookmark" }
libimagentrytag = { version = "0.4.0", path = "../../../lib/entry/libimagentrytag" }
libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }

View file

@ -37,7 +37,6 @@ extern crate clap;
#[macro_use] extern crate version;
extern crate libimagbookmark;
extern crate libimagentrytag;
extern crate libimagrt;
extern crate libimagerror;
extern crate libimagutil;

View file

@ -19,7 +19,6 @@
use clap::{Arg, App, SubCommand};
use libimagentrytag::ui::tag_add_arg;
use libimagutil::cli_validators::*;
pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
@ -44,7 +43,6 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.value_name("URL")
.validator(is_url)
.help("Add this URL, multiple possible"))
.arg(tag_add_arg())
)
.subcommand(SubCommand::with_name("remove")

View file

@ -19,16 +19,18 @@
use std::process::exit;
use clap::ArgMatches;
use libimagdiary::diary::Diary;
use libimagdiary::diaryid::DiaryId;
use libimagdiary::error::DiaryErrorKind as DEK;
use libimagdiary::error::ResultExt;
use libimagentryedit::edit::Edit;
use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error;
use libimagdiary::entry::Entry;
use libimagdiary::error::Result;
use libimagerror::trace::trace_error_exit;
use libimagutil::warn_exit::warn_exit;
use libimagstore::store::FileLockEntry;
use libimagstore::store::Store;
use util::get_diary_name;
@ -36,88 +38,90 @@ pub fn create(rt: &Runtime) {
let diaryname = get_diary_name(rt)
.unwrap_or_else( || warn_exit("No diary selected. Use either the configuration file or the commandline option", 1));
let prevent_edit = rt.cli().subcommand_matches("create").unwrap().is_present("no-edit");
let mut entry = create_entry(rt.store(), &diaryname, rt);
fn create_entry<'a>(diary: &'a Diary, rt: &Runtime) -> Result<Entry<'a>> {
use std::str::FromStr;
let create = rt.cli().subcommand_matches("create").unwrap();
if !create.is_present("timed") {
debug!("Creating non-timed entry");
diary.new_entry_today()
} else {
let id = match create.value_of("timed") {
Some("h") | Some("hourly") => {
debug!("Creating hourly-timed entry");
let time = DiaryId::now(String::from(diary.name()));
let hr = create
.value_of("hour")
.map(|v| { debug!("Creating hourly entry with hour = {:?}", v); v })
.and_then(|s| {
FromStr::from_str(s)
.map_err(|_| warn!("Could not parse hour: '{}'", s))
.ok()
})
.unwrap_or(time.hour());
time.with_hour(hr).with_minute(0)
},
Some("m") | Some("minutely") => {
debug!("Creating minutely-timed entry");
let time = DiaryId::now(String::from(diary.name()));
let hr = create
.value_of("hour")
.map(|h| { debug!("hour = {:?}", h); h })
.and_then(|s| {
FromStr::from_str(s)
.map_err(|_| warn!("Could not parse hour: '{}'", s))
.ok()
})
.unwrap_or(time.hour());
let min = create
.value_of("minute")
.map(|m| { debug!("minute = {:?}", m); m })
.and_then(|s| {
FromStr::from_str(s)
.map_err(|_| warn!("Could not parse minute: '{}'", s))
.ok()
})
.unwrap_or(time.minute());
time.with_hour(hr).with_minute(min)
},
Some(_) => {
warn!("Timed creation failed: Unknown spec '{}'",
create.value_of("timed").unwrap());
exit(1);
},
None => warn_exit("Unexpected error, cannot continue", 1)
};
diary.new_entry_by_id(id)
}
}
let diary = Diary::open(rt.store(), &diaryname[..]);
let res = create_entry(&diary, rt)
.and_then(|mut entry| {
if prevent_edit {
debug!("Not editing new diary entry");
Ok(())
} else {
debug!("Editing new diary entry");
entry.edit_content(rt).chain_err(|| DEK::DiaryEditError)
}
});
let res = if rt.cli().subcommand_matches("create").unwrap().is_present("no-edit") {
debug!("Not editing new diary entry");
Ok(())
} else {
debug!("Editing new diary entry");
entry.edit_content(rt)
.chain_err(|| DEK::DiaryEditError)
};
if let Err(e) = res {
trace_error(&e);
trace_error_exit(&e, 1);
} else {
info!("Ok!");
}
}
fn create_entry<'a>(diary: &'a Store, diaryname: &str, rt: &Runtime) -> FileLockEntry<'a> {
let create = rt.cli().subcommand_matches("create").unwrap();
let entry = if !create.is_present("timed") {
debug!("Creating non-timed entry");
diary.new_entry_today(diaryname)
} else {
let id = create_id_from_clispec(&create, &diaryname);
diary.retrieve(id).chain_err(|| DEK::StoreReadError)
};
match entry {
Err(e) => trace_error_exit(&e, 1),
Ok(e) => {
debug!("Created: {}", e.get_location());
e
}
}
}
fn create_id_from_clispec(create: &ArgMatches, diaryname: &str) -> DiaryId {
use std::str::FromStr;
let get_hourly_id = |create: &ArgMatches| -> DiaryId {
let time = DiaryId::now(String::from(diaryname));
let hr = create
.value_of("hour")
.map(|v| { debug!("Creating hourly entry with hour = {:?}", v); v })
.and_then(|s| {
FromStr::from_str(s)
.map_err(|_| warn!("Could not parse hour: '{}'", s))
.ok()
})
.unwrap_or(time.hour());
time.with_hour(hr)
};
match create.value_of("timed") {
Some("h") | Some("hourly") => {
debug!("Creating hourly-timed entry");
get_hourly_id(create)
},
Some("m") | Some("minutely") => {
let time = get_hourly_id(create);
let min = create
.value_of("minute")
.map(|m| { debug!("minute = {:?}", m); m })
.and_then(|s| {
FromStr::from_str(s)
.map_err(|_| warn!("Could not parse minute: '{}'", s))
.ok()
})
.unwrap_or(time.minute());
time.with_minute(min)
},
Some(_) => {
warn!("Timed creation failed: Unknown spec '{}'",
create.value_of("timed").unwrap());
exit(1);
},
None => warn_exit("Unexpected error, cannot continue", 1)
}
}

View file

@ -17,15 +17,17 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::process::exit;
use chrono::naive::NaiveDateTime;
use libimagdiary::diary::Diary;
use libimagdiary::diaryid::DiaryId;
use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error_exit;
use libimagtimeui::datetime::DateTime;
use libimagtimeui::parse::Parse;
use libimagutil::warn_exit::warn_exit;
use libimagstore::storeid::IntoStoreId;
use util::get_diary_name;
@ -35,36 +37,34 @@ pub fn delete(rt: &Runtime) {
let diaryname = get_diary_name(rt)
.unwrap_or_else(|| warn_exit("No diary selected. Use either the configuration file or the commandline option", 1));
let diary = Diary::open(rt.store(), &diaryname[..]);
debug!("Diary opened: {:?}", diary);
let datetime : Option<NaiveDateTime> = rt
let to_del_location = rt
.cli()
.subcommand_matches("delete")
.unwrap()
.value_of("datetime")
.map(|dt| { debug!("DateTime = {:?}", dt); dt })
.and_then(DateTime::parse)
.map(|dt| dt.into());
.map(|dt| dt.into())
.ok_or_else(|| {
warn!("Not deleting entries, because missing date/time specification");
exit(1);
})
.and_then(|dt: NaiveDateTime| {
DiaryId::from_datetime(diaryname.clone(), dt)
.into_storeid()
.map(|id| rt.store().retrieve(id))
.unwrap_or_else(|e| trace_error_exit(&e, 1))
})
.unwrap_or_else(|e| trace_error_exit(&e, 1))
.get_location()
.clone();
let to_del = match datetime {
Some(dt) => Some(diary.retrieve(DiaryId::from_datetime(diaryname.clone(), dt))),
None => diary.get_youngest_entry(),
};
let to_del = match to_del {
Some(Ok(e)) => e,
Some(Err(e)) => trace_error_exit(&e, 1),
None => warn_exit("No entry", 1)
};
if !ask_bool(&format!("Deleting {:?}", to_del.get_location())[..], Some(true)) {
if !ask_bool(&format!("Deleting {:?}", to_del_location), Some(true)) {
info!("Aborting delete action");
return;
}
if let Err(e) = diary.delete_entry(to_del) {
if let Err(e) = rt.store().delete(to_del_location) {
trace_error_exit(&e, 1)
}

View file

@ -17,6 +17,8 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::process::exit;
use chrono::naive::NaiveDateTime;
use libimagdiary::diary::Diary;
@ -30,33 +32,39 @@ use libimagerror::trace::MapErrTrace;
use libimagtimeui::datetime::DateTime;
use libimagtimeui::parse::Parse;
use libimagutil::warn_exit::warn_exit;
use libimagerror::trace::trace_error_exit;
use util::get_diary_name;
pub fn edit(rt: &Runtime) {
let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1));
let diary = Diary::open(rt.store(), &diaryname[..]);
let datetime : Option<NaiveDateTime> = rt
.cli()
rt.cli()
.subcommand_matches("edit")
.unwrap()
.value_of("datetime")
.and_then(DateTime::parse)
.map(|dt| dt.into());
let to_edit = match datetime {
Some(dt) => Some(diary.retrieve(DiaryId::from_datetime(diaryname.clone(), dt))),
None => diary.get_youngest_entry(),
};
match to_edit {
Some(Ok(mut e)) => e.edit_content(rt).chain_err(|| DEK::IOError),
Some(Err(e)) => Err(e),
None => Err(DE::from_kind(DEK::EntryNotInDiary)),
}
.map_err_trace().ok();
.map(|dt| dt.into())
.map(|dt: NaiveDateTime| DiaryId::from_datetime(diaryname.clone(), dt))
.or_else(|| {
rt.store()
.get_youngest_entry_id(&diaryname)
.map(|optid| match optid {
Ok(id) => id,
Err(e) => trace_error_exit(&e, 1),
})
})
.ok_or_else(|| {
error!("No entries in diary. Aborting");
exit(1)
})
.and_then(|id| rt.store().get(id))
.map(|opte| match opte {
Some(mut e) => e.edit_content(rt).chain_err(|| DEK::IOError),
None => Err(DE::from_kind(DEK::EntryNotInDiary)),
})
.map_err_trace()
.ok();
}

View file

@ -42,9 +42,7 @@ pub fn list(rt: &Runtime) {
.unwrap_or(String::from("<<Path Parsing Error>>"))
}
let diary = Diary::open(rt.store(), &diaryname[..]);
debug!("Diary opened: {:?}", diary);
diary.entries()
Diary::entries(rt.store(), &diaryname)
.and_then(|es| {
debug!("Iterator for listing: {:?}", es);

View file

@ -27,10 +27,9 @@ use util::get_diary_name;
pub fn view(rt: &Runtime) {
let diaryname = get_diary_name(rt).unwrap_or_else(|| warn_exit("No diary name", 1));
let diary = Diary::open(rt.store(), &diaryname[..]);
let hdr = rt.cli().subcommand_matches("view").unwrap().is_present("show-header");
diary.entries()
Diary::entries(rt.store(), &diaryname)
.and_then(|entries| DV::new(hdr).view_entries(entries.into_iter().filter_map(Result::ok)))
.map_err_trace()
.ok();

View file

@ -17,5 +17,4 @@ version = "2.0.1"
libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
libimagmail = { version = "0.4.0", path = "../../../lib/domain/libimagmail" }
libimagentryref = { version = "0.4.0", path = "../../../lib/entry/libimagentryref" }
libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }

View file

@ -25,11 +25,9 @@ extern crate libimagrt;
extern crate libimagmail;
extern crate libimagerror;
extern crate libimagutil;
extern crate libimagentryref;
use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
use libimagmail::mail::Mail;
use libimagentryref::reference::Ref;
use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagutil::info_result::*;
@ -74,11 +72,11 @@ fn list(rt: &Runtime) {
let iter = match store.retrieve_for_module("ref") {
Ok(iter) => iter.filter_map(|id| {
Ref::get(store, id)
.chain_err(|| MEK::RefHandlingError)
.and_then(|rf| Mail::from_ref(rf))
.map_err_trace()
.ok()
match store.get(id).chain_err(|| MEK::RefHandlingError).map_err_trace() {
Ok(Some(fle)) => Mail::from_fle(fle).map_err_trace().ok(),
Ok(None) => None,
Err(e) => trace_error_exit(&e, 1),
}
}),
Err(e) => trace_error_exit(&e, 1),
};

View file

@ -23,5 +23,4 @@ libimagrt = { version = "0.4.0", path = "../../../lib/core/libimagrt" }
libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
libimagnotes = { version = "0.4.0", path = "../../../lib/domain/libimagnotes" }
libimagentryedit = { version = "0.4.0", path = "../../../lib/entry/libimagentryedit" }
libimagentrytag = { version = "0.4.0", path = "../../../lib/entry/libimagentrytag" }
libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }

View file

@ -25,7 +25,6 @@ extern crate itertools;
extern crate libimagnotes;
extern crate libimagrt;
extern crate libimagentryedit;
extern crate libimagentrytag;
extern crate libimagerror;
extern crate libimagutil;

View file

@ -19,8 +19,6 @@
use clap::{Arg, App, SubCommand};
use libimagentrytag::ui::tag_argument;
pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
app
.subcommand(SubCommand::with_name("create")
@ -62,8 +60,6 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.required(true)
.help("Edit Note with this name")
.value_name("NAME"))
.arg(tag_argument())
)
.subcommand(SubCommand::with_name("list")

View file

@ -35,7 +35,7 @@ use toml::Value;
use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagtodo::task::Task;
use libimagtodo::taskstore::TaskStore;
use libimagerror::trace::{MapErrTrace, trace_error, trace_error_exit};
mod ui;
@ -61,9 +61,11 @@ fn tw_hook(rt: &Runtime) {
let subcmd = rt.cli().subcommand_matches("tw-hook").unwrap();
if subcmd.is_present("add") {
let stdin = stdin();
let stdin = stdin.lock(); // implements BufRead which is required for `Task::import()`
match Task::import(rt.store(), stdin) {
// implements BufRead which is required for `Store::import_task_from_reader()`
let stdin = stdin.lock();
match rt.store().import_task_from_reader(stdin) {
Ok((_, line, uuid)) => println!("{}\nTask {} stored in imag", line, uuid),
Err(e) => trace_error_exit(&e, 1),
}
@ -71,7 +73,7 @@ fn tw_hook(rt: &Runtime) {
// The used hook is "on-modify". This hook gives two json-objects
// per usage und wants one (the second one) back.
let stdin = stdin();
Task::delete_by_imports(rt.store(), stdin.lock()).map_err_trace().ok();
rt.store().delete_tasks_by_imports(stdin.lock()).map_err_trace().ok();
} else {
// Should not be possible, as one argument is required via
// ArgGroup
@ -92,30 +94,35 @@ fn list(rt: &Runtime) {
is_match!(e.kind(), &::toml_query::error::ErrorKind::IdentifierNotFoundInDocument(_))
};
let res = Task::all(rt.store()) // get all tasks
let res = rt.store().all_tasks() // get all tasks
.map(|iter| { // and if this succeeded
// filter out the ones were we can read the uuid
let uuids : Vec<_> = iter.filter_map(|t| match t {
Ok(v) => match v.get_header().read(&String::from("todo.uuid")) {
Ok(Some(&Value::String(ref u))) => Some(u.clone()),
Ok(Some(_)) => {
warn!("Header type error");
None
},
Ok(None) => {
warn!("Header missing field");
None
let uuids : Vec<_> = iter.filter_map(|storeid| {
match rt.store().retrieve(storeid) {
Ok(fle) => {
match fle.get_header().read(&String::from("todo.uuid")) {
Ok(Some(&Value::String(ref u))) => Some(u.clone()),
Ok(Some(_)) => {
error!("Header type error, expected String at 'todo.uuid' in {}",
fle.get_location());
None
},
Ok(None) => {
error!("Header missing field in {}", fle.get_location());
None
},
Err(e) => {
if !no_identifier(&e) {
trace_error(&e);
}
None
}
}
},
Err(e) => {
if !no_identifier(&e) {
trace_error(&e);
}
trace_error(&e);
None
}
},
Err(e) => {
trace_error(&e);
None
},
}
})
.collect();

View file

@ -25,6 +25,9 @@ This section contains the changelog from the last release to the next release.
* The codebase was moved to a more tree-ish approach, where several
subdirectories were introduced for different types of crates
* The documentation got a major overhaul and was partly rewritten
* The logger is now configurable via the config file.
* New
* `libimagentrygps` was introduced
* Fixed bugs
* The config loading in `libimagrt`
[was fixed](http://git.imag-pim.org/imag/commit/?id=9193d50f96bce099665d2eb716bcaa29a8d9b8ff).

View file

@ -1,6 +1,35 @@
# This is a example configuration file for the imag suite.
# It is written in TOML
[imag.logging]
level = "debug"
destinations = [ "-" ]
# Valid variables for logging:
# * "level"
# * "module_path"
# * "file"
# * "line"
# * "target"
# * "message"
#
# Valid functions to be applied:
# * "black"
# * "blue"
# * "cyan"
# * "green"
# * "purple"
# * "red"
# * "white"
# * "yellow"
[imag.logging.format]
trace = "[imag][{{red level}}][{{module_path}}]: {{message}}"
debug = "[imag][{{cyan level}}]: {{message}}"
info = "[imag]: {{message}}"
warn = "[imag][{{bold level}}]: {{yellow message}}"
error = "[imag][{{red level}}]: {{red message}}"
#
# Configuration options for the user interface
#

View file

@ -24,7 +24,16 @@ ansi_term = "0.9"
is-match = "0.1"
toml-query = "0.3.0"
error-chain = "0.10"
handlebars = "0.29.0"
libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
libimagutil = { version = "0.4.0", path = "../../../lib/etc/libimagutil" }
[features]
default = []
# Enable testing functionality. Used for building the libimagrt for testing CLI
# apps. Do not use in production!
testing = []

View file

@ -38,6 +38,71 @@ error_chain! {
display("Process exited with failure")
}
IOLogFileOpenError {
description("IO Error: Could not open logfile")
display("IO Error: Could not open logfile")
}
ConfigReadError {
description("Error while reading the configuration")
display("Error while reading the configuration")
}
ConfigTypeError {
description("Error while reading the configuration: Type Error")
display("Error while reading the configuration: Type Error")
}
GlobalLogLevelConfigMissing {
description("Global config 'imag.logging.level' missing")
display("Global config 'imag.logging.level' missing")
}
GlobalDestinationConfigMissing {
description("Global config 'imag.logging.destinations' missing")
display("Global config 'imag.logging.destinations' missing")
}
InvalidLogLevelSpec {
description("Invalid log level specification: Only 'trace', 'debug', 'info', 'warn', 'error' are allowed")
display("Invalid log level specification: Only 'trace', 'debug', 'info', 'warn', 'error' are allowed")
}
TomlReadError {
description("Error while reading in TOML document")
display("Error while reading in TOML document")
}
TemplateStringRegistrationError {
description("Error while registering logging template string")
display("Error while registering logging template string")
}
ConfigMissingLoggingFormatTrace {
description("Missing config for logging format for trace logging")
display("Missing config for logging format for trace logging")
}
ConfigMissingLoggingFormatDebug {
description("Missing config for logging format for debug logging")
display("Missing config for logging format for debug logging")
}
ConfigMissingLoggingFormatInfo {
description("Missing config for logging format for info logging")
display("Missing config for logging format for info logging")
}
ConfigMissingLoggingFormatWarn {
description("Missing config for logging format for warn logging")
display("Missing config for logging format for warn logging")
}
ConfigMissingLoggingFormatError {
description("Missing config for logging format for error logging")
display("Missing config for logging format for error logging")
}
}
}

View file

@ -41,6 +41,7 @@ extern crate itertools;
#[cfg(unix)] extern crate xdg_basedir;
extern crate env_logger;
extern crate ansi_term;
extern crate handlebars;
extern crate clap;
extern crate toml;

View file

@ -19,67 +19,114 @@
use std::io::Write;
use std::io::stderr;
use std::collections::BTreeMap;
use configuration::Configuration;
use error::RuntimeErrorKind as EK;
use error::RuntimeError as RE;
use error::ResultExt;
use runtime::Runtime;
use clap::ArgMatches;
use log::{Log, LogLevel, LogRecord, LogMetadata};
use toml::Value;
use toml_query::read::TomlValueReadExt;
use handlebars::Handlebars;
use ansi_term::Style;
use ansi_term::Colour;
use ansi_term::ANSIString;
type ModuleName = String;
type Result<T> = ::std::result::Result<T, RE>;
enum LogDestination {
Stderr,
File(::std::fs::File),
}
impl Default for LogDestination {
fn default() -> LogDestination {
LogDestination::Stderr
}
}
struct ModuleSettings {
enabled: bool,
level: Option<LogLevel>,
#[allow(unused)]
destinations: Option<Vec<LogDestination>>,
}
/// Logger implementation for `log` crate.
pub struct ImagLogger {
prefix: String,
dbg_fileline: bool,
lvl: LogLevel,
color_enabled: bool,
global_loglevel : LogLevel,
#[allow(unused)]
global_destinations : Vec<LogDestination>,
// global_format_trace : ,
// global_format_debug : ,
// global_format_info : ,
// global_format_warn : ,
// global_format_error : ,
module_settings : BTreeMap<ModuleName, ModuleSettings>,
handlebars: Handlebars,
}
impl ImagLogger {
/// Create a new ImagLogger object with a certain level
pub fn new(lvl: LogLevel) -> ImagLogger {
ImagLogger {
prefix: "[imag]".to_owned(),
dbg_fileline: true,
lvl: lvl,
color_enabled: true
pub fn new(matches: &ArgMatches, config: Option<&Configuration>) -> Result<ImagLogger> {
let mut handlebars = Handlebars::new();
handlebars.register_helper("black" , Box::new(self::template_helpers::ColorizeBlackHelper));
handlebars.register_helper("blue" , Box::new(self::template_helpers::ColorizeBlueHelper));
handlebars.register_helper("cyan" , Box::new(self::template_helpers::ColorizeCyanHelper));
handlebars.register_helper("green" , Box::new(self::template_helpers::ColorizeGreenHelper));
handlebars.register_helper("purple" , Box::new(self::template_helpers::ColorizePurpleHelper));
handlebars.register_helper("red" , Box::new(self::template_helpers::ColorizeRedHelper));
handlebars.register_helper("white" , Box::new(self::template_helpers::ColorizeWhiteHelper));
handlebars.register_helper("yellow" , Box::new(self::template_helpers::ColorizeYellowHelper));
handlebars.register_helper("underline" , Box::new(self::template_helpers::UnderlineHelper));
handlebars.register_helper("bold" , Box::new(self::template_helpers::BoldHelper));
handlebars.register_helper("blink" , Box::new(self::template_helpers::BlinkHelper));
handlebars.register_helper("strikethrough" , Box::new(self::template_helpers::StrikethroughHelper));
{
let fmt = try!(aggregate_global_format_trace(matches, config));
try!(handlebars.register_template_string("TRACE", fmt) // name must be uppercase
.chain_err(|| EK::TemplateStringRegistrationError));
}
}
/// Set debugging to include file and line
pub fn with_dbg_file_and_line(mut self, b: bool) -> ImagLogger {
self.dbg_fileline = b;
self
}
/// Set debugging to include prefix
pub fn with_prefix(mut self, pref: String) -> ImagLogger {
self.prefix = pref;
self
}
/// Set debugging to have color
pub fn with_color(mut self, b: bool) -> ImagLogger {
self.color_enabled = b;
self
}
/// Helper function to colorize a string with a certain Style
fn style_or_not(&self, c: Style, s: String) -> ANSIString {
if self.color_enabled {
c.paint(s)
} else {
ANSIString::from(s)
{
let fmt = try!(aggregate_global_format_debug(matches, config));
try!(handlebars.register_template_string("DEBUG", fmt) // name must be uppercase
.chain_err(|| EK::TemplateStringRegistrationError));
}
{
let fmt = try!(aggregate_global_format_info(matches, config));
try!(handlebars.register_template_string("INFO", fmt) // name must be uppercase
.chain_err(|| EK::TemplateStringRegistrationError));
}
{
let fmt = try!(aggregate_global_format_warn(matches, config));
try!(handlebars.register_template_string("WARN", fmt) // name must be uppercase
.chain_err(|| EK::TemplateStringRegistrationError));
}
{
let fmt = try!(aggregate_global_format_error(matches, config));
try!(handlebars.register_template_string("ERROR", fmt) // name must be uppercase
.chain_err(|| EK::TemplateStringRegistrationError));
}
Ok(ImagLogger {
global_loglevel : try!(aggregate_global_loglevel(matches, config)),
global_destinations : try!(aggregate_global_destinations(matches, config)),
module_settings : try!(aggregate_module_settings(matches, config)),
handlebars : handlebars,
})
}
/// Helper function to colorize a string with a certain Color
fn color_or_not(&self, c: Colour, s: String) -> ANSIString {
if self.color_enabled {
c.paint(s)
} else {
ANSIString::from(s)
}
pub fn global_loglevel(&self) -> LogLevel {
self.global_loglevel
}
}
@ -87,47 +134,432 @@ impl ImagLogger {
impl Log for ImagLogger {
fn enabled(&self, metadata: &LogMetadata) -> bool {
metadata.level() <= self.lvl
metadata.level() <= self.global_loglevel
}
fn log(&self, record: &LogRecord) {
use ansi_term::Colour::Red;
use ansi_term::Colour::Yellow;
use ansi_term::Colour::Cyan;
if record.location().module_path().starts_with("handlebars") {
// This is a ugly, yet necessary hack. When logging, we use handlebars for templating.
// But as the handlebars library itselfs logs via a normal logging macro ("debug!()"),
// we have a recursion in our chain.
//
// To prevent this recursion, we return here.
//
// (As of handlebars 0.29.0 - please check whether you can update handlebars if you see
// this. Hopefully the next version has a compiletime flag to disable logging)
return;
}
if self.enabled(record.metadata()) {
// TODO: This is just simple logging. Maybe we can enhance this lateron
let loc = record.location();
match record.metadata().level() {
LogLevel::Debug => {
let lvl = self.color_or_not(Cyan, format!("{}", record.level()));
let args = self.color_or_not(Cyan, format!("{}", record.args()));
if self.dbg_fileline {
let file = self.color_or_not(Cyan, format!("{}", loc.file()));
let ln = self.color_or_not(Cyan, format!("{}", loc.line()));
let mut data = BTreeMap::new();
writeln!(stderr(), "{}[{: <5}][{}][{: >5}]: {}", self.prefix, lvl, file, ln, args).ok();
} else {
writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, lvl, args).ok();
}
},
LogLevel::Warn | LogLevel::Error => {
let lvl = self.style_or_not(Red.blink(), format!("{}", record.level()));
let args = self.color_or_not(Red, format!("{}", record.args()));
{
data.insert("level", format!("{}", record.level()));
data.insert("module_path", String::from(record.location().module_path()));
data.insert("file", String::from(record.location().file()));
data.insert("line", format!("{}", record.location().line()));
data.insert("target", String::from(record.target()));
data.insert("message", format!("{}", record.args()));
}
writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, lvl, args).ok();
},
LogLevel::Info => {
let lvl = self.color_or_not(Yellow, format!("{}", record.level()));
let args = self.color_or_not(Yellow, format!("{}", record.args()));
let logtext = self
.handlebars
.render(&format!("{}", record.level()), &data)
.unwrap_or_else(|e| format!("Failed rendering logging data: {:?}\n", e));
writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, lvl, args).ok();
},
_ => {
writeln!(stderr(), "{}[{: <5}]: {}", self.prefix, record.level(), record.args()).ok();
},
self.module_settings
.get(record.target())
.map(|module_setting| {
let set = module_setting.enabled &&
module_setting.level.unwrap_or(self.global_loglevel) >= record.level();
if set {
let _ = write!(stderr(), "{}\n", logtext);
}
})
.unwrap_or_else(|| {
if self.global_loglevel >= record.level() {
// Yes, we log
let _ = write!(stderr(), "{}\n", logtext);
}
});
}
}
fn match_log_level_str(s: &str) -> Result<LogLevel> {
match s {
"trace" => Ok(LogLevel::Trace),
"debug" => Ok(LogLevel::Debug),
"info" => Ok(LogLevel::Info),
"warn" => Ok(LogLevel::Warn),
"error" => Ok(LogLevel::Error),
_ => return Err(RE::from_kind(EK::InvalidLogLevelSpec)),
}
}
fn aggregate_global_loglevel(matches: &ArgMatches, config: Option<&Configuration>)
-> Result<LogLevel>
{
match config {
Some(cfg) => match cfg
.read("imag.logging.level")
.chain_err(|| EK::ConfigReadError)
{
Ok(Some(&Value::String(ref s))) => match_log_level_str(s),
Ok(Some(_)) => Err(RE::from_kind(EK::ConfigTypeError)),
Ok(None) => Err(RE::from_kind(EK::GlobalLogLevelConfigMissing)),
Err(e) => Err(e)
},
None => {
if matches.is_present(Runtime::arg_debugging_name()) {
return Ok(LogLevel::Debug)
}
matches
.value_of(Runtime::arg_verbosity_name())
.map(match_log_level_str)
.unwrap_or(Ok(LogLevel::Info))
}
}
}
fn translate_destination(raw: &str) -> Result<LogDestination> {
use std::fs::OpenOptions;
match raw {
"-" => Ok(LogDestination::Stderr),
other => {
OpenOptions::new()
.append(true)
.create(true)
.open(other)
.map(LogDestination::File)
.chain_err(|| EK::IOLogFileOpenError)
}
}
}
fn translate_destinations(raw: &Vec<Value>) -> Result<Vec<LogDestination>> {
raw.iter()
.fold(Ok(vec![]), |acc, val| {
acc.and_then(|mut v| {
let dest = match *val {
Value::String(ref s) => try!(translate_destination(s)),
_ => return Err(RE::from_kind(EK::ConfigTypeError)),
};
v.push(dest);
Ok(v)
})
})
}
fn aggregate_global_destinations(matches: &ArgMatches, config: Option<&Configuration>)
-> Result<Vec<LogDestination>>
{
match config {
Some(cfg) => match cfg
.read("imag.logging.destinations")
.chain_err(|| EK::ConfigReadError)
{
Ok(Some(&Value::Array(ref a))) => translate_destinations(a),
Ok(Some(_)) => Err(RE::from_kind(EK::ConfigTypeError)),
Ok(None) => Err(RE::from_kind(EK::GlobalDestinationConfigMissing)),
Err(e) => Err(e)
},
None => {
if let Some(values) = matches.value_of(Runtime::arg_logdest_name()) {
// parse logdest specification from commandline
values.split(",")
.fold(Ok(vec![]), move |acc, dest| {
acc.and_then(|mut v| {
v.push(try!(translate_destination(dest)));
Ok(v)
})
})
} else {
Ok(vec![ LogDestination::default() ])
}
}
}
}
#[inline]
fn aggregate_global_format(
read_str: &str,
cli_match_name: &str,
error_kind_if_missing: EK,
matches: &ArgMatches,
config: Option<&Configuration>
)
-> Result<String>
{
match config {
Some(cfg) => match cfg
.read(read_str)
.chain_err(|| EK::ConfigReadError)
{
Ok(Some(&Value::String(ref s))) => Ok(s.clone()),
Ok(Some(_)) => Err(RE::from_kind(EK::ConfigTypeError)),
Ok(None) => Err(RE::from_kind(error_kind_if_missing)),
Err(e) => Err(e)
},
None => match matches.value_of(cli_match_name).map(String::from) {
Some(s) => Ok(s),
None => Err(RE::from_kind(error_kind_if_missing))
}
}
}
fn aggregate_global_format_trace(matches: &ArgMatches, config: Option<&Configuration>)
-> Result<String>
{
aggregate_global_format("imag.logging.format.trace",
Runtime::arg_override_trace_logging_format(),
EK::ConfigMissingLoggingFormatTrace,
matches,
config)
}
fn aggregate_global_format_debug(matches: &ArgMatches, config: Option<&Configuration>)
-> Result<String>
{
aggregate_global_format("imag.logging.format.debug",
Runtime::arg_override_debug_logging_format(),
EK::ConfigMissingLoggingFormatDebug,
matches,
config)
}
fn aggregate_global_format_info(matches: &ArgMatches, config: Option<&Configuration>)
-> Result<String>
{
aggregate_global_format("imag.logging.format.info",
Runtime::arg_override_info_logging_format(),
EK::ConfigMissingLoggingFormatInfo,
matches,
config)
}
fn aggregate_global_format_warn(matches: &ArgMatches, config: Option<&Configuration>)
-> Result<String>
{
aggregate_global_format("imag.logging.format.warn",
Runtime::arg_override_warn_logging_format(),
EK::ConfigMissingLoggingFormatWarn,
matches,
config)
}
fn aggregate_global_format_error(matches: &ArgMatches, config: Option<&Configuration>)
-> Result<String>
{
aggregate_global_format("imag.logging.format.error",
Runtime::arg_override_error_logging_format(),
EK::ConfigMissingLoggingFormatError,
matches,
config)
}
fn aggregate_module_settings(_matches: &ArgMatches, config: Option<&Configuration>)
-> Result<BTreeMap<ModuleName, ModuleSettings>>
{
match config {
Some(cfg) => match cfg
.read("imag.logging.modules")
.chain_err(|| EK::ConfigReadError)
{
Ok(Some(&Value::Table(ref t))) => {
// translate the module settings from the table `t`
let mut settings = BTreeMap::new();
for (module_name, v) in t {
let destinations = try!(match v.read("destinations") {
Ok(Some(&Value::Array(ref a))) => translate_destinations(a).map(Some),
Ok(None) => Ok(None),
Ok(Some(_)) => Err(RE::from_kind(EK::ConfigTypeError)),
Err(e) => Err(e).chain_err(|| EK::TomlReadError),
});
let level = try!(match v.read("level") {
Ok(Some(&Value::String(ref s))) => match_log_level_str(s).map(Some),
Ok(None) => Ok(None),
Ok(Some(_)) => Err(RE::from_kind(EK::ConfigTypeError)),
Err(e) => Err(e).chain_err(|| EK::TomlReadError),
});
let enabled = try!(match v.read("enabled") {
Ok(Some(&Value::Boolean(b))) => Ok(b),
Ok(None) => Ok(false),
Ok(Some(_)) => Err(RE::from_kind(EK::ConfigTypeError)),
Err(e) => Err(e).chain_err(|| EK::TomlReadError),
});
let module_settings = ModuleSettings {
enabled: enabled,
level: level,
destinations: destinations,
};
// We don't care whether there was a value, we override it.
let _ = settings.insert(module_name.to_owned(), module_settings);
}
Ok(settings)
},
Ok(Some(_)) => Err(RE::from_kind(EK::ConfigTypeError)),
Ok(None) => {
// No modules configured. This is okay!
Ok(BTreeMap::new())
},
Err(e) => Err(e),
},
None => {
write!(stderr(), "No Configuration.").ok();
write!(stderr(), "cannot find module-settings for logging.").ok();
write!(stderr(), "Will use global defaults").ok();
Ok(BTreeMap::new())
}
}
}
mod template_helpers {
use handlebars::{Handlebars, HelperDef, JsonRender, RenderError, RenderContext, Helper};
use ansi_term::Colour;
use ansi_term::Style;
#[derive(Clone, Copy)]
pub struct ColorizeBlackHelper;
impl HelperDef for ColorizeBlackHelper {
fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
colorize(Colour::Black, h, hb, rc)
}
}
#[derive(Clone, Copy)]
pub struct ColorizeBlueHelper;
impl HelperDef for ColorizeBlueHelper {
fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
colorize(Colour::Blue, h, hb, rc)
}
}
#[derive(Clone, Copy)]
pub struct ColorizeCyanHelper;
impl HelperDef for ColorizeCyanHelper {
fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
colorize(Colour::Cyan, h, hb, rc)
}
}
#[derive(Clone, Copy)]
pub struct ColorizeGreenHelper;
impl HelperDef for ColorizeGreenHelper {
fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
colorize(Colour::Green, h, hb, rc)
}
}
#[derive(Clone, Copy)]
pub struct ColorizePurpleHelper;
impl HelperDef for ColorizePurpleHelper {
fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
colorize(Colour::Purple, h, hb, rc)
}
}
#[derive(Clone, Copy)]
pub struct ColorizeRedHelper;
impl HelperDef for ColorizeRedHelper {
fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
colorize(Colour::Red, h, hb, rc)
}
}
#[derive(Clone, Copy)]
pub struct ColorizeWhiteHelper;
impl HelperDef for ColorizeWhiteHelper {
fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
colorize(Colour::White, h, hb, rc)
}
}
#[derive(Clone, Copy)]
pub struct ColorizeYellowHelper;
impl HelperDef for ColorizeYellowHelper {
fn call(&self, h: &Helper, hb: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
colorize(Colour::Yellow, h, hb, rc)
}
}
fn colorize(color: Colour, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments")));
try!(write!(rc.writer(), "{}", color.paint(p.value().render())));
Ok(())
}
#[derive(Clone, Copy)]
pub struct UnderlineHelper;
impl HelperDef for UnderlineHelper {
fn call(&self, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(),
RenderError> {
let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments")));
let s = Style::new().underline();
try!(write!(rc.writer(), "{}", s.paint(p.value().render())));
Ok(())
}
}
#[derive(Clone, Copy)]
pub struct BoldHelper;
impl HelperDef for BoldHelper {
fn call(&self, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(),
RenderError> {
let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments")));
let s = Style::new().bold();
try!(write!(rc.writer(), "{}", s.paint(p.value().render())));
Ok(())
}
}
#[derive(Clone, Copy)]
pub struct BlinkHelper;
impl HelperDef for BlinkHelper {
fn call(&self, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(),
RenderError> {
let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments")));
let s = Style::new().blink();
try!(write!(rc.writer(), "{}", s.paint(p.value().render())));
Ok(())
}
}
#[derive(Clone, Copy)]
pub struct StrikethroughHelper;
impl HelperDef for StrikethroughHelper {
fn call(&self, h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(),
RenderError> {
let p = try!(h.param(0).ok_or(RenderError::new("Too few arguments")));
let s = Style::new().strikethrough();
try!(write!(rc.writer(), "{}", s.paint(p.value().render())));
Ok(())
}
}
}

View file

@ -20,14 +20,12 @@
use std::path::PathBuf;
use std::process::Command;
use std::env;
use std::io::stderr;
use std::io::Write;
use std::process::exit;
pub use clap::App;
use clap::{Arg, ArgMatches};
use log;
use log::LogLevelFilter;
use configuration::{Configuration, InternalConfiguration};
use error::RuntimeError;
@ -35,6 +33,7 @@ use error::RuntimeErrorKind;
use error::ResultExt;
use logger::ImagLogger;
use libimagerror::trace::*;
use libimagstore::store::Store;
use libimagstore::file_abstraction::InMemoryFileAbstraction;
use spec::CliSpec;
@ -77,8 +76,8 @@ impl<'a> Runtime<'a> {
Err(e) => if !is_match!(e.kind(), &ConfigErrorKind::NoConfigFileFound) {
return Err(e).chain_err(|| RuntimeErrorKind::Instantiate);
} else {
warn!("No config file found.");
warn!("Continuing without configuration file");
println!("No config file found.");
println!("Continuing without configuration file");
None
},
@ -111,16 +110,10 @@ impl<'a> Runtime<'a> {
where C: Clone + CliSpec<'a> + InternalConfiguration
{
use std::io::stdout;
use clap::Shell;
let is_debugging = matches.is_present(Runtime::arg_debugging_name());
if cli_app.enable_logging() {
let is_verbose = matches.is_present(Runtime::arg_verbosity_name());
let colored = !matches.is_present(Runtime::arg_no_color_output_name());
Runtime::init_logger(is_debugging, is_verbose, colored);
Runtime::init_logger(&matches, config.as_ref())
}
match matches.value_of(Runtime::arg_generate_compl()) {
@ -150,9 +143,9 @@ impl<'a> Runtime<'a> {
None => None,
};
if is_debugging {
write!(stderr(), "Config: {:?}\n", config).ok();
write!(stderr(), "Store-config: {:?}\n", store_config).ok();
if matches.is_present(Runtime::arg_debugging_name()) {
debug!("Config: {:?}\n", config);
debug!("Store-config: {:?}\n", store_config);
}
let store_result = if cli_app.use_inmemory_fs() {
@ -199,9 +192,11 @@ impl<'a> Runtime<'a> {
.arg(Arg::with_name(Runtime::arg_verbosity_name())
.short("v")
.long("verbose")
.help("Enables verbosity")
.help("Enables verbosity, can be used to set log level to one of 'trace', 'debug', 'info', 'warn' or 'error'")
.required(false)
.takes_value(false))
.takes_value(true)
.possible_values(&["trace", "debug", "info", "warn", "error"])
.value_name("LOGLEVEL"))
.arg(Arg::with_name(Runtime::arg_debugging_name())
.long("debug")
@ -253,6 +248,61 @@ impl<'a> Runtime<'a> {
.value_name("SHELL")
.possible_values(&["bash", "fish", "zsh"]))
.arg(Arg::with_name(Runtime::arg_logdest_name())
.long(Runtime::arg_logdest_name())
.help("Override the logging destinations from the configuration: values can be seperated by ',', a value of '-' marks the stderr output, everything else is expected to be a path")
.required(false)
.takes_value(true)
.value_name("LOGDESTS"))
.arg(Arg::with_name(Runtime::arg_override_module_logging_setting_name())
.long(Runtime::arg_override_module_logging_setting_name())
.help("Override a module logging setting for one module. Format: <modulename>=<setting>=<value>, whereas <setting> is either 'enabled', 'level' or 'destinations' - This commandline argument is CURRENTLY NOT IMPLEMENTED")
.multiple(true)
.required(false)
.takes_value(true)
.value_name("SPEC"))
.arg(Arg::with_name(Runtime::arg_override_trace_logging_format())
.long(Runtime::arg_override_trace_logging_format())
.help("Override the logging format for the trace logging")
.multiple(false)
.required(false)
.takes_value(true)
.value_name("FMT"))
.arg(Arg::with_name(Runtime::arg_override_debug_logging_format())
.long(Runtime::arg_override_debug_logging_format())
.help("Override the logging format for the debug logging")
.multiple(false)
.required(false)
.takes_value(true)
.value_name("FMT"))
.arg(Arg::with_name(Runtime::arg_override_info_logging_format())
.long(Runtime::arg_override_info_logging_format())
.help("Override the logging format for the info logging")
.multiple(false)
.required(false)
.takes_value(true)
.value_name("FMT"))
.arg(Arg::with_name(Runtime::arg_override_warn_logging_format())
.long(Runtime::arg_override_warn_logging_format())
.help("Override the logging format for the warn logging")
.multiple(false)
.required(false)
.takes_value(true)
.value_name("FMT"))
.arg(Arg::with_name(Runtime::arg_override_error_logging_format())
.long(Runtime::arg_override_error_logging_format())
.help("Override the logging format for the error logging")
.multiple(false)
.required(false)
.takes_value(true)
.value_name("FMT"))
}
/// Get the argument names of the Runtime which are available
@ -314,26 +364,73 @@ impl<'a> Runtime<'a> {
"generate-completion"
}
/// Extract the Store object from the Runtime object, destroying the Runtime object
///
/// # Warning
///
/// This function is for testing _only_! It can be used to re-build a Runtime object with an
/// alternative Store.
#[cfg(feature = "testing")]
pub fn extract_store(self) -> Store {
self.store
}
/// Re-set the Store object within
///
/// # Warning
///
/// This function is for testing _only_! It can be used to re-build a Runtime object with an
/// alternative Store.
#[cfg(feature = "testing")]
pub fn with_store(mut self, s: Store) -> Self {
self.store = s;
self
}
/// Get the argument name for the logging destination
pub fn arg_logdest_name() -> &'static str {
"logging-destinations"
}
pub fn arg_override_module_logging_setting_name() -> &'static str {
"override-module-log-setting"
}
pub fn arg_override_trace_logging_format() -> &'static str {
"override-logging-format-trace"
}
pub fn arg_override_debug_logging_format() -> &'static str {
"override-logging-format-debug"
}
pub fn arg_override_info_logging_format() -> &'static str {
"override-logging-format-info"
}
pub fn arg_override_warn_logging_format() -> &'static str {
"override-logging-format-warn"
}
pub fn arg_override_error_logging_format() -> &'static str {
"override-logging-format-error"
}
/// Initialize the internal logger
fn init_logger(is_debugging: bool, is_verbose: bool, colored: bool) {
fn init_logger(matches: &ArgMatches, config: Option<&Configuration>) {
use std::env::var as env_var;
use env_logger;
if env_var("IMAG_LOG_ENV").is_ok() {
env_logger::init().unwrap();
} else {
let lvl = if is_debugging {
LogLevelFilter::Debug
} else if is_verbose {
LogLevelFilter::Info
} else {
LogLevelFilter::Warn
};
log::set_logger(|max_log_lvl| {
max_log_lvl.set(lvl);
debug!("Init logger with {}", lvl);
Box::new(ImagLogger::new(lvl.to_log_level().unwrap()).with_color(colored))
let logger = ImagLogger::new(matches, config)
.map_err_trace()
.unwrap_or_else(|_| exit(1));
max_log_lvl.set(logger.global_loglevel().to_log_level_filter());
debug!("Init logger with {}", logger.global_loglevel());
Box::new(logger)
})
.map_err(|e| panic!("Could not setup logger: {:?}", e))
.ok();

View file

@ -19,110 +19,116 @@
use std::cmp::Ordering;
use libimagstore::store::FileLockEntry;
use libimagstore::store::Store;
use libimagstore::storeid::IntoStoreId;
use libimagerror::trace::trace_error;
use chrono::offset::Local;
use chrono::Datelike;
use itertools::Itertools;
use chrono::naive::NaiveDateTime;
use chrono::Timelike;
use entry::Entry;
use entry::DiaryEntry;
use diaryid::DiaryId;
use error::DiaryError as DE;
use error::DiaryErrorKind as DEK;
use error::ResultExt;
use error::Result;
use iter::DiaryEntryIterator;
use is_in_diary::IsInDiary;
use iter::DiaryNameIterator;
#[derive(Debug)]
pub struct Diary<'a> {
store: &'a Store,
name: &'a str,
}
impl<'a> Diary<'a> {
pub fn open(store: &'a Store, name: &'a str) -> Diary<'a> {
Diary {
store: store,
name: name,
}
}
pub trait Diary {
// create or get a new entry for today
pub fn new_entry_today(&self) -> Result<Entry> {
fn new_entry_today(&self, diary_name: &str) -> Result<FileLockEntry>;
// create or get a new entry for now
fn new_entry_now(&self, diary_name: &str) -> Result<FileLockEntry>;
// Get an iterator for iterating over all entries of a Diary
fn entries(&self, diary_name: &str) -> Result<DiaryEntryIterator>;
fn get_youngest_entry_id(&self, diary_name: &str) -> Option<Result<DiaryId>>;
/// Get all diary names
fn diary_names(&self) -> Result<DiaryNameIterator>;
}
impl Diary for Store {
// create or get a new entry for today
fn new_entry_today(&self, diary_name: &str) -> Result<FileLockEntry> {
let dt = Local::now();
let ndt = dt.naive_local();
let id = DiaryId::new(String::from(self.name), ndt.year(), ndt.month(), ndt.day(), 0, 0);
self.new_entry_by_id(id)
let id = DiaryId::new(String::from(diary_name), ndt.year(), ndt.month(), ndt.day(), 0, 0);
self.retrieve(id).chain_err(|| DEK::StoreReadError)
}
pub fn new_entry_by_id(&self, id: DiaryId) -> Result<Entry> {
self.retrieve(id.with_diary_name(String::from(self.name)))
}
fn new_entry_now(&self, diary_name: &str) -> Result<FileLockEntry> {
let dt = Local::now();
let ndt = dt.naive_local();
let id = DiaryId::new(String::from(diary_name),
ndt.year(),
ndt.month(),
ndt.day(),
ndt.hour(),
ndt.minute());
pub fn retrieve(&self, id: DiaryId) -> Result<Entry> {
id.into_storeid()
.and_then(|id| self.store.retrieve(id))
.map(|fle| Entry::new(fle))
.chain_err(|| DEK::StoreWriteError)
self.retrieve(id).chain_err(|| DEK::StoreReadError)
}
// Get an iterator for iterating over all entries
pub fn entries(&self) -> Result<DiaryEntryIterator<'a>> {
self.store
.retrieve_for_module("diary")
.map(|iter| DiaryEntryIterator::new(self.name, self.store, iter))
fn entries(&self, diary_name: &str) -> Result<DiaryEntryIterator> {
self.retrieve_for_module("diary")
.map(|iter| DiaryEntryIterator::new(self, String::from(diary_name), iter))
.chain_err(|| DEK::StoreReadError)
}
pub fn delete_entry(&self, entry: Entry) -> Result<()> {
if !entry.is_in_diary(self.name) {
return Err(DE::from_kind(DEK::EntryNotInDiary));
}
let id = entry.get_location().clone();
drop(entry);
self.store.delete(id).chain_err(|| DEK::StoreWriteError)
}
pub fn get_youngest_entry(&self) -> Option<Result<Entry>> {
match self.entries() {
fn get_youngest_entry_id(&self, diary_name: &str) -> Option<Result<DiaryId>> {
match Diary::entries(self, diary_name) {
Err(e) => Some(Err(e)),
Ok(entries) => {
entries.sorted_by(|a, b| {
match (a, b) {
(&Ok(ref a), &Ok(ref b)) => {
let a : NaiveDateTime = a.diary_id().into();
let b : NaiveDateTime = b.diary_id().into();
entries
.map(|e| e.and_then(|e| e.diary_id()))
.sorted_by(|a, b| {
match (a, b) {
(&Ok(ref a), &Ok(ref b)) => {
let a : NaiveDateTime = a.clone().into();
let b : NaiveDateTime = b.clone().into();
a.cmp(&b)
},
a.cmp(&b)
},
(&Ok(_), &Err(ref e)) => {
trace_error(e);
Ordering::Less
},
(&Err(ref e), &Ok(_)) => {
trace_error(e);
Ordering::Greater
},
(&Err(ref e1), &Err(ref e2)) => {
trace_error(e1);
trace_error(e2);
Ordering::Equal
},
}
}).into_iter().next()
(&Ok(_), &Err(ref e)) => {
trace_error(e);
Ordering::Less
},
(&Err(ref e), &Ok(_)) => {
trace_error(e);
Ordering::Greater
},
(&Err(ref e1), &Err(ref e2)) => {
trace_error(e1);
trace_error(e2);
Ordering::Equal
},
}
})
.into_iter()
//.map(|sidres| sidres.map(|sid| DiaryId::from_storeid(&sid)))
.next()
}
}
}
pub fn name(&self) -> &'a str {
&self.name
/// Get all diary names
fn diary_names(&self) -> Result<DiaryNameIterator> {
self.retrieve_for_module("diary")
.chain_err(|| DEK::StoreReadError)
.map(DiaryNameIterator::new)
}
}

View file

@ -17,74 +17,24 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::ops::Deref;
use std::ops::DerefMut;
use libimagstore::store::FileLockEntry;
use libimagentryedit::edit::Edit;
use libimagentryedit::error::Result as EditResult;
use libimagrt::runtime::Runtime;
use libimagstore::store::Entry;
use diaryid::DiaryId;
use diaryid::FromStoreId;
use error::Result;
#[derive(Debug)]
pub struct Entry<'a>(FileLockEntry<'a>);
impl<'a> Deref for Entry<'a> {
type Target = FileLockEntry<'a>;
fn deref(&self) -> &FileLockEntry<'a> {
&self.0
}
pub trait DiaryEntry {
fn diary_id(&self) -> Result<DiaryId>;
}
impl<'a> DerefMut for Entry<'a> {
fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
&mut self.0
}
}
impl<'a> Entry<'a> {
pub fn new(fle: FileLockEntry<'a>) -> Entry<'a> {
Entry(fle)
}
impl DiaryEntry for Entry {
/// Get the diary id for this entry.
///
/// TODO: calls Option::unwrap() as it assumes that an existing Entry has an ID that is parsable
pub fn diary_id(&self) -> DiaryId {
DiaryId::from_storeid(&self.0.get_location().clone()).unwrap()
fn diary_id(&self) -> Result<DiaryId> {
DiaryId::from_storeid(&self.get_location().clone())
}
}
impl<'a> Into<FileLockEntry<'a>> for Entry<'a> {
fn into(self) -> FileLockEntry<'a> {
self.0
}
}
impl<'a> From<FileLockEntry<'a>> for Entry<'a> {
fn from(fle: FileLockEntry<'a>) -> Entry<'a> {
Entry::new(fle)
}
}
impl<'a> Edit for Entry<'a> {
fn edit_content(&mut self, rt: &Runtime) -> EditResult<()> {
self.0.edit_content(rt)
}
}

View file

@ -73,6 +73,11 @@ error_chain! {
display("Error while parsing ID")
}
DiaryNameFindingError {
description("Error while finding a diary name")
display("Error while finding a diary name")
}
}
}

View file

@ -24,6 +24,8 @@ pub trait IsInDiary {
fn is_in_diary(&self, name: &str) -> bool;
fn is_a_diary_entry(&self) -> bool;
}
impl IsInDiary for Entry {
@ -32,6 +34,10 @@ impl IsInDiary for Entry {
self.get_location().clone().is_in_diary(name)
}
fn is_a_diary_entry(&self) -> bool {
self.get_location().clone().is_a_diary_entry()
}
}
impl IsInDiary for StoreId {
@ -40,5 +46,9 @@ impl IsInDiary for StoreId {
self.local().starts_with(format!("diary/{}", name))
}
fn is_a_diary_entry(&self) -> bool {
self.local().starts_with("diary")
}
}

View file

@ -21,21 +21,22 @@ use std::fmt::{Debug, Formatter, Error as FmtError};
use std::result::Result as RResult;
use libimagstore::store::Store;
use libimagstore::store::FileLockEntry;
use libimagstore::storeid::StoreIdIterator;
use libimagerror::trace::trace_error;
use diaryid::DiaryId;
use diaryid::FromStoreId;
use is_in_diary::IsInDiary;
use entry::Entry as DiaryEntry;
use error::DiaryErrorKind as DEK;
use error::DiaryError as DE;
use error::ResultExt;
use error::Result;
use libimagerror::trace::trace_error;
/// A iterator for iterating over diary entries
pub struct DiaryEntryIterator<'a> {
store: &'a Store,
name: &'a str,
name: String,
iter: StoreIdIterator,
year: Option<i32>,
@ -54,7 +55,7 @@ impl<'a> Debug for DiaryEntryIterator<'a> {
impl<'a> DiaryEntryIterator<'a> {
pub fn new(diaryname: &'a str, store: &'a Store, iter: StoreIdIterator) -> DiaryEntryIterator<'a> {
pub fn new(store: &'a Store, diaryname: String, iter: StoreIdIterator) -> DiaryEntryIterator<'a> {
DiaryEntryIterator {
store: store,
name: diaryname,
@ -87,9 +88,9 @@ impl<'a> DiaryEntryIterator<'a> {
}
impl<'a> Iterator for DiaryEntryIterator<'a> {
type Item = Result<DiaryEntry<'a>>;
type Item = Result<FileLockEntry<'a>>;
fn next(&mut self) -> Option<Result<DiaryEntry<'a>>> {
fn next(&mut self) -> Option<Self::Item> {
loop {
let next = match self.iter.next() {
Some(s) => s,
@ -97,7 +98,7 @@ impl<'a> Iterator for DiaryEntryIterator<'a> {
};
debug!("Next element: {:?}", next);
if next.is_in_diary(self.name) {
if next.is_in_diary(&self.name) {
debug!("Seems to be in diary: {:?}", next);
let id = match DiaryId::from_storeid(&next) {
Ok(i) => i,
@ -118,9 +119,7 @@ impl<'a> Iterator for DiaryEntryIterator<'a> {
return Some(self
.store
.retrieve(next)
.map(|fle| DiaryEntry::new(fle))
.chain_err(|| DEK::StoreReadError)
);
.chain_err(|| DEK::StoreReadError));
}
} else {
debug!("Not in the requested diary ({}): {:?}", self.name, next);
@ -130,3 +129,37 @@ impl<'a> Iterator for DiaryEntryIterator<'a> {
}
/// Get diary names.
///
/// # Warning
///
/// Does _not_ run a `unique` on the iterator!
pub struct DiaryNameIterator(StoreIdIterator);
impl DiaryNameIterator {
pub fn new(s: StoreIdIterator) -> DiaryNameIterator {
DiaryNameIterator(s)
}
}
impl Iterator for DiaryNameIterator {
type Item = Result<String>;
fn next(&mut self) -> Option<Self::Item> {
self.0
.next()
.map(|s| {
s.to_str()
.chain_err(|| DEK::DiaryNameFindingError)
.and_then(|s| {
s.split("diary/")
.nth(1)
.and_then(|n| n.split("/").nth(0).map(String::from))
.ok_or(DE::from_kind(DEK::DiaryNameFindingError))
})
})
}
}

View file

@ -19,13 +19,15 @@
//! A diary viewer built on libimagentryview.
use entry::Entry;
use entry::DiaryEntry;
use error::DiaryErrorKind as DEK;
use error::ResultExt;
use error::Result;
use libimagstore::store::FileLockEntry;
use libimagentryview::viewer::Viewer;
use libimagentryview::builtin::plain::PlainViewer;
use libimagerror::trace::trace_error;
/// This viewer does _not_ implement libimagentryview::viewer::Viewer because we need to be able to
/// call some diary-type specific functions on the entries passed to this.
@ -46,10 +48,12 @@ impl DiaryViewer {
/// View all entries from the iterator, or stop immediately if an error occurs, returning that
/// error.
pub fn view_entries<'a, I: Iterator<Item = Entry<'a>>>(&self, entries: I) -> Result<()> {
pub fn view_entries<'a, I: Iterator<Item = FileLockEntry<'a>>>(&self, entries: I) -> Result<()> {
for entry in entries {
let id = entry.diary_id();
println!("{} :\n", id);
match entry.diary_id() {
Ok(id) => println!("{} :\n", id),
Err(e) => trace_error(&e),
}
let _ = try!(self.0
.view_entry(&entry)
.chain_err(|| DEK::ViewError)

View file

@ -27,16 +27,16 @@
use mail::Mail;
use error::Result;
use libimagentryref::reference::Ref;
use libimagstore::store::FileLockEntry;
use std::marker::PhantomData;
pub struct MailIter<'a, I: 'a + Iterator<Item = Ref<'a>>> {
_marker: PhantomData<&'a I>,
pub struct MailIter<'a, I: Iterator<Item = FileLockEntry<'a>>> {
_marker: PhantomData<I>,
i: I,
}
impl<'a, I: Iterator<Item = Ref<'a>>> MailIter<'a, I> {
impl<'a, I: Iterator<Item = FileLockEntry<'a>>> MailIter<'a, I> {
pub fn new(i: I) -> MailIter<'a, I> {
MailIter { _marker: PhantomData, i: i }
@ -44,12 +44,11 @@ impl<'a, I: Iterator<Item = Ref<'a>>> MailIter<'a, I> {
}
impl<'a, I: Iterator<Item = Ref<'a>>> Iterator for MailIter<'a, I> {
impl<'a, I: Iterator<Item = FileLockEntry<'a>>> Iterator for MailIter<'a, I> {
type Item = Result<Mail<'a>>;
fn next(&mut self) -> Option<Result<Mail<'a>>> {
self.i.next().map(Mail::from_ref)
fn next(&mut self) -> Option<Self::Item> {
self.i.next().map(Mail::from_fle)
}
}

View file

@ -23,8 +23,10 @@ use std::fs::File;
use std::io::Read;
use libimagstore::store::Store;
use libimagstore::store::FileLockEntry;
use libimagentryref::reference::Ref;
use libimagentryref::flags::RefFlags;
use libimagentryref::refstore::RefStore;
use email::MimeMessage;
use email::results::ParsingResult as EmailParsingResult;
@ -47,7 +49,7 @@ impl From<String> for Buffer {
}
}
pub struct Mail<'a>(Ref<'a>, Buffer);
pub struct Mail<'a>(FileLockEntry<'a>, Buffer);
impl<'a> Mail<'a> {
@ -58,7 +60,7 @@ impl<'a> Mail<'a> {
let f = RefFlags::default().with_content_hashing(true).with_permission_tracking(false);
let p = PathBuf::from(p.as_ref());
Ref::create_with_hasher(store, p, f, h)
store.create_with_hasher(p, f, h)
.chain_err(|| MEK::RefCreationError)
.and_then(|reference| {
debug!("Build reference file: {:?}", reference);
@ -79,20 +81,19 @@ impl<'a> Mail<'a> {
/// Opens a mail by the passed hash
pub fn open<S: AsRef<str>>(store: &Store, hash: S) -> Result<Option<Mail>> {
debug!("Opening Mail by Hash");
Ref::get_by_hash(store, String::from(hash.as_ref()))
store.get_by_hash(String::from(hash.as_ref()))
.chain_err(|| MEK::FetchByHashError)
.chain_err(|| MEK::FetchError)
.and_then(|o| match o {
Some(r) => Mail::from_ref(r).map(Some),
Some(r) => Mail::from_fle(r).map(Some),
None => Ok(None),
})
}
/// Implement me as TryFrom as soon as it is stable
pub fn from_ref(r: Ref<'a>) -> Result<Mail> {
debug!("Building Mail object from Ref: {:?}", r);
r.fs_file()
pub fn from_fle(fle: FileLockEntry<'a>) -> Result<Mail<'a>> {
fle.fs_file()
.chain_err(|| MEK::RefHandlingError)
.and_then(|path| File::open(path).chain_err(|| MEK::IOError))
.and_then(|mut file| {
@ -102,7 +103,7 @@ impl<'a> Mail<'a> {
.chain_err(|| MEK::IOError)
})
.map(Buffer::from)
.map(|buffer| Mail(r, buffer))
.map(|buffer| Mail(fle, buffer))
}
pub fn get_field(&self, field: &str) -> Result<Option<String>> {

View file

@ -48,6 +48,20 @@ error_chain! {
display("Encountered non-UTF8 characters while reading input")
}
HeaderFieldMissing {
description("Header field missing")
display("Header field missing")
}
HeaderTypeError {
description("Header field type error")
display("Header field type error")
}
UuidParserError {
description("Uuid parser error")
display("Uuid parser error")
}
}
}

View file

@ -50,4 +50,5 @@ module_entry_path_mod!("todo");
pub mod error;
pub mod task;
pub mod taskstore;

View file

@ -17,272 +17,31 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut};
use std::io::BufRead;
use std::result::Result as RResult;
use toml::Value;
use uuid::Uuid;
use task_hookrs::task::Task as TTask;
use task_hookrs::import::{import_task, import_tasks};
use libimagstore::store::{FileLockEntry, Store};
use libimagstore::storeid::{IntoStoreId, StoreIdIterator, StoreId};
use module_path::ModuleEntryPath;
use error::{TodoErrorKind as TEK, ResultExt};
use error::TodoError as TE;
use error::TodoErrorKind as TEK;
use error::ResultExt;
use error::Result;
/// Task struct containing a `FileLockEntry`
#[derive(Debug)]
pub struct Task<'a>(FileLockEntry<'a>);
use libimagstore::store::Entry;
impl<'a> Task<'a> {
/// Concstructs a new `Task` with a `FileLockEntry`
pub fn new(fle: FileLockEntry<'a>) -> Task<'a> {
Task(fle)
}
pub fn import<R: BufRead>(store: &'a Store, mut r: R) -> Result<(Task<'a>, String, Uuid)> {
let mut line = String::new();
try!(r.read_line(&mut line).chain_err(|| TEK::UTF8Error));
import_task(&line.as_str())
.map_err(|_| TE::from_kind(TEK::ImportError))
.and_then(|t| {
let uuid = t.uuid().clone();
t.into_task(store).map(|t| (t, line, uuid))
})
}
/// Get a task from an import string. That is: read the imported string, get the UUID from it
/// and try to load this UUID from store.
///
/// Possible return values are:
///
/// * Ok(Ok(Task))
/// * Ok(Err(String)) - where the String is the String read from the `r` parameter
/// * Err(_) - where the error is an error that happened during evaluation
///
pub fn get_from_import<R>(store: &'a Store, mut r: R) -> Result<RResult<Task<'a>, String>>
where R: BufRead
{
let mut line = String::new();
try!(r.read_line(&mut line).chain_err(|| TEK::UTF8Error));
Task::get_from_string(store, line)
}
/// Get a task from a String. The String is expected to contain the JSON-representation of the
/// Task to get from the store (only the UUID really matters in this case)
///
/// For an explanation on the return values see `Task::get_from_import()`.
pub fn get_from_string(store: &'a Store, s: String) -> Result<RResult<Task<'a>, String>> {
import_task(s.as_str())
.map_err(|_| TE::from_kind(TEK::ImportError))
.map(|t| t.uuid().clone())
.and_then(|uuid| Task::get_from_uuid(store, uuid))
.and_then(|o| match o {
None => Ok(Err(s)),
Some(t) => Ok(Ok(t)),
})
}
/// Get a task from an UUID.
///
/// If there is no task with this UUID, this returns `Ok(None)`.
pub fn get_from_uuid(store: &'a Store, uuid: Uuid) -> Result<Option<Task<'a>>> {
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
.into_storeid()
.and_then(|store_id| store.get(store_id))
.map(|o| o.map(Task::new))
.chain_err(|| TEK::StoreError)
}
/// Same as Task::get_from_import() but uses Store::retrieve() rather than Store::get(), to
/// implicitely create the task if it does not exist.
pub fn retrieve_from_import<R: BufRead>(store: &'a Store, mut r: R) -> Result<Task<'a>> {
let mut line = String::new();
try!(r.read_line(&mut line).chain_err(|| TEK::UTF8Error));
Task::retrieve_from_string(store, line)
}
/// Retrieve a task from a String. The String is expected to contain the JSON-representation of
/// the Task to retrieve from the store (only the UUID really matters in this case)
pub fn retrieve_from_string(store: &'a Store, s: String) -> Result<Task<'a>> {
Task::get_from_string(store, s)
.and_then(|opt| match opt {
Ok(task) => Ok(task),
Err(string) => import_task(string.as_str())
.map_err(|_| TE::from_kind(TEK::ImportError))
.and_then(|t| t.into_task(store)),
})
}
pub fn delete_by_imports<R: BufRead>(store: &Store, r: R) -> Result<()> {
use serde_json::ser::to_string as serde_to_string;
use task_hookrs::status::TaskStatus;
for (counter, res_ttask) in import_tasks(r).into_iter().enumerate() {
match res_ttask {
Ok(ttask) => {
if counter % 2 == 1 {
// Only every second task is needed, the first one is the
// task before the change, and the second one after
// the change. The (maybe modified) second one is
// expected by taskwarrior.
match serde_to_string(&ttask).chain_err(|| TEK::ImportError) {
// use println!() here, as we talk with TW
Ok(val) => println!("{}", val),
Err(e) => return Err(e),
}
// Taskwarrior does not have the concept of deleted tasks, but only modified
// ones.
//
// Here we check if the status of a task is deleted and if yes, we delete it
// from the store.
if *ttask.status() == TaskStatus::Deleted {
match Task::delete_by_uuid(store, *ttask.uuid()) {
Ok(_) => info!("Deleted task {}", *ttask.uuid()),
Err(e) => return Err(e),
}
}
} // end if c % 2
},
Err(e) => return Err(e).map_err(|_| TE::from_kind(TEK::ImportError)),
}
}
Ok(())
}
pub fn delete_by_uuid(store: &Store, uuid: Uuid) -> Result<()> {
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
.into_storeid()
.and_then(|id| store.delete(id))
.chain_err(|| TEK::StoreError)
}
pub fn all_as_ids(store: &Store) -> Result<StoreIdIterator> {
store.retrieve_for_module("todo/taskwarrior")
.chain_err(|| TEK::StoreError)
}
pub fn all(store: &Store) -> Result<TaskIterator> {
Task::all_as_ids(store)
.map(|iter| TaskIterator::new(store, iter))
}
use uuid::Uuid;
use toml::Value;
use toml_query::read::TomlValueReadExt;
pub trait Task {
fn get_uuid(&self) -> Result<Uuid>;
}
impl<'a> Deref for Task<'a> {
type Target = FileLockEntry<'a>;
fn deref(&self) -> &FileLockEntry<'a> {
&self.0
}
}
impl<'a> DerefMut for Task<'a> {
fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
&mut self.0
}
}
/// A trait to get a `libimagtodo::task::Task` out of the implementing object.
pub trait IntoTask<'a> {
/// # Usage
/// ```ignore
/// use std::io::stdin;
///
/// use task_hookrs::task::Task;
/// use task_hookrs::import::import;
/// use libimagstore::store::{Store, FileLockEntry};
///
/// if let Ok(task_hookrs_task) = import(stdin()) {
/// // Store is given at runtime
/// let task = task_hookrs_task.into_filelockentry(store);
/// println!("Task with uuid: {}", task.flentry.get_header().get("todo.uuid"));
/// }
/// ```
fn into_task(self, store : &'a Store) -> Result<Task<'a>>;
}
impl<'a> IntoTask<'a> for TTask {
fn into_task(self, store : &'a Store) -> Result<Task<'a>> {
use toml_query::read::TomlValueReadExt;
use toml_query::set::TomlValueSetExt;
let uuid = self.uuid();
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
.into_storeid()
.chain_err(|| TEK::StoreIdError)
.and_then(|id| {
store.retrieve(id)
.chain_err(|| TEK::StoreError)
.and_then(|mut fle| {
{
let hdr = fle.get_header_mut();
if try!(hdr.read("todo").chain_err(|| TEK::StoreError)).is_none() {
try!(hdr
.set("todo", Value::Table(BTreeMap::new()))
.chain_err(|| TEK::StoreError));
}
try!(hdr.set("todo.uuid", Value::String(format!("{}",uuid)))
.chain_err(|| TEK::StoreError));
}
// If none of the errors above have returned the function, everything is fine
Ok(Task::new(fle))
})
})
}
}
trait FromStoreId {
fn from_storeid<'a>(&'a Store, StoreId) -> Result<Task<'a>>;
}
impl<'a> FromStoreId for Task<'a> {
fn from_storeid<'b>(store: &'b Store, id: StoreId) -> Result<Task<'b>> {
store.retrieve(id)
.chain_err(|| TEK::StoreError)
.map(Task::new)
}
}
pub struct TaskIterator<'a> {
store: &'a Store,
iditer: StoreIdIterator,
}
impl<'a> TaskIterator<'a> {
pub fn new(store: &'a Store, iditer: StoreIdIterator) -> TaskIterator<'a> {
TaskIterator {
store: store,
iditer: iditer,
impl Task for Entry {
fn get_uuid(&self) -> Result<Uuid> {
match self.get_header().read("todo.uuid") {
Ok(Some(&Value::String(ref uuid))) => {
Uuid::parse_str(uuid).chain_err(|| TEK::UuidParserError)
},
Ok(Some(_)) => Err(TE::from_kind(TEK::HeaderTypeError)),
Ok(None) => Err(TE::from_kind(TEK::HeaderFieldMissing)),
Err(e) => Err(e).chain_err(|| TEK::StoreError),
}
}
}
impl<'a> Iterator for TaskIterator<'a> {
type Item = Result<Task<'a>>;
fn next(&mut self) -> Option<Result<Task<'a>>> {
self.iditer.next().map(|id| Task::from_storeid(self.store, id))
}
}

View file

@ -0,0 +1,207 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::collections::BTreeMap;
use std::io::BufRead;
use std::result::Result as RResult;
use toml::Value;
use uuid::Uuid;
use task_hookrs::task::Task as TTask;
use task_hookrs::import::{import_task, import_tasks};
use libimagstore::store::{FileLockEntry, Store};
use libimagstore::storeid::{IntoStoreId, StoreIdIterator};
use module_path::ModuleEntryPath;
use error::TodoErrorKind as TEK;
use error::TodoError as TE;
use error::Result;
use error::ResultExt;
/// Task struct containing a `FileLockEntry`
pub trait TaskStore<'a> {
fn import_task_from_reader<R: BufRead>(&'a self, r: R) -> Result<(FileLockEntry<'a>, String, Uuid)>;
fn get_task_from_import<R: BufRead>(&'a self, r: R) -> Result<RResult<FileLockEntry<'a>, String>>;
fn get_task_from_string(&'a self, s: String) -> Result<RResult<FileLockEntry<'a>, String>>;
fn get_task_from_uuid(&'a self, uuid: Uuid) -> Result<Option<FileLockEntry<'a>>>;
fn retrieve_task_from_import<R: BufRead>(&'a self, r: R) -> Result<FileLockEntry<'a>>;
fn retrieve_task_from_string(&'a self, s: String) -> Result<FileLockEntry<'a>>;
fn delete_tasks_by_imports<R: BufRead>(&self, r: R) -> Result<()>;
fn delete_task_by_uuid(&self, uuid: Uuid) -> Result<()>;
fn all_tasks(&self) -> Result<StoreIdIterator>;
fn new_from_twtask(&'a self, task: TTask) -> Result<FileLockEntry<'a>>;
}
impl<'a> TaskStore<'a> for Store {
fn import_task_from_reader<R: BufRead>(&'a self, mut r: R) -> Result<(FileLockEntry<'a>, String, Uuid)> {
let mut line = String::new();
try!(r.read_line(&mut line).map_err(|_| TE::from_kind(TEK::UTF8Error)));
import_task(&line.as_str())
.map_err(|_| TE::from_kind(TEK::ImportError))
.and_then(|t| {
let uuid = t.uuid().clone();
self.new_from_twtask(t).map(|t| (t, line, uuid))
})
}
/// Get a task from an import string. That is: read the imported string, get the UUID from it
/// and try to load this UUID from store.
///
/// Possible return values are:
///
/// * Ok(Ok(Task))
/// * Ok(Err(String)) - where the String is the String read from the `r` parameter
/// * Err(_) - where the error is an error that happened during evaluation
///
fn get_task_from_import<R: BufRead>(&'a self, mut r: R) -> Result<RResult<FileLockEntry<'a>, String>> {
let mut line = String::new();
try!(r.read_line(&mut line).chain_err(|| TEK::UTF8Error));
self.get_task_from_string(line)
}
/// Get a task from a String. The String is expected to contain the JSON-representation of the
/// Task to get from the store (only the UUID really matters in this case)
///
/// For an explanation on the return values see `Task::get_from_import()`.
fn get_task_from_string(&'a self, s: String) -> Result<RResult<FileLockEntry<'a>, String>> {
import_task(s.as_str())
.map_err(|_| TE::from_kind(TEK::ImportError))
.map(|t| t.uuid().clone())
.and_then(|uuid| self.get_task_from_uuid(uuid))
.and_then(|o| match o {
None => Ok(Err(s)),
Some(t) => Ok(Ok(t)),
})
}
/// Get a task from an UUID.
///
/// If there is no task with this UUID, this returns `Ok(None)`.
fn get_task_from_uuid(&'a self, uuid: Uuid) -> Result<Option<FileLockEntry<'a>>> {
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
.into_storeid()
.and_then(|store_id| self.get(store_id))
.chain_err(|| TEK::StoreError)
}
/// Same as Task::get_from_import() but uses Store::retrieve() rather than Store::get(), to
/// implicitely create the task if it does not exist.
fn retrieve_task_from_import<R: BufRead>(&'a self, mut r: R) -> Result<FileLockEntry<'a>> {
let mut line = String::new();
try!(r.read_line(&mut line).chain_err(|| TEK::UTF8Error));
self.retrieve_task_from_string(line)
}
/// Retrieve a task from a String. The String is expected to contain the JSON-representation of
/// the Task to retrieve from the store (only the UUID really matters in this case)
fn retrieve_task_from_string(&'a self, s: String) -> Result<FileLockEntry<'a>> {
self.get_task_from_string(s)
.and_then(|opt| match opt {
Ok(task) => Ok(task),
Err(string) => import_task(string.as_str())
.map_err(|_| TE::from_kind(TEK::ImportError))
.and_then(|t| self.new_from_twtask(t)),
})
}
fn delete_tasks_by_imports<R: BufRead>(&self, r: R) -> Result<()> {
use serde_json::ser::to_string as serde_to_string;
use task_hookrs::status::TaskStatus;
for (counter, res_ttask) in import_tasks(r).into_iter().enumerate() {
match res_ttask {
Ok(ttask) => {
if counter % 2 == 1 {
// Only every second task is needed, the first one is the
// task before the change, and the second one after
// the change. The (maybe modified) second one is
// expected by taskwarrior.
match serde_to_string(&ttask).chain_err(|| TEK::ImportError) {
// use println!() here, as we talk with TW
Ok(val) => println!("{}", val),
Err(e) => return Err(e),
}
// Taskwarrior does not have the concept of deleted tasks, but only modified
// ones.
//
// Here we check if the status of a task is deleted and if yes, we delete it
// from the store.
if *ttask.status() == TaskStatus::Deleted {
match self.delete_task_by_uuid(*ttask.uuid()) {
Ok(_) => info!("Deleted task {}", *ttask.uuid()),
Err(e) => return Err(e),
}
}
} // end if c % 2
},
Err(e) => return Err(TE::from_kind(TEK::ImportError)),
}
}
Ok(())
}
fn delete_task_by_uuid(&self, uuid: Uuid) -> Result<()> {
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
.into_storeid()
.and_then(|id| self.delete(id))
.chain_err(|| TEK::StoreError)
}
fn all_tasks(&self) -> Result<StoreIdIterator> {
self.retrieve_for_module("todo/taskwarrior")
.chain_err(|| TEK::StoreError)
}
fn new_from_twtask(&'a self, task: TTask) -> Result<FileLockEntry<'a>> {
use toml_query::read::TomlValueReadExt;
use toml_query::set::TomlValueSetExt;
let uuid = task.uuid();
ModuleEntryPath::new(format!("taskwarrior/{}", uuid))
.into_storeid()
.chain_err(|| TEK::StoreIdError)
.and_then(|id| {
self.retrieve(id)
.chain_err(|| TEK::StoreError)
.and_then(|mut fle| {
{
let hdr = fle.get_header_mut();
if try!(hdr.read("todo").chain_err(|| TEK::StoreError)).is_none() {
try!(hdr
.set("todo", Value::Table(BTreeMap::new()))
.chain_err(|| TEK::StoreError));
}
try!(hdr.set("todo.uuid", Value::String(format!("{}",uuid)))
.chain_err(|| TEK::StoreError));
}
// If none of the errors above have returned the function, everything is fine
Ok(fle)
})
})
}
}

View file

@ -0,0 +1,24 @@
[package]
name = "libimagentrygps"
version = "0.4.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
description = "Library for the imag core distribution"
keywords = ["imag", "PIM", "personal", "information", "management"]
readme = "../README.md"
license = "LGPL-2.1"
[dependencies]
toml = "^0.4"
toml-query = "0.3.0"
serde_derive = "1"
serde = "1"
error-chain = "0.10"
libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }
[dev-dependencies]
env_logger = "0.3"

View file

@ -0,0 +1,123 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use error::Result;
use error::GPSErrorKind as GPSEK;
use error::ResultExt;
use types::*;
use libimagstore::store::Entry;
use toml_query::read::TomlValueReadExt;
use toml_query::insert::TomlValueInsertExt;
pub trait GPSEntry {
fn set_coordinates(&mut self, c: Coordinates) -> Result<()>;
fn get_coordinates(&self) -> Result<Option<Coordinates>>;
}
impl GPSEntry for Entry {
fn set_coordinates(&mut self, c: Coordinates) -> Result<()> {
self.get_header_mut()
.insert("gps.coordinates", c.into())
.map(|_| ())
.chain_err(|| GPSEK::HeaderWriteError)
}
fn get_coordinates(&self) -> Result<Option<Coordinates>> {
match self.get_header().read("gps.coordinates").chain_err(|| GPSEK::HeaderWriteError) {
Ok(Some(hdr)) => Coordinates::from_value(hdr).map(Some),
Ok(None) => Ok(None),
Err(e) => Err(e),
}
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use libimagstore::store::Store;
use entry::*;
fn setup_logging() {
use env_logger;
let _ = env_logger::init().unwrap_or(());
}
fn get_store() -> Store {
Store::new(PathBuf::from("/"), None).unwrap()
}
#[test]
fn test_set_gps() {
setup_logging();
let store = get_store();
let mut entry = store.create(PathBuf::from("test_set_gps")).unwrap();
let coordinates = Coordinates {
latitude: GPSValue::new(0, 0, 0),
longitude: GPSValue::new(0, 0, 0),
};
let res = entry.set_coordinates(coordinates);
assert!(res.is_ok());
}
#[test]
fn test_setget_gps() {
setup_logging();
let store = get_store();
let mut entry = store.create(PathBuf::from("test_setget_gps")).unwrap();
let coordinates = Coordinates {
latitude: GPSValue::new(0, 0, 0),
longitude: GPSValue::new(0, 0, 0),
};
let res = entry.set_coordinates(coordinates);
assert!(res.is_ok());
let coordinates = entry.get_coordinates();
assert!(coordinates.is_ok());
let coordinates = coordinates.unwrap();
assert!(coordinates.is_some());
let coordinates = coordinates.unwrap();
assert_eq!(0, coordinates.longitude.degree);
assert_eq!(0, coordinates.longitude.minutes);
assert_eq!(0, coordinates.longitude.seconds);
assert_eq!(0, coordinates.latitude.degree);
assert_eq!(0, coordinates.latitude.minutes);
assert_eq!(0, coordinates.latitude.seconds);
}
}

View file

@ -0,0 +1,87 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
error_chain! {
types {
GPSError, GPSErrorKind, ResultExt, Result;
}
errors {
StoreReadError {
description("Store read error")
display("Store read error")
}
StoreWriteError {
description("Store write error")
display("Store write error")
}
HeaderWriteError {
description("Couldn't write Header for annotation")
display("Couldn't write Header for annotation")
}
HeaderReadError {
description("Couldn't read Header of Entry")
display("Couldn't read Header of Entry")
}
HeaderTypeError {
description("Header field has unexpected type")
display("Header field has unexpected type")
}
TypeError {
description("Type Error")
display("Type Error")
}
DegreeMissing {
description("'degree' value missing")
display("'degree' value missing")
}
MinutesMissing {
description("'minutes' value missing")
display("'minutes' value missing")
}
SecondsMissing {
description("'seconds' value missing")
display("'seconds' value missing")
}
LongitudeMissing {
description("'longitude' value missing")
display("'longitude' value missing")
}
LatitudeMissing {
description("'latitude' value missing")
display("'latitude' value missing")
}
NumberConversionError {
description("Cannot convert number to fit into variable")
display("Cannot convert number to fit into variable")
}
}
}

View file

@ -17,30 +17,18 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use clap::ArgMatches;
extern crate toml;
extern crate toml_query;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate error_chain;
use libimagstore::store::FileLockEntry;
extern crate libimagstore;
#[macro_use] extern crate libimagerror;
use error::Result;
use tagable::*;
use ui::{get_add_tags, get_remove_tags};
#[cfg(test)]
extern crate env_logger;
pub fn exec_cli_for_entry(matches: &ArgMatches, entry: &mut FileLockEntry) -> Result<()> {
if let Some(ts) = get_add_tags(matches) {
for t in ts {
if let Err(e) = entry.add_tag(t) {
return Err(e);
}
}
}
pub mod entry;
pub mod error;
pub mod types;
if let Some(ts) = get_remove_tags(matches) {
for t in ts {
if let Err(e) = entry.remove_tag(t) {
return Err(e);
}
}
}
Ok(())
}

View file

@ -0,0 +1,156 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::collections::BTreeMap;
use toml::Value;
use error::GPSErrorKind as GPSEK;
use error::GPSError as GPSE;
use error::Result;
use error::ResultExt;
pub trait FromValue : Sized {
fn from_value(v: &Value) -> Result<Self>;
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct GPSValue {
pub degree: i8,
pub minutes: i8,
pub seconds: i8
}
impl GPSValue {
pub fn new(d: i8, m: i8, s: i8) -> GPSValue {
GPSValue {
degree: d,
minutes: m,
seconds: s
}
}
}
impl Into<Value> for GPSValue {
fn into(self) -> Value {
let mut map = BTreeMap::new();
let _ = map.insert("degree".to_owned(), Value::Integer(self.degree as i64));
let _ = map.insert("minutes".to_owned(), Value::Integer(self.minutes as i64));
let _ = map.insert("seconds".to_owned(), Value::Integer(self.seconds as i64));
Value::Table(map)
}
}
impl FromValue for GPSValue {
fn from_value(v: &Value) -> Result<Self> {
match *v {
Value::Table(ref map) => {
Ok(GPSValue::new(
map.get("degree")
.ok_or_else(|| GPSE::from_kind(GPSEK::DegreeMissing))
.and_then(|v| match *v {
Value::Integer(i) => i64_to_i8(i),
_ => Err(GPSE::from_kind(GPSEK::HeaderTypeError)),
})?,
map
.get("minutes")
.ok_or_else(|| GPSE::from_kind(GPSEK::MinutesMissing))
.and_then(|v| match *v {
Value::Integer(i) => i64_to_i8(i),
_ => Err(GPSE::from_kind(GPSEK::HeaderTypeError)),
})?,
map
.get("seconds")
.ok_or_else(|| GPSE::from_kind(GPSEK::SecondsMissing))
.and_then(|v| match *v {
Value::Integer(i) => i64_to_i8(i),
_ => Err(GPSE::from_kind(GPSEK::HeaderTypeError)),
})?
))
}
_ => Err(GPSE::from_kind(GPSEK::TypeError))
}
}
}
/// Data-transfer type for transfering longitude-latitude-pairs
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Coordinates {
pub longitude: GPSValue,
pub latitude: GPSValue,
}
impl Coordinates {
pub fn new(long: GPSValue, lat: GPSValue) -> Coordinates {
Coordinates {
longitude: long,
latitude: lat,
}
}
}
impl Into<Value> for Coordinates {
fn into(self) -> Value {
let mut map = BTreeMap::new();
let _ = map.insert("longitude".to_owned(), self.longitude.into());
let _ = map.insert("latitude".to_owned(), self.latitude.into());
Value::Table(map)
}
}
impl FromValue for Coordinates {
fn from_value(v: &Value) -> Result<Self> {
match *v {
Value::Table(ref map) => {
Ok(Coordinates::new(
match map.get("longitude") {
Some(v) => GPSValue::from_value(v),
None => Err(GPSE::from_kind(GPSEK::LongitudeMissing)),
}?,
match map.get("latitude") {
Some(v) => GPSValue::from_value(v),
None => Err(GPSE::from_kind(GPSEK::LongitudeMissing)),
}?
))
}
_ => Err(GPSE::from_kind(GPSEK::TypeError))
}
}
}
/// Helper to convert a i64 to i8 or return an error if this doesn't work.
fn i64_to_i8(i: i64) -> Result<i8> {
if i > (<i8>::max_value() as i64) {
Err(GPSE::from_kind(GPSEK::NumberConversionError))
} else {
Ok(i as i8)
}
}

View file

@ -35,7 +35,6 @@ use std::collections::BTreeMap;
use std::fmt::Debug;
use libimagstore::store::Entry;
use libimagstore::store::FileLockEntry;
use libimagstore::store::Store;
use libimagstore::storeid::StoreId;
use libimagstore::storeid::IntoStoreId;
@ -58,37 +57,32 @@ use url::Url;
use crypto::sha1::Sha1;
use crypto::digest::Digest;
/// "Link" Type, just an abstraction over `FileLockEntry` to have some convenience internally.
pub struct Link<'a> {
link: FileLockEntry<'a>
pub trait Link {
fn get_link_uri_from_filelockentry(&self) -> Result<Option<Url>>;
fn get_url(&self) -> Result<Option<Url>>;
}
impl<'a> Link<'a> {
impl Link for Entry {
pub fn new(fle: FileLockEntry<'a>) -> Link<'a> {
Link { link: fle }
}
/// Get a link Url object from a `FileLockEntry`, ignore errors.
fn get_link_uri_from_filelockentry(file: &FileLockEntry<'a>) -> Option<Url> {
file.get_header()
fn get_link_uri_from_filelockentry(&self) -> Result<Option<Url>> {
self.get_header()
.read("imag.content.url")
.ok()
.chain_err(|| LEK::EntryHeaderReadError)
.and_then(|opt| match opt {
Some(&Value::String(ref s)) => {
debug!("Found url, parsing: {:?}", s);
Url::parse(&s[..]).ok()
Url::parse(&s[..]).chain_err(|| LEK::InvalidUri).map(Some)
},
_ => None
Some(_) => Err(LE::from_kind(LEK::LinkParserFieldTypeError)),
None => Ok(None),
})
}
pub fn get_url(&self) -> Result<Option<Url>> {
let opt = self.link
.get_header()
.read("imag.content.url");
match opt {
fn get_url(&self) -> Result<Option<Url>> {
match self.get_header().read("imag.content.url") {
Ok(Some(&Value::String(ref s))) => {
Url::parse(&s[..])
.map(Some)
@ -261,23 +255,32 @@ pub mod iter {
type Item = Result<Url>;
fn next(&mut self) -> Option<Self::Item> {
use super::get_external_link_from_file;
use external::Link;
self.0
.next()
.map(|id| {
debug!("Retrieving entry for id: '{:?}'", id);
self.1
.retrieve(id.clone())
.chain_err(|| LEK::StoreReadError)
.map_dbg_err(|_| format!("Retrieving entry for id: '{:?}' failed", id))
.and_then(|f| {
debug!("Store::retrieve({:?}) succeeded", id);
debug!("getting external link from file now");
get_external_link_from_file(&f)
.map_dbg_err(|e| format!("URL -> Err = {:?}", e))
})
})
loop {
let next = self.0
.next()
.map(|id| {
debug!("Retrieving entry for id: '{:?}'", id);
self.1
.retrieve(id.clone())
.chain_err(|| LEK::StoreReadError)
.map_dbg_err(|_| format!("Retrieving entry for id: '{:?}' failed", id))
.and_then(|f| {
debug!("Store::retrieve({:?}) succeeded", id);
debug!("getting external link from file now");
f.get_link_uri_from_filelockentry()
.map_dbg_err(|e| format!("URL -> Err = {:?}", e))
})
});
match next {
Some(Ok(Some(link))) => return Some(Ok(link)),
Some(Ok(None)) => continue,
Some(Err(e)) => return Some(Err(e)),
None => return None
}
}
}
}
@ -291,11 +294,6 @@ pub fn is_external_link_storeid<A: AsRef<StoreId> + Debug>(id: A) -> bool {
id.as_ref().local().starts_with("links/external")
}
fn get_external_link_from_file(entry: &FileLockEntry) -> Result<Url> {
Link::get_link_uri_from_filelockentry(entry) // TODO: Do not hide error by using this function
.ok_or(LE::from_kind(LEK::StoreReadError))
}
/// Implement `ExternalLinker` for `Entry`, hiding the fact that there is no such thing as an external
/// link in an entry, but internal links to other entries which serve as external links, as one
/// entry in the store can only have one external link.

View file

@ -18,9 +18,9 @@ itertools = "0.5"
log = "0.3"
rust-crypto = "0.2"
toml = "^0.4"
walkdir = "1.0.*"
toml-query = "0.3.0"
error-chain = "0.10"
walkdir = "1.0.*"
libimagstore = { version = "0.4.0", path = "../../../lib/core/libimagstore" }
libimagerror = { version = "0.4.0", path = "../../../lib/core/libimagerror" }

View file

@ -52,15 +52,13 @@ impl Hasher for NBytesHasher {
}
fn create_hash<R: Read>(&mut self, _: &PathBuf, contents: &mut R) -> Result<String> {
let s = contents
let s = try!(contents
.bytes()
.take(self.n)
.collect::<RResult<Vec<u8>, _>>()
.chain_err(|| REK::IOError)
.and_then(|v| String::from_utf8(v).chain_err(|| REK::IOError))
.chain_err(|| REK::UTF8Error)
.chain_err(|| REK::IOError);
self.hasher.input_str(&try!(s)[..]);
.and_then(|v| String::from_utf8(v).chain_err(|| REK::UTF8Error)));
self.hasher.input_str(&s[..]);
Ok(self.hasher.result_str())
}

View file

@ -55,3 +55,5 @@ pub mod hasher;
pub mod hashers;
pub mod lister;
pub mod reference;
pub mod refstore;
mod util;

View file

@ -20,13 +20,14 @@
use std::default::Default;
use std::io::stdout;
use std::io::Write;
use std::ops::Deref;
use libimagentrylist::lister::Lister;
use libimagentrylist::error::Result;
use libimagentrylist::error::ResultExt;
use libimagerror::trace::trace_error;
use libimagstore::store::FileLockEntry;
use libimagentrylist::error::ListErrorKind as LEK;
use libimagentrylist::error as lerror;
use reference::Ref;
@ -86,13 +87,45 @@ impl Lister for RefLister {
debug!("fold({:?}, {:?})", accu, entry);
let r = accu.and_then(|_| {
debug!("Listing Entry: {:?}", entry);
lister_fn(entry,
self.check_dead,
self.check_changed,
self.check_changed_content,
self.check_changed_permiss)
{
let is_dead = if self.check_dead {
if try!(lerror::ResultExt::chain_err(entry.fs_link_exists(), || LEK::FormatError)) {
"dead"
} else {
"alive"
}
} else {
"not checked"
};
let is_changed = if self.check_changed {
if check_changed(entry.deref()) { "changed" } else { "unchanged" }
} else {
"not checked"
};
let is_changed_content = if self.check_changed_content {
if check_changed_content(entry.deref()) { "changed" } else { "unchanged" }
} else {
"not checked"
};
let is_changed_permiss = if self.check_changed_permiss {
if check_changed_permiss(entry.deref()) { "changed" } else { "unchanged" }
} else {
"not checked"
};
Ok(format!("{} | {} | {} | {} | {} | {}",
is_dead,
is_changed,
is_changed_content,
is_changed_permiss,
entry.get_path_hash().unwrap_or_else(|_| String::from("Cannot get hash")),
entry.get_location()))
}
.and_then(|s| {
write!(stdout(), "{}\n", s).chain_err(|| LEK::IOError)
lerror::ResultExt::chain_err(write!(stdout(), "{}\n", s), || LEK::FormatError)
})
})
.map(|_| ());
@ -104,68 +137,11 @@ impl Lister for RefLister {
}
fn lister_fn(fle: FileLockEntry,
do_check_dead: bool,
do_check_changed: bool,
do_check_changed_content: bool,
do_check_changed_permiss: bool) -> Result<String>
{
Ref::from_filelockentry(fle)
.map(|r| {
let is_dead = if do_check_dead {
if check_dead(&r) { "dead" } else { "alive" }
} else {
"not checked"
};
let is_changed = if do_check_changed {
if check_changed(&r) { "changed" } else { "unchanged" }
} else {
"not checked"
};
let is_changed_content = if do_check_changed_content {
if check_changed_content(&r) { "changed" } else { "unchanged" }
} else {
"not checked"
};
let is_changed_permiss = if do_check_changed_permiss {
if check_changed_permiss(&r) { "changed" } else { "unchanged" }
} else {
"not checked"
};
format!("{} | {} | {} | {} | {} | {}",
is_dead,
is_changed,
is_changed_content,
is_changed_permiss,
r.get_path_hash().unwrap_or_else(|_| String::from("Cannot get hash")),
r.get_location())
})
.chain_err(|| LEK::FormatError)
}
fn check_dead(r: &Ref) -> bool {
match r.fs_link_exists() {
Ok(b) => b,
Err(e) => {
warn!("Could not check whether the ref {} exists on the FS:", r);
trace_error(&e);
// We continue here and tell the callee that this reference is dead, what is kind of
// true actually, as we might not have access to it right now
true
},
}
}
fn check_changed(r: &Ref) -> bool {
fn check_changed<R: Ref>(r: &R) -> bool {
check_changed_content(r) && check_changed_permiss(r)
}
fn check_changed_content(r: &Ref) -> bool {
fn check_changed_content<R: Ref>(r: &R) -> bool {
let eq = r.get_current_hash()
.and_then(|hash| r.get_stored_hash().map(|stored| (hash, stored)))
.map(|(hash, stored)| hash == stored);
@ -173,7 +149,7 @@ fn check_changed_content(r: &Ref) -> bool {
match eq {
Ok(eq) => eq,
Err(e) => {
warn!("Could not check whether the ref {} changed on the FS:", r);
warn!("Could not check whether the ref changed on the FS");
trace_error(&e);
// We continue here and tell the callee that this reference is unchanged
@ -182,7 +158,7 @@ fn check_changed_content(r: &Ref) -> bool {
}
}
fn check_changed_permiss(_: &Ref) -> bool {
fn check_changed_permiss<R: Ref>(_: &R) -> bool {
warn!("Permission changes tracking not supported yet.");
false
}

View file

@ -21,230 +21,104 @@
//! files outside of the imag store.
use std::path::PathBuf;
use std::ops::Deref;
use std::ops::DerefMut;
use std::collections::BTreeMap;
use std::fs::File;
use std::fmt::{Display, Error as FmtError, Formatter};
use std::fs::Permissions;
use std::result::Result as RResult;
use libimagstore::store::FileLockEntry;
use libimagstore::storeid::StoreId;
use libimagstore::storeid::IntoStoreId;
use libimagstore::store::Store;
use libimagstore::store::Entry;
use toml::Value;
use toml_query::read::TomlValueReadExt;
use toml_query::set::TomlValueSetExt;
use toml_query::insert::TomlValueInsertExt;
use error::RefErrorKind as REK;
use error::RefError as RE;
use error::ResultExt;
use flags::RefFlags;
use error::Result;
use hasher::*;
use module_path::ModuleEntryPath;
#[derive(Debug)]
pub struct Ref<'a>(FileLockEntry<'a>);
impl<'a> Ref<'a> {
/// Try to build a Ref object based on an existing FileLockEntry object
pub fn from_filelockentry(fle: FileLockEntry<'a>) -> Result<Ref<'a>> {
Ref::read_reference(&fle).map(|_| Ref(fle))
}
/// Try to get `si` as Ref object from the store
pub fn get(store: &'a Store, si: StoreId) -> Result<Ref<'a>> {
match store.get(si) {
Err(e) => return Err(e).chain_err(|| REK::StoreReadError),
Ok(None) => return Err(RE::from_kind(REK::RefNotInStore)),
Ok(Some(fle)) => Ref::from_filelockentry(fle),
}
}
/// Get a Ref object from the store by hash.
///
/// Returns None if the hash cannot be found.
pub fn get_by_hash(store: &'a Store, hash: String) -> Result<Option<Ref<'a>>> {
ModuleEntryPath::new(hash)
.into_storeid()
.and_then(|id| store.get(id))
.map(|opt_fle| opt_fle.map(|fle| Ref(fle)))
.chain_err(|| REK::StoreReadError)
}
/// Delete a ref by hash
///
/// If the returned Result contains an error, the ref might not be deleted.
pub fn delete_by_hash(store: &'a Store, hash: String) -> Result<()> {
ModuleEntryPath::new(hash)
.into_storeid()
.and_then(|id| store.delete(id))
.chain_err(|| REK::StoreWriteError)
}
fn read_reference(fle: &FileLockEntry<'a>) -> Result<PathBuf> {
match fle.get_header().read("ref.path") {
Ok(Some(&Value::String(ref s))) => Ok(PathBuf::from(s)),
Ok(Some(_)) => Err(RE::from_kind(REK::HeaderTypeError)),
Ok(None) => Err(RE::from_kind(REK::HeaderFieldMissingError)),
Err(e) => Err(e).chain_err(|| REK::StoreReadError),
}
}
pub fn create_with_hasher<H: Hasher>(store: &'a Store, pb: PathBuf, flags: RefFlags, mut h: H)
-> Result<Ref<'a>>
{
if !pb.exists() {
return Err(RE::from_kind(REK::RefTargetDoesNotExist));
}
if flags.get_content_hashing() && pb.is_dir() {
return Err(RE::from_kind(REK::RefTargetCannotBeHashed));
}
let (mut fle, content_hash, permissions, canonical_path) = { // scope to be able to fold
try!(File::open(pb.clone())
.chain_err(|| REK::RefTargetFileCannotBeOpened)
// If we were able to open this file,
// we hash the contents of the file and return (file, hash)
.and_then(|mut file| {
let opt_contenthash = if flags.get_content_hashing() {
Some(try!(h.create_hash(&pb, &mut file)))
} else {
None
};
Ok((file, opt_contenthash))
})
// and then we get the permissions if we have to
// and return (file, content hash, permissions)
.and_then(|(file, opt_contenthash)| {
let opt_permissions = if flags.get_permission_tracking() {
Some(try!(file
.metadata()
.map(|md| md.permissions())
.chain_err(|| REK::RefTargetCannotReadPermissions)
))
} else {
None
};
Ok((opt_contenthash, opt_permissions))
})
// and then we try to canonicalize the PathBuf, because we want to store a
// canonicalized path
// and return (file, content hash, permissions, canonicalized path)
.and_then(|(opt_contenthash, opt_permissions)| {
pb.canonicalize()
.map(|can| (opt_contenthash, opt_permissions, can))
// if PathBuf::canonicalize() failed, build an error from the return value
.chain_err(|| REK::PathCanonicalizationError)
})
// and then we hash the canonicalized path
// and return (file, content hash, permissions, canonicalized path, path hash)
.and_then(|(opt_contenthash, opt_permissions, can)| {
let path_hash = try!(Ref::hash_path(&can)
.chain_err(|| REK::PathHashingError)
);
Ok((opt_contenthash, opt_permissions, can, path_hash))
})
// and then we convert the PathBuf of the canonicalized path to a String to be able
// to save it in the Ref FileLockEntry obj
// and return
// (file, content hash, permissions, canonicalized path as String, path hash)
.and_then(|(opt_conhash, opt_perm, can, path_hash)| {
match can.to_str().map(String::from) {
// UTF convert error in PathBuf::to_str(),
None => Err(RE::from_kind(REK::PathUTF8Error)),
Some(can) => Ok((opt_conhash, opt_perm, can, path_hash))
}
})
// and then we create the FileLockEntry in the Store
// and return (filelockentry, content hash, permissions, canonicalized path)
.and_then(|(opt_conhash, opt_perm, can, path_hash)| {
let fle = try!(store
.create(ModuleEntryPath::new(path_hash))
.chain_err(|| REK::StoreWriteError)
);
Ok((fle, opt_conhash, opt_perm, can))
})
)
};
for tpl in [
Some((String::from("ref"), Value::Table(BTreeMap::new()))),
Some((String::from("ref.permissions"), Value::Table(BTreeMap::new()))),
Some((String::from("ref.path"), Value::String(canonical_path))),
Some((String::from("ref.content_hash"), Value::Table(BTreeMap::new()))),
content_hash.map(|hash| {
(format!("ref.content_hash.{}", h.hash_name()), Value::String(hash))
}),
permissions.map(|p| {
(String::from("ref.permissions.ro"), Value::Boolean(p.readonly()))
}),
].into_iter()
{
match tpl {
&Some((ref s, ref v)) => {
match fle.get_header_mut().insert(s, v.clone()) {
Ok(None) => {
debug!("Header insert worked");
}
Ok(Some(val)) => {
debug!("Overwrote: {}, which was: {:?}", s, val);
},
Err(e) => {
return Err(e).chain_err(|| REK::HeaderFieldWriteError);
},
}
}
&None => {
debug!("Not going to insert.");
}
}
}
Ok(Ref(fle))
}
/// Create a Ref object which refers to `pb`
pub fn create(store: &'a Store, pb: PathBuf, flags: RefFlags) -> Result<Ref<'a>> {
Ref::create_with_hasher(store, pb, flags, DefaultHasher::new())
}
/// Creates a Hash from a PathBuf by making the PathBuf absolute and then running a hash
/// algorithm on it
fn hash_path(pb: &PathBuf) -> Result<String> {
use crypto::sha1::Sha1;
use crypto::digest::Digest;
match pb.to_str() {
Some(s) => {
let mut hasher = Sha1::new();
hasher.input_str(s);
Ok(hasher.result_str())
},
None => return Err(RE::from_kind(REK::PathUTF8Error)),
}
}
pub trait Ref {
/// Get the hash from the path of the ref
pub fn get_path_hash(&self) -> Result<String> {
self.0
.get_location()
fn get_path_hash(&self) -> Result<String>;
/// Get the hash of the link target which is stored in the ref object
fn get_stored_hash(&self) -> Result<String>;
/// Get the hahs of the link target which is stored in the ref object, which is hashed with a
/// custom Hasher instance.
fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String>;
/// Get the hash of the link target by reading the link target and hashing the contents
fn get_current_hash(&self) -> Result<String>;
/// Get the hash of the link target by reading the link target and hashing the contents with the
/// custom hasher
fn get_current_hash_with_hasher<H: Hasher>(&self, h: H) -> Result<String>;
/// check whether the pointer the Ref represents still points to a file which exists
fn fs_link_exists(&self) -> Result<bool>;
/// Alias for `r.fs_link_exists() && r.deref().is_file()`
fn is_ref_to_file(&self) -> Result<bool>;
/// Alias for `r.fs_link_exists() && r.deref().is_dir()`
fn is_ref_to_dir(&self) -> Result<bool>;
/// Alias for `!Ref::fs_link_exists()`
fn is_dangling(&self) -> Result<bool>;
/// check whether the pointer the Ref represents is valid
/// This includes:
/// - Hashsum of the file is still the same as stored in the Ref
/// - file permissions are still valid
fn fs_link_valid(&self) -> Result<bool>;
/// Check whether the file permissions of the referenced file are equal to the stored
/// permissions
fn fs_link_valid_permissions(&self) -> Result<bool>;
/// Check whether the Hashsum of the referenced file is equal to the stored hashsum
fn fs_link_valid_hash(&self) -> Result<bool>;
/// Update the Ref by re-checking the file from FS
/// This errors if the file is not present or cannot be read()
fn update_ref(&mut self) -> Result<()>;
/// Update the Ref by re-checking the file from FS using the passed Hasher instance
/// This errors if the file is not present or cannot be read()
fn update_ref_with_hasher<H: Hasher>(&mut self, h: &H) -> Result<()>;
/// Get the path of the file which is reffered to by this Ref
fn fs_file(&self) -> Result<PathBuf>;
/// Re-find a referenced file
///
/// This function tries to re-find a ref by searching all directories in `search_roots` recursively
/// for a file which matches the hash of the Ref.
///
/// If `search_roots` is `None`, it starts at the filesystem root `/`.
///
/// If the target cannot be found, this yields a RefTargetDoesNotExist error kind.
///
/// # Warning
///
/// This option causes heavy I/O as it recursively searches the Filesystem.
fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Result<PathBuf>;
/// See documentation of `Ref::refind()`
fn refind_with_hasher<H: Hasher>(&self, search_roots: Option<Vec<PathBuf>>, h: H)
-> Result<PathBuf>;
/// Get the permissions of the file which are present
fn get_current_permissions(&self) -> Result<Permissions>;
}
impl Ref for Entry {
/// Get the hash from the path of the ref
fn get_path_hash(&self) -> Result<String> {
self.get_location()
.clone()
.into_pathbuf()
.chain_err(|| REK::StoreIdError)
@ -258,14 +132,14 @@ impl<'a> Ref<'a> {
}
/// Get the hash of the link target which is stored in the ref object
pub fn get_stored_hash(&self) -> Result<String> {
fn get_stored_hash(&self) -> Result<String> {
self.get_stored_hash_with_hasher(&DefaultHasher::new())
}
/// Get the hahs of the link target which is stored in the ref object, which is hashed with a
/// custom Hasher instance.
pub fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String> {
match self.0.get_header().read(&format!("ref.content_hash.{}", h.hash_name())[..]) {
fn get_stored_hash_with_hasher<H: Hasher>(&self, h: &H) -> Result<String> {
match self.get_header().read(&format!("ref.content_hash.{}", h.hash_name())[..]) {
// content hash stored...
Ok(Some(&Value::String(ref s))) => Ok(s.clone()),
@ -281,13 +155,13 @@ impl<'a> Ref<'a> {
}
/// Get the hash of the link target by reading the link target and hashing the contents
pub fn get_current_hash(&self) -> Result<String> {
fn get_current_hash(&self) -> Result<String> {
self.get_current_hash_with_hasher(DefaultHasher::new())
}
/// Get the hash of the link target by reading the link target and hashing the contents with the
/// custom hasher
pub fn get_current_hash_with_hasher<H: Hasher>(&self, mut h: H) -> Result<String> {
fn get_current_hash_with_hasher<H: Hasher>(&self, mut h: H) -> Result<String> {
self.fs_file()
.and_then(|pb| {
File::open(pb.clone())
@ -297,38 +171,23 @@ impl<'a> Ref<'a> {
.and_then(|(path, mut file)| h.create_hash(&path, &mut file))
}
/// Get the permissions of the file which are present
fn get_current_permissions(&self) -> Result<Permissions> {
self.fs_file()
.and_then(|pb| {
File::open(pb)
.chain_err(|| REK::HeaderFieldReadError)
})
.and_then(|file| {
file
.metadata()
.map(|md| md.permissions())
.chain_err(|| REK::RefTargetCannotReadPermissions)
})
}
/// check whether the pointer the Ref represents still points to a file which exists
pub fn fs_link_exists(&self) -> Result<bool> {
fn fs_link_exists(&self) -> Result<bool> {
self.fs_file().map(|pathbuf| pathbuf.exists())
}
/// Alias for `r.fs_link_exists() && r.deref().is_file()`
pub fn is_ref_to_file(&self) -> Result<bool> {
fn is_ref_to_file(&self) -> Result<bool> {
self.fs_file().map(|pathbuf| pathbuf.is_file())
}
/// Alias for `r.fs_link_exists() && r.deref().is_dir()`
pub fn is_ref_to_dir(&self) -> Result<bool> {
fn is_ref_to_dir(&self) -> Result<bool> {
self.fs_file().map(|pathbuf| pathbuf.is_dir())
}
/// Alias for `!Ref::fs_link_exists()`
pub fn is_dangling(&self) -> Result<bool> {
fn is_dangling(&self) -> Result<bool> {
self.fs_link_exists().map(|b| !b)
}
@ -336,7 +195,7 @@ impl<'a> Ref<'a> {
/// This includes:
/// - Hashsum of the file is still the same as stored in the Ref
/// - file permissions are still valid
pub fn fs_link_valid(&self) -> Result<bool> {
fn fs_link_valid(&self) -> Result<bool> {
match (self.fs_link_valid_permissions(), self.fs_link_valid_hash()) {
(Ok(true) , Ok(true)) => Ok(true),
(Ok(_) , Ok(_)) => Ok(false),
@ -347,8 +206,8 @@ impl<'a> Ref<'a> {
/// Check whether the file permissions of the referenced file are equal to the stored
/// permissions
pub fn fs_link_valid_permissions(&self) -> Result<bool> {
self.0
fn fs_link_valid_permissions(&self) -> Result<bool> {
self
.get_header()
.read("ref.permissions.ro")
.chain_err(|| REK::HeaderFieldReadError)
@ -364,7 +223,7 @@ impl<'a> Ref<'a> {
}
/// Check whether the Hashsum of the referenced file is equal to the stored hashsum
pub fn fs_link_valid_hash(&self) -> Result<bool> {
fn fs_link_valid_hash(&self) -> Result<bool> {
let stored_hash = try!(self.get_stored_hash());
let current_hash = try!(self.get_current_hash());
Ok(stored_hash == current_hash)
@ -372,23 +231,23 @@ impl<'a> Ref<'a> {
/// Update the Ref by re-checking the file from FS
/// This errors if the file is not present or cannot be read()
pub fn update_ref(&mut self) -> Result<()> {
fn update_ref(&mut self) -> Result<()> {
self.update_ref_with_hasher(&DefaultHasher::new())
}
/// Update the Ref by re-checking the file from FS using the passed Hasher instance
/// This errors if the file is not present or cannot be read()
pub fn update_ref_with_hasher<H: Hasher>(&mut self, h: &H) -> Result<()> {
fn update_ref_with_hasher<H: Hasher>(&mut self, h: &H) -> Result<()> {
let current_hash = try!(self.get_current_hash()); // uses the default hasher
let current_perm = try!(self.get_current_permissions());
try!(self.0
try!(self
.get_header_mut()
.set("ref.permissions.ro", Value::Boolean(current_perm.readonly()))
.chain_err(|| REK::StoreWriteError)
);
try!(self.0
try!(self
.get_header_mut()
.set(&format!("ref.content_hash.{}", h.hash_name())[..], Value::String(current_hash))
.chain_err(|| REK::StoreWriteError)
@ -398,8 +257,8 @@ impl<'a> Ref<'a> {
}
/// Get the path of the file which is reffered to by this Ref
pub fn fs_file(&self) -> Result<PathBuf> {
match self.0.get_header().read("ref.path") {
fn fs_file(&self) -> Result<PathBuf> {
match self.get_header().read("ref.path") {
Ok(Some(&Value::String(ref s))) => Ok(PathBuf::from(s)),
Ok(Some(_)) => Err(RE::from_kind(REK::HeaderTypeError)),
Ok(None) => Err(RE::from_kind(REK::HeaderFieldMissingError)),
@ -407,53 +266,6 @@ impl<'a> Ref<'a> {
}
}
/// Check whether there is a reference to the file at `pb`
pub fn exists(store: &Store, pb: PathBuf) -> Result<bool> {
pb.canonicalize()
.chain_err(|| REK::PathCanonicalizationError)
.and_then(|can| {
Ref::hash_path(&can)
.chain_err(|| REK::PathHashingError)
})
.and_then(|hash| {
store.retrieve_for_module("ref").map(|iter| (hash, iter))
.chain_err(|| REK::StoreReadError)
})
.and_then(|(hash, possible_refs)| {
// This is kind of a manual Iterator::filter() call what we do here, but with the
// actual ::filter method we cannot return the error in a nice way, so we do it
// manually here. If you can come up with a better version of this, feel free to
// take this note as a todo.
for r in possible_refs {
let contains_hash = try!(r.to_str()
.chain_err(|| REK::TypeConversionError)
.map(|s| s.contains(&hash[..])));
if !contains_hash {
continue;
}
match store.get(r) {
Ok(Some(fle)) => {
if Ref::read_reference(&fle).map(|path| path == pb).unwrap_or(false) {
return Ok(true)
}
},
Ok(None) => { // Something weird just happened
return Err(RE::from_kind(REK::StoreReadError));
},
Err(e) => {
return Err(e).chain_err(|| REK::StoreReadError);
},
}
}
Ok(false)
})
}
/// Re-find a referenced file
///
/// This function tries to re-find a ref by searching all directories in `search_roots` recursively
@ -466,11 +278,12 @@ impl<'a> Ref<'a> {
/// # Warning
///
/// This option causes heavy I/O as it recursively searches the Filesystem.
pub fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Result<PathBuf> {
fn refind(&self, search_roots: Option<Vec<PathBuf>>) -> Result<PathBuf> {
self.refind_with_hasher(search_roots, DefaultHasher::new())
}
pub fn refind_with_hasher<H: Hasher>(&self, search_roots: Option<Vec<PathBuf>>, mut h: H)
/// See documentation of `Ref::refind()`
fn refind_with_hasher<H: Hasher>(&self, search_roots: Option<Vec<PathBuf>>, mut h: H)
-> Result<PathBuf>
{
use itertools::Itertools;
@ -514,43 +327,19 @@ impl<'a> Ref<'a> {
})
}
}
impl<'a> Deref for Ref<'a> {
type Target = FileLockEntry<'a>;
fn deref(&self) -> &FileLockEntry<'a> {
&self.0
}
}
impl<'a> DerefMut for Ref<'a> {
fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
&mut self.0
}
}
impl<'a> Display for Ref<'a> {
fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
let path = self.fs_file()
.map(|pb| String::from(pb.to_str().unwrap_or("<UTF8-Error>")))
.unwrap_or(String::from("Could not read Path from reference object"));
let hash = self.get_stored_hash().unwrap_or(String::from("<could not read hash>"));
write!(fmt, "Ref({} -> {})", hash, path)
}
}
impl<'a> Into<FileLockEntry<'a>> for Ref<'a> {
fn into(self) -> FileLockEntry<'a> {
self.0
/// Get the permissions of the file which are present
fn get_current_permissions(&self) -> Result<Permissions> {
self.fs_file()
.and_then(|pb| {
File::open(pb)
.chain_err(|| REK::HeaderFieldReadError)
})
.and_then(|file| {
file
.metadata()
.map(|md| md.permissions())
.chain_err(|| REK::RefTargetCannotReadPermissions)
})
}
}

View file

@ -0,0 +1,268 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::path::PathBuf;
use std::collections::BTreeMap;
use std::fs::File;
use libimagstore::store::FileLockEntry;
use libimagstore::storeid::StoreId;
use libimagstore::storeid::IntoStoreId;
use libimagstore::store::Store;
use toml::Value;
use error::RefErrorKind as REK;
use error::RefError as RE;
use error::ResultExt;
use error::Result;
use flags::RefFlags;
use hasher::*;
use module_path::ModuleEntryPath;
use util::*;
pub trait RefStore {
/// Check whether there is a reference to the file at `pb`
fn exists(&self, pb: PathBuf) -> Result<bool>;
/// Try to get `si` as Ref object from the store
fn get<'a>(&'a self, si: StoreId) -> Result<FileLockEntry<'a>>;
/// Get a Ref object from the store by hash.
///
/// Returns None if the hash cannot be found.
fn get_by_hash<'a>(&'a self, hash: String) -> Result<Option<FileLockEntry<'a>>>;
/// Delete a ref by hash
///
/// If the returned Result contains an error, the ref might not be deleted.
fn delete_by_hash(&self, hash: String) -> Result<()>;
/// Create a Ref object which refers to `pb`
fn create<'a>(&'a self, pb: PathBuf, flags: RefFlags) -> Result<FileLockEntry<'a>>;
fn create_with_hasher<'a, H: Hasher>(&'a self, pb: PathBuf, flags: RefFlags, h: H)
-> Result<FileLockEntry<'a>>;
}
impl RefStore for Store {
/// Check whether there is a reference to the file at `pb`
fn exists(&self, pb: PathBuf) -> Result<bool> {
pb.canonicalize()
.chain_err(|| REK::PathCanonicalizationError)
.and_then(|c| hash_path(&c))
.chain_err(|| REK::PathHashingError)
.and_then(|hash| {
self.retrieve_for_module("ref")
.map(|iter| (hash, iter))
.chain_err(|| REK::StoreReadError)
})
.and_then(|(hash, possible_refs)| {
// This is kind of a manual Iterator::filter() call what we do here, but with the
// actual ::filter method we cannot return the error in a nice way, so we do it
// manually here. If you can come up with a better version of this, feel free to
// take this note as a todo.
for r in possible_refs {
let contains_hash = try!(r.to_str()
.chain_err(|| REK::TypeConversionError)
.map(|s| s.contains(&hash[..]))
);
if !contains_hash {
continue;
}
match self.get(r) {
Ok(Some(fle)) => {
if read_reference(&fle).map(|path| path == pb).unwrap_or(false) {
return Ok(true)
}
},
Ok(None) => return Err(RE::from_kind(REK::StoreReadError)),
Err(e) => return Err(e).chain_err(|| REK::StoreReadError)
}
}
Ok(false)
})
}
/// Try to get `si` as Ref object from the store
fn get<'a>(&'a self, si: StoreId) -> Result<FileLockEntry<'a>> {
match self.get(si) {
Err(e) => return Err(e).chain_err(|| REK::StoreReadError),
Ok(None) => return Err(RE::from_kind(REK::RefNotInStore)),
Ok(Some(fle)) => Ok(fle),
}
}
/// Get a Ref object from the store by hash.
///
/// Returns None if the hash cannot be found.
fn get_by_hash<'a>(&'a self, hash: String) -> Result<Option<FileLockEntry<'a>>> {
ModuleEntryPath::new(hash)
.into_storeid()
.and_then(|id| self.get(id))
.chain_err(|| REK::StoreReadError)
}
/// Delete a ref by hash
///
/// If the returned Result contains an error, the ref might not be deleted.
fn delete_by_hash(&self, hash: String) -> Result<()> {
ModuleEntryPath::new(hash)
.into_storeid()
.and_then(|id| self.delete(id))
.chain_err(|| REK::StoreWriteError)
}
/// Create a Ref object which refers to `pb`
fn create<'a>(&'a self, pb: PathBuf, flags: RefFlags) -> Result<FileLockEntry<'a>> {
self.create_with_hasher(pb, flags, DefaultHasher::new())
}
fn create_with_hasher<'a, H: Hasher>(&'a self, pb: PathBuf, flags: RefFlags, mut h: H)
-> Result<FileLockEntry<'a>>
{
use toml_query::insert::TomlValueInsertExt;
if !pb.exists() {
return Err(RE::from_kind(REK::RefTargetDoesNotExist));
}
if flags.get_content_hashing() && pb.is_dir() {
return Err(RE::from_kind(REK::RefTargetCannotBeHashed));
}
let (mut fle, content_hash, permissions, canonical_path) = { // scope to be able to fold
try!(File::open(pb.clone())
.chain_err(|| REK::RefTargetFileCannotBeOpened)
// If we were able to open this file,
// we hash the contents of the file and return (file, hash)
.and_then(|mut file| {
let opt_contenthash = if flags.get_content_hashing() {
Some(try!(h.create_hash(&pb, &mut file)))
} else {
None
};
Ok((file, opt_contenthash))
})
// and then we get the permissions if we have to
// and return (file, content hash, permissions)
.and_then(|(file, opt_contenthash)| {
let opt_permissions = if flags.get_permission_tracking() {
Some(try!(file
.metadata()
.map(|md| md.permissions())
.chain_err(|| REK::RefTargetCannotReadPermissions)
))
} else {
None
};
Ok((opt_contenthash, opt_permissions))
})
// and then we try to canonicalize the PathBuf, because we want to store a
// canonicalized path
// and return (file, content hash, permissions, canonicalized path)
.and_then(|(opt_contenthash, opt_permissions)| {
pb.canonicalize()
.map(|can| (opt_contenthash, opt_permissions, can))
// if PathBuf::canonicalize() failed, build an error from the return value
.chain_err(|| REK::PathCanonicalizationError)
})
// and then we hash the canonicalized path
// and return (file, content hash, permissions, canonicalized path, path hash)
.and_then(|(opt_contenthash, opt_permissions, can)| {
let path_hash = try!(hash_path(&can).chain_err(|| REK::PathHashingError));
Ok((opt_contenthash, opt_permissions, can, path_hash))
})
// and then we convert the PathBuf of the canonicalized path to a String to be able
// to save it in the Ref FileLockEntry obj
// and return
// (file, content hash, permissions, canonicalized path as String, path hash)
.and_then(|(opt_conhash, opt_perm, can, path_hash)| {
match can.to_str().map(String::from) {
// UTF convert error in PathBuf::to_str(),
None => Err(RE::from_kind(REK::PathUTF8Error)),
Some(can) => Ok((opt_conhash, opt_perm, can, path_hash))
}
})
// and then we create the FileLockEntry in the Store
// and return (filelockentry, content hash, permissions, canonicalized path)
.and_then(|(opt_conhash, opt_perm, can, path_hash)| {
let fle = try!(self
.create(ModuleEntryPath::new(path_hash))
.chain_err(|| REK::StoreWriteError)
);
Ok((fle, opt_conhash, opt_perm, can))
})
)
};
for tpl in [
Some((String::from("ref"), Value::Table(BTreeMap::new()))),
Some((String::from("ref.permissions"), Value::Table(BTreeMap::new()))),
Some((String::from("ref.path"), Value::String(canonical_path))),
Some((String::from("ref.content_hash"), Value::Table(BTreeMap::new()))),
content_hash.map(|hash| {
(format!("ref.content_hash.{}", h.hash_name()), Value::String(hash))
}),
permissions.map(|p| {
(String::from("ref.permissions.ro"), Value::Boolean(p.readonly()))
}),
].into_iter()
{
match tpl {
&Some((ref s, ref v)) => {
match fle.get_header_mut().insert(s, v.clone()) {
Ok(Some(_)) => {
let e = RE::from_kind(REK::HeaderFieldAlreadyExistsError);
return Err(e).chain_err(|| REK::HeaderFieldWriteError);
},
Ok(None) => {
// Okay, we just inserted a new header value...
},
Err(e) => return Err(e).chain_err(|| REK::HeaderFieldWriteError),
}
}
&None => {
debug!("Not going to insert.");
}
}
}
Ok(fle)
}
}

View file

@ -0,0 +1,57 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::path::PathBuf;
use error::RefErrorKind as REK;
use error::RefError as RE;
use error::Result;
use error::ResultExt;
use libimagstore::store::Entry;
use toml::Value;
use toml_query::read::TomlValueReadExt;
/// Creates a Hash from a PathBuf by making the PathBuf absolute and then running a hash
/// algorithm on it
pub fn hash_path(pb: &PathBuf) -> Result<String> {
use crypto::sha1::Sha1;
use crypto::digest::Digest;
match pb.to_str() {
Some(s) => {
let mut hasher = Sha1::new();
hasher.input_str(s);
Ok(hasher.result_str())
},
None => return Err(RE::from_kind(REK::PathUTF8Error)),
}
}
/// Read the reference from a file
pub fn read_reference(refentry: &Entry) -> Result<PathBuf> {
match refentry.get_header().read("ref.path") {
Ok(Some(&Value::String(ref s))) => Ok(PathBuf::from(s)),
Ok(Some(_)) => Err(RE::from_kind(REK::HeaderTypeError)),
Ok(None) => Err(RE::from_kind(REK::HeaderFieldMissingError)),
Err(e) => Err(e).chain_err(|| REK::StoreReadError),
}
}

View file

@ -49,8 +49,6 @@ extern crate libimagstore;
extern crate libimagerror;
pub mod error;
pub mod exec;
pub mod tag;
pub mod tagable;
pub mod ui;

View file

@ -1,125 +0,0 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use clap::{Arg, ArgMatches, App, SubCommand};
use tag::Tag;
use tag::is_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(tag_subcommand_name())
.author("Matthias Beyer <mail@beyermatthias.de>")
.version("0.1")
.about("Add or remove tags")
.arg(tag_add_arg())
.arg(tag_remove_arg())
}
pub fn tag_add_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(tag_subcommand_add_arg_name())
.short("a")
.long("add")
.takes_value(true)
.value_name("tags")
.multiple(true)
.validator(is_tag)
.help("Add tags, seperated by comma or by specifying multiple times")
}
pub fn tag_remove_arg<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(tag_subcommand_remove_arg_name())
.short("r")
.long("remove")
.takes_value(true)
.value_name("tags")
.multiple(true)
.validator(is_tag)
.help("Remove tags, seperated by comma or by specifying multiple times")
}
pub fn tag_subcommand_name() -> &'static str {
"tags"
}
pub fn tag_subcommand_add_arg_name() -> &'static str {
"add-tags"
}
pub fn tag_subcommand_remove_arg_name() -> &'static str {
"remove-tags"
}
pub fn tag_subcommand_names() -> Vec<&'static str> {
vec![tag_subcommand_add_arg_name(), tag_subcommand_remove_arg_name()]
}
/// 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(tag_argument_name())
.short("t")
.long("tags")
.takes_value(true)
.multiple(true)
.validator(is_tag)
.help("Add or remove tags, prefixed by '+' (for adding) or '-' (for removing)")
}
pub fn tag_argument_name() -> &'static str {
"specify-tags"
}
/// 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>> {
let add = tag_subcommand_add_arg_name();
extract_tags(matches, add, '+')
.or_else(|| matches.values_of(add).map(|values| values.map(String::from).collect()))
}
/// 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>> {
let rem = tag_subcommand_remove_arg_name();
extract_tags(matches, rem, '+')
.or_else(|| matches.values_of(rem).map(|values| values.map(String::from).collect()))
}
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.starts_with(specchar))
.map(|s| {
String::from(s.split_at(1).1)
})
.collect()
})
}
}

View file

@ -19,7 +19,7 @@
use error::InteractionError as IE;
use error::InteractionErrorKind as IEK;
use error::MapErrInto;
use error::ResultExt;
use toml::Value;
@ -46,36 +46,36 @@ impl Readline {
let histfile = try!(match histfile {
Value::String(s) => PathBuf::from(s),
_ => Err(IE::from_kind(IEK::ConfigTypeError))
.map_err_into(IEK::ConfigError)
.map_err_into(IEK::ReadlineError)
.chain_err(|| IEK::ConfigError)
.chain_err(|| IEK::ReadlineError)
});
let histsize = try!(match histsize {
Value::Integer(i) => i,
_ => Err(IE::from_kind(IEK::ConfigTypeError))
.map_err_into(IEK::ConfigError)
.map_err_into(IEK::ReadlineError)
.chain_err(|| IEK::ConfigError)
.chain_err(|| IEK::ReadlineError)
});
let histigndups = try!(match histigndups {
Value::Boolean(b) => b,
_ => Err(IE::from_kind(IEK::ConfigTypeError))
.map_err_into(IEK::ConfigError)
.map_err_into(IEK::ReadlineError)
.chain_err(|| IEK::ConfigError)
.chain_err(|| IEK::ReadlineError)
});
let histignspace = try!(match histignspace {
Value::Boolean(b) => b,
_ => Err(IE::from_kind(IEK::ConfigTypeError))
.map_err_into(IEK::ConfigError)
.map_err_into(IEK::ReadlineError)
.chain_err(|| IEK::ConfigError)
.chain_err(|| IEK::ReadlineError)
});
let prompt = try!(match prompt {
Value::String(s) => s,
_ => Err(IE::from_kind(IEK::ConfigTypeError))
.map_err_into(IEK::ConfigError)
.map_err_into(IEK::ReadlineError)
.chain_err(|| IEK::ConfigError)
.chain_err(|| IEK::ReadlineError)
});
let config = Config::builder().
@ -88,10 +88,10 @@ impl Readline {
if !histfile.exists() {
let _ = try!(File::create(histfile.clone())
.map_err_into(IEK::ReadlineHistoryFileCreationError));
.chain_err(|| IEK::ReadlineHistoryFileCreationError));
}
let _ = try!(editor.load_history(&histfile).map_err_into(ReadlineError));
let _ = try!(editor.load_history(&histfile).chain_err(|| ReadlineError));
Ok(Readline {
editor: editor,

View file

@ -95,19 +95,34 @@ macro_rules! make_mock_app {
}
}
#[allow(unused)]
pub fn generate_minimal_test_config() -> Option<Configuration> { ::toml::de::from_str("[store]\nimplicit-create=true")
.map(Configuration::with_value)
.ok()
}
#[allow(unused)]
pub fn generate_test_runtime<'a>(mut args: Vec<&'static str>) -> Result<Runtime<'a>, RuntimeError> {
let mut cli_args = vec!["imag-link", "--rtp", "/tmp"];
let mut cli_args = vec![$appname, "--rtp", "/tmp"];
cli_args.append(&mut args);
let cli_app = MockLinkApp::new(cli_args);
Runtime::with_configuration(cli_app, generate_minimal_test_config())
}
#[allow(unused)]
pub fn reset_test_runtime<'a>(mut args: Vec<&'static str>, old_runtime: Runtime)
-> Result<Runtime<'a>, RuntimeError>
{
let mut cli_args = vec![$appname, "--rtp", "/tmp"];
cli_args.append(&mut args);
let cli_app = MockLinkApp::new(cli_args);
Runtime::with_configuration(cli_app, generate_minimal_test_config())
.map(|rt| rt.with_store(old_runtime.extract_store()))
}
}
};

View file

@ -0,0 +1,33 @@
#!/usr/bin/env bash
#
# An hook script to check the commit log message taken by
# applypatch from an e-mail message for proper "Signed-off-by" line(s).
#
# To enable this hook, copy this file to ".git/hooks/applypatch-msg" and make it
# executable.
#
# This hook is used when applying patches which are send via mail, to verify the
# Signed-off-by line is in the commit message.
#
. git-sh-setup
RED='\e[0;31m' # Red
YELLOW='\e[0;33m' # Yellow
NORMAL='\e[0m' # Text Reset
warn() {
echo -e >&2 "${YELLOW}$*${DEFAULT}"
}
abort() {
echo -e >&2 "${RED}$*${DEFAULT}"
exit 1
}
headline=$(head -n 1 $1 | wc -c)
[[ $headline -gt 50 ]] && warn "Headline of patch longer than 50 chars"
grep "^Signed-off-by" $1 >/dev/null 2>/dev/null && abort "No Signed-off-by line"

View file

@ -0,0 +1,17 @@
#!/usr/bin/env bash
#
# The following snippet can be used to _WARN_ if a Signed-off-by line is missing
# in the commit message
#
RED='\e[0;31m' # Red
NORMAL='\e[0m' # Text Reset
if [ "1" != "$(grep -c '^Signed-off-by: ' "$1")" ]; then
printf >&2 "%sMissing Signed-off-by line.%s\n" "$RED" "$NORMAL"
# To not only warn, but abort the commit, uncomment the next line
# exit 1
fi

View file

@ -0,0 +1,58 @@
#!/usr/bin/env bash
#
# The following snippet can be used to WARN about "!fixup" / "WIP" / "TMP"
# commits when pushing
#
# Aborting the push is possible
#
remote="$1"
url="$2"
z40=0000000000000000000000000000000000000000
while read local_ref local_sha remote_ref remote_sha
do
if [ "$local_sha" = $z40 ]
then
# Branch is deleted, nothing to check here, move along.
else
if [ "$remote_sha" = $z40 ]
then
# New branch, examine all commits
range="$local_sha"
else
# Update to existing branch, examine new commits
range="$remote_sha..$local_sha"
fi
# Check for WIP commit
commit=$(git rev-list -n 1 --grep '^WIP|^TMP|!fixup' "$range")
if [ -n "$commit" ]
then
echo >&2 "Found WIP commit in $local_ref, not pushing"
# TO NOT ONLY WARN BUT ABORT UNCOMMENT THE NEXT LINE
# exit 1
fi
# Check for commits without sign-off
if [ "$remote_sha" = $z40 ]; then
# New branch is pushed, we only want to check commits that are not
# on master.
range="$(git merge-base master "$local_sha")..$local_sha"
fi
while read ref; do
msg=$(git log -n 1 --format=%B "$ref")
if ! grep -q '^Signed-off-by: ' <<<"$msg"; then
echo >&2 "Unsigned commit $ref"
exit 1
fi
done < <(git rev-list "$range")
# The process substitution above is a hack to make sure loop runs in
# the same shell and can actually exit the whole script.
fi
done
exit 0

View file

@ -0,0 +1,50 @@
#!/usr/bin/env bash
#
# The following snippet can be used to WARN about a missing signed-off-by line
# in commits when pushing
#
# Aborting the push is possible
#
remote="$1"
url="$2"
z40=0000000000000000000000000000000000000000
while read local_ref local_sha remote_ref remote_sha
do
if [ "$local_sha" = $z40 ]
then
# Branch is deleted, nothing to check here, move along.
else
if [ "$remote_sha" = $z40 ]
then
# New branch, examine all commits
range="$local_sha"
else
# Update to existing branch, examine new commits
range="$remote_sha..$local_sha"
fi
if [ "$remote_sha" = $z40 ]; then
# New branch is pushed, we only want to check commits that are not
# on master.
range="$(git merge-base master "$local_sha")..$local_sha"
fi
while read ref; do
msg=$(git log -n 1 --format=%B "$ref")
if ! grep -q '^Signed-off-by: ' <<<"$msg"; then
echo >&2 "Unsigned commit $ref"
# TO NOT ONLY WARN BUT ABORT UNCOMMENT THE NEXT LINE
# exit 1
fi
done < <(git rev-list "$range")
# The process substitution above is a hack to make sure loop runs in
# the same shell and can actually exit the whole script.
fi
done
exit 0

View file

@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Checks whether all commit between $1 and $2 have a signed-off-by line
RED='\e[0;31m' # Red
NORMAL='\e[0m' # Text Reset
faulty=$(git rev-list --grep "Signed-off-by" --invert-grep $1..$2 | wc -l)
if [[ $faulty -eq 0 ]]
then
echo >&2 "All good"
else
echo -en >&2 "${RED}Got $faulty non Signed-off-by commits${NORMAL}"
echo -e >&2 "${RED}between $1 and $2${NORMAL}"
fi