diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0fd002ee..bc9cc903 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,31 @@ So you want to contribute to imag! Thank you, that's awesome of you! If you already have something in mind, go ahead with [the prerequisites section](#prerequisites). If you don't know what you could do, start here. +All contribors agree to the +[developer certificate of origin](#developer-certificate-of-origin) +by contributing to imag. + +## Without Github + +If you do not want to use github for your contribution, this is completely okay +with us. Feel free to contact [me](https://github.com/matthiasbeyer) via mail, +feel also free to submit patches via mail (use `git format-patch` and +`git send-email`, always add a cover letter to describe your submission). + +Also ensure that each commit has +[a "Signed-off-by: " line](https://stackoverflow.com/questions/1962094/what-is-the-sign-off-feature-in-git-for). +By adding that line, you agree to our +[developer certificate of origin](#developer-certificate-of-origin). + +We do not have a mailinglist, so we do not have a process where all contributors +can review your PR. This means that once _I am_ okay with your patchset, I will +submit it as PR in the github repository, so more people can review it and CI +can test it. I might come back to you if something broke in CI or someone has a +suggestion how to improve your PR. +I will keep you as author of the commits. + +The following sections describe the way how to contribute with github. + ## Finding an issue Finding an issue is simple: We have @@ -95,7 +120,167 @@ ask questions as well! Feel free to reach out via mail. +## More information about the structure of this project + +Here goes some notes on how this project is structured. + +### Issue- and PR-Labels + +Our labels are color coded as well as "namespaced". The color groups labels +exactly as the prefix does. The prefix is the first component which is seperated +from the others by `"/`". See below: + +| Label | Description | search | +| --- | --- | --- | +| complexity/easy | Easy to do | [search][search-complexity/easy] | +| complexity/high | Not so easy to do | [search][search-complexity/high] | +| complexity/medium | Relatively easy | [search][search-complexity/medium] | +| kind/bug | Bug | [search][search-kind/bug] | +| kind/doc | Documentation related | [search][search-kind/doc] | +| kind/enhancement | Enhancement | [search][search-kind/enhancement] | +| kind/feature | Feature | [search][search-kind/feature] | +| kind/hotfix | Hotfix | [search][search-kind/hotfix] | +| kind/infrastructure | Infrastructure code | [search][search-kind/infrastructure] | +| kind/invalid | Not valid Issue/PR | [search][search-kind/invalid] | +| kind/nicetohave | Would be a nice thing | [search][search-kind/nicetohave] | +| kind/refactor | Refactor codebase | [search][search-kind/refactor] | +| meta/assigned | Is assigned | [search][search-meta/assigned] | +| meta/blocked | Blocked by other Issue/PR | [search][search-meta/blocked] | +| meta/blocker | Blocks other Issue/PR | [search][search-meta/blocker] | +| meta/decision-pending | Not clear what to do | [search][search-meta/decision-pending] | +| meta/dependencies | Dependency-related | [search][search-meta/dependencies] | +| meta/doc | Documentation related | [search][search-meta/doc] | +| meta/importance/high | Very Important | [search][search-meta/importance/high] | +| meta/importance/low | Not so important | [search][search-meta/importance/low] | +| meta/importance/medium | Rather important | [search][search-meta/importance/medium] | +| meta/on-hold | Do not work on this! | [search][search-meta/on-hold] | +| meta/ready | Ready for review/merge | [search][search-meta/ready] | +| meta/reopen-later | Reopen closed issue/pr later | [search][search-meta/reopen-later] | +| meta/WIP | Work in Progress | [search][search-meta/WIP] | +| nochange/duplicate | Duplicated | [search][search-nochange/duplicate] | +| nochange/question | Question | [search][search-nochange/question] | +| nochange/rfc | Request for comments | [search][search-nochange/rfc] | +| nochange/wontfix | Won't fix this issue | [search][search-nochange/wontfix] | +| part/bin/imag-counter | Targets binary: imag-counter | [search][search-part/bin/imag-counter] | +| part/bin/imag-link | Targets binary: imag-link | [search][search-part/bin/imag-link] | +| part/bin/imag-store | Targets binary: imag-store | [search][search-part/bin/imag-store] | +| part/bin/imag-tag | Targets binary: imag-tag | [search][search-part/bin/imag-tag] | +| part/bin/imag-view | Targets binary: imag-view | [search][search-part/bin/imag-view] | +| part/interface | Changes the interface | [search][search-part/interface] | +| part/lib/imagcounter | Targets library: imagcounter | [search][search-part/lib/imagcounter] | +| part/lib/imagentryfilter | Targets library: imagentryfilter | [search][search-part/lib/imagentryfilter] | +| part/lib/imagentrylink | Targets library: imagentrylink | [search][search-part/lib/imagentrylink] | +| part/lib/imagentrylist | Targets library: imagentrylist | [search][search-part/lib/imagentrylist] | +| part/lib/imagentrymarkup | Targets library: imagentrymarkup | [search][search-part/lib/imagentrymarkup] | +| part/lib/imagentryprinter | Targets library: imagentryprinter | [search][search-part/lib/imagentryprinter] | +| part/lib/imagentrytag | Targets library: imagentrytag | [search][search-part/lib/imagentrytag] | +| part/lib/imagentryview | Targets library: imagentryview | [search][search-part/lib/imagentryview] | +| part/lib/imagnotes | Targets library: imagnotes | [search][search-part/lib/imagnotes] | +| part/lib/imagrt | Targets library: imagrt | [search][search-part/lib/imagrt] | +| part/lib/imagstore | Targets library: imagstore | [search][search-part/lib/imagstore] | +| part/lib/imagstorestdhook | Targets library: imagstorestdhook | [search][search-part/lib/imagstorestdhook] | +| part/lib/imagutil | Targets library: | [search][search-part/lib/imagutil] | +| part/_new_binary | Introduces new binary | [search][search-part/_new_binary] | +| part/_new_library | Introduces new library | [search][search-part/_new_library] | +| test/change | Changes a test | [search][search-test/change] | +| test/missing | Test missing | [search][search-test/missing] | +| test/new | New test | [search][search-test/new] | + +## Developer Certificate of Origin + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + ## FAQ _to be written_ +[search-complexity/easy]: https://github.com/matthiasbeyer/imag/labels/ +[search-complexity/high]: https://github.com/matthiasbeyer/imag/labels/complexity%2Fhigh +[search-complexity/medium]: https://github.com/matthiasbeyer/imag/labels/complexity%2Fmedium +[search-kind/bug]: https://github.com/matthiasbeyer/imag/labels/kind%2Fbug +[search-kind/doc]: https://github.com/matthiasbeyer/imag/labels/kind%2Fdoc +[search-kind/enhancement]: https://github.com/matthiasbeyer/imag/labels/kind%2Fenhancement +[search-kind/feature]: https://github.com/matthiasbeyer/imag/labels/kind%2Ffeature +[search-kind/hotfix]: https://github.com/matthiasbeyer/imag/labels/kind%2Fhotfix +[search-kind/infrastructure]: https://github.com/matthiasbeyer/imag/labels/kind%2Finfrastructure +[search-kind/invalid]: https://github.com/matthiasbeyer/imag/labels/kind%2Finvalid +[search-kind/nicetohave]: https://github.com/matthiasbeyer/imag/labels/kind%2Fnicetohave +[search-kind/refactor]: https://github.com/matthiasbeyer/imag/labels/kind%2Frefactor +[search-meta/assigned]: https://github.com/matthiasbeyer/imag/labels/meta%2Fassigned +[search-meta/blocked]: https://github.com/matthiasbeyer/imag/labels/meta%2Fblocked +[search-meta/blocker]: https://github.com/matthiasbeyer/imag/labels/meta%2Fblocker +[search-meta/decision-pending]: https://github.com/matthiasbeyer/imag/labels/meta%2Fdecision-pending +[search-meta/dependencies]: https://github.com/matthiasbeyer/imag/labels/meta%2Fdependencies +[search-meta/doc]: https://github.com/matthiasbeyer/imag/labels/meta%2Fdoc +[search-meta/importance/high]: https://github.com/matthiasbeyer/imag/labels/meta%2Fimportance%2Fhigh +[search-meta/importance/low]: https://github.com/matthiasbeyer/imag/labels/meta%2Fimportance%2Flow +[search-meta/importance/medium]: https://github.com/matthiasbeyer/imag/labels/meta%2Fimportance%2Fmedium +[search-meta/on-hold]: https://github.com/matthiasbeyer/imag/labels/meta%2Fon-hold +[search-meta/ready]: https://github.com/matthiasbeyer/imag/labels/meta%2Fready +[search-meta/reopen-later]: https://github.com/matthiasbeyer/imag/labels/meta%2Freopen-later +[search-meta/WIP]: https://github.com/matthiasbeyer/imag/labels/meta%2FWIP +[search-nochange/duplicate]: https://github.com/matthiasbeyer/imag/labels/nochange%2Fduplicate +[search-nochange/question]: https://github.com/matthiasbeyer/imag/labels/nochange%2Fquestion +[search-nochange/rfc]: https://github.com/matthiasbeyer/imag/labels/nochange%2Frfc +[search-nochange/wontfix]: https://github.com/matthiasbeyer/imag/labels/nochange%2Fwontfix +[search-part/bin/imag-counter]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-counter +[search-part/bin/imag-link]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-link +[search-part/bin/imag-store]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-store +[search-part/bin/imag-tag]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-tag +[search-part/bin/imag-view]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-view +[search-part/interface]: https://github.com/matthiasbeyer/imag/labels/part%2F_interface +[search-part/lib/imagcounter]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagcounter +[search-part/lib/imagentryfilter]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentryfilter +[search-part/lib/imagentrylink]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentrylink +[search-part/lib/imagentrylist]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentrylist +[search-part/lib/imagentrymarkup]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentrymarkup +[search-part/lib/imagentryprinter]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentryprinter +[search-part/lib/imagentrytag]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentrytag +[search-part/lib/imagentryview]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentryview +[search-part/lib/imagnotes]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagnotes +[search-part/lib/imagrt]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagrt +[search-part/lib/imagstore]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagstore +[search-part/lib/imagstorestdhook]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagstorestdhook +[search-part/lib/imagutil]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagutil +[search-part/_new_binary]: https://github.com/matthiasbeyer/imag/labels/part%2F_new_binary +[search-part/_new_library]: https://github.com/matthiasbeyer/imag/labels/part%2F_new_library +[search-test/change]: https://github.com/matthiasbeyer/imag/labels/test%2Fchange +[search-test/missing]: https://github.com/matthiasbeyer/imag/labels/test%2Fmissing +[search-test/new]: https://github.com/matthiasbeyer/imag/labels/test%2Fnew diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..7e587fb9 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +toml = $@/Cargo.toml +bin = $@/target/debug/$@ + +default: all + +.PHONY: clean + +all: imag-counter imag-link imag-notes imag-store imag-tag imag-view + +imag-%: prep + cargo build --manifest-path $(toml) + cp $(bin) out/ + +prep: + mkdir -p out/ + +clean: + rm -rf out/ diff --git a/README.md b/README.md index e51719f8..09ff37dd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # imag Imag is a CLI PIM suite with a nice API-ish commandline interface, so you can -integrate it in your tools of coice (Editor, MUA, RSS reader, etc etc). +integrate it in your tools of choice (Editor, MUA, RSS reader, etc etc). ## Goal @@ -89,26 +89,26 @@ provided (as the libraries are work-in-progress). ### Building -To build a single module: +One can build all the modules simply by running `make` which defaults to building all the modules +and placing them in the `out/` directory of the project root. + ``` -$> cd /imag- -$> cargo build -``` -It may be tiresome to build all the modules by hand, but one can do something -like this: -``` -$> for dir in \ ->$(find ./ -maxdepth 1 -path "./imag-*" -name "imag-*" -type d) ->do ->pushd $dir; cargo build; popd ->done +$> make + ... +$> ls out/ +imag-counter imag-link imag-notes imag-store imag-tag imag-view ``` +Building all the modules may take some time, so alternatively one can build only a specific module +by runing `$> make $module` where `$module` is one of the `imag-*` names, such as `imag-counter`, +`imag-link`, etc. + ### Running To run imag, simply call `./bin/imag`. This script has a function to search for modules, which utilizes an environment variable called `IMAG_IS_THE_SHIT`. To run imag with all components: + ``` $> IMAG_IS_THE_SHIT=$(pwd) ./bin/imag ``` diff --git a/bin/Cargo.toml b/bin/Cargo.toml new file mode 100644 index 00000000..b9d6cf22 --- /dev/null +++ b/bin/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "imag" +version = "0.1.0" +authors = ["Matthias Beyer "] + +[dependencies] +version = "2.0" +walkdir = "0.1.5" +crossbeam = "0.2.9" + diff --git a/bin/imag b/bin/imag deleted file mode 100755 index 552ea9b4..00000000 --- a/bin/imag +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env bash - -version() { - echo "0.1.0" -} - -help() { - local cmds="$(commands)" - - echo " _ "; - echo " (_)_ __ ___ __ _ __ _ "; - echo " | | '_ \` _ \ / _\` |/ _\` |"; - echo " | | | | | | | (_| | (_| |"; - echo " |_|_| |_| |_|\__,_|\__, |"; - echo " |___/ "; - echo " -------------------------"; - cat < - - imag - the personal information management suite for the commandline - - imag is a PIM suite for the commandline. It consists of several commands, - called "modules". Each module implements one PIM aspect and all of these - modules can be used independently. - - Available commands: - $(for cmd in $cmds; do - echo -e "\t$(echo $cmd | sed -r 's,(.*)/imag-(.*),\2,')"; - done) - - Call a command with "imag " - Each command can be called with "--help" to get the respective helptext. - - Please visit https://github.com/matthiasbeyer/imag to view the source code, - follow the development of imag or maybe even contribute to imag. - - imag is free software. It is released under the terms of LGPLv2.1 - - (c) 2016 Matthias Beyer and contributors -EOS -} - -commands() { - [[ ! -z "$IMAG_IS_THE_SHIT" ]] && \ - find $IMAG_IS_THE_SHIT -type f -iname "imag-*" -print 2>/dev/null - find ${PATH//:/ } -maxdepth 1 -type f -iname "imag-*" -print 2>/dev/null -} - -main() { - case $1 in - --versions) - echo -n "imag "; version - for command in $(commands); do - $command --version - done - exit 0 - ;; - - --version) - version - exit 0 - ;; - - --help | -h) - help - exit 0 - ;; - - *) - local cmd=$1; shift - local executable=$(commands | grep $cmd | head -n 1) - exec $executable $* - ;; - - esac -} - -main $* diff --git a/bin/src/main.rs b/bin/src/main.rs new file mode 100644 index 00000000..3b154a47 --- /dev/null +++ b/bin/src/main.rs @@ -0,0 +1,187 @@ +extern crate crossbeam; +#[macro_use] extern crate version; +extern crate walkdir; + +use std::env; +use std::process::exit; +use std::process::Command; +use std::process::Stdio; +use std::io::ErrorKind; + +use walkdir::WalkDir; +use crossbeam::*; + +fn help(cmds: Vec) { + println!(r#" + + _ + (_)_ __ ___ __ _ __ _ + | | '_ \` _ \/ _\`|/ _\`| + | | | | | | | (_| | (_| | + |_|_| |_| |_|\__,_|\__, | + |___/ + ------------------------- + + Usage: imag [--version | --versions | -h | --help] + + imag - the personal information management suite for the commandline + + imag is a PIM suite for the commandline. It consists of several commands, + called "modules". Each module implements one PIM aspect and all of these + modules can be used independently. + + Available commands: + "#); + + for cmd in cmds.iter() { + println!("\t{}", cmd); + } + + println!(r#" + + Call a command with 'imag ' + Each command can be called with "--help" to get the respective helptext. + + Please visit https://github.com/matthiasbeyer/imag to view the source code, + follow the development of imag or maybe even contribute to imag. + + imag is free software. It is released under the terms of LGPLv2.1 + + (c) 2016 Matthias Beyer and contributors"#); +} + +fn get_commands() -> Vec { + let path = env::var("PATH"); + if path.is_err() { + println!("PATH error: {:?}", path); + exit(1); + } + let pathelements = path.unwrap(); + let pathelements = pathelements.split(":"); + + let joinhandles : Vec>> = pathelements + .map(|elem| { + crossbeam::scope(|scope| { + scope.spawn(|| { + WalkDir::new(elem) + .max_depth(1) + .into_iter() + .filter(|path| { + match path { + &Ok(ref path) => path.path().starts_with(format!("{}/imag-", elem)), + &Err(_) => false, + } + }) + .filter_map(|x| x.ok()) + .map(|path| { + path.path() + .to_str() + .map(String::from) + }) + .filter_map(|x| x) + .collect() + }) + }) + }) + .collect(); + + let mut execs = vec![]; + for joinhandle in joinhandles.into_iter() { + let mut v = joinhandle.join(); + execs.append(&mut v); + } + + execs +} + +fn find_command() -> Option { + env::args().skip(1).filter(|x| !x.starts_with("-")).next() +} + +fn find_args(command: &str) -> Vec { + env::args() + .skip(1) + .position(|e| e == command) + .map(|pos| env::args().skip(pos + 2).collect::>()) + .unwrap_or(vec![]) +} + +fn main() { + let commands = get_commands(); + let mut args = env::args(); + let _ = args.next(); + let first_arg = match find_command() { + Some(s) => s, + None => { + help(commands); + exit(0); + }, + }; + + match &first_arg[..] { + "--help" | "-h" => { + help(commands); + exit(0); + }, + + "--version" => println!("imag {}", &version!()[..]), + + "--versions" => { + let mut result = vec![]; + for command in commands.iter() { + result.push(crossbeam::scope(|scope| { + scope.spawn(|| { + let v = Command::new(command).arg("--version").output(); + match v { + Ok(v) => match String::from_utf8(v.stdout) { + Ok(s) => format!("{} -> {}", command, s), + Err(e) => format!("Failed calling {} -> {:?}", command, e), + }, + Err(e) => format!("Failed calling {} -> {:?}", command, e), + } + }) + })) + } + + for versionstring in result.into_iter().map(|handle| handle.join()) { + println!("{}", versionstring); + } + }, + + s => { + match Command::new(format!("imag-{}", s)) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .args(&find_args(s)[..]) + .spawn() + .and_then(|mut handle| handle.wait()) + { + Ok(exit_status) => { + if !exit_status.success() { + println!("{} exited with non-zero exit code", s); + exit(exit_status.code().unwrap_or(42)); + } + }, + + Err(e) => { + match e.kind() { + ErrorKind::NotFound => { + println!("No such command: 'imag-{}'", s); + exit(2); + }, + ErrorKind::PermissionDenied => { + println!("No permission to execute: 'imag-{}'", s); + exit(1); + }, + _ => { + println!("Error spawning: {:?}", e); + exit(1337); + } + } + } + } + + }, + } +} diff --git a/doc/Makefile b/doc/Makefile index 495c043b..7f52f29c 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -11,6 +11,7 @@ export MAKE_FLAGS=--no-print-directory export OUT=$(shell pwd)/bin export OUT_PDF=$(OUT)/pdf/ export OUT_HTML=$(OUT)/html/ +export OUT_MAN=$(OUT)/man/ DOCUMENT_CLASS=article SETTING_FONTSIZE=11pt @@ -52,6 +53,10 @@ DOCUMENT_SETTINGS_HTML= \ --table-of-contents \ --webtex +DOCUMENT_SETTINGS_MAN= \ + -s \ + --variable section=5 \ + # # # @@ -89,8 +94,11 @@ export PANDOC_CC_PDF=$(PANDOC) \ export PANDOC_CC_HTML=$(PANDOC) $(PANDOC_PARAMS) $(DOCUMENT_SETTINGS_HTML) +export PANDOC_CC_MAN=$(PANDOC) $(PANDOC_PARAMS) $(DOCUMENT_SETTINGS_MAN) + TARGET_PDF=$(OUT_PDF)/paper.pdf TARGET_HTML=$(OUT_HTML)/index.html +TARGET_MAN=$(OUT_MAN)/imag.5 # # # Tasks @@ -116,6 +124,11 @@ $(OUT_PDF): $(OUT) @$(ECHO) "\t[MKDIR ] $@" @$(MKDIR) $(OUT_PDF) +# create man out directory +$(OUT_MAN): $(OUT) + @$(ECHO) "\t[MKDIR ] $@" + @$(MKDIR) $(OUT_MAN) + # cleanup task clean: @$(ECHO) "\t[RM ] $@" @@ -137,5 +150,12 @@ $(TARGET_HTML): $(OUT_HTML) --template $(TEMPLATES)/default.html5 \ $(SRC) -o $@ +man: $(TARGET_MAN) + +$(TARGET_MAN): $(OUT_MAN) + @$(ECHO) "\t[PANDOC] man" + @$(PANDOC_CC_MAN) \ + $(SRC) -o $@ + .PHONY: $(TARGET_PDF) $(TARGET_HTML) diff --git a/imag-counter/Cargo.toml b/imag-counter/Cargo.toml index 5ef71d51..964eda85 100644 --- a/imag-counter/Cargo.toml +++ b/imag-counter/Cargo.toml @@ -5,12 +5,15 @@ authors = ["Matthias Beyer "] [dependencies] clap = "2.1.1" -log = "0.3.5" +log = "0.3" version = "2.0.1" [dependencies.libimagrt] path = "../libimagrt" +[dependencies.libimagerror] +path = "../libimagerror" + [dependencies.libimagutil] path = "../libimagutil" diff --git a/imag-counter/src/create.rs b/imag-counter/src/create.rs index 66f6590d..22a38b34 100644 --- a/imag-counter/src/create.rs +++ b/imag-counter/src/create.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use std::process::exit; use libimagrt::runtime::Runtime; -use libimagutil::trace::trace_error; +use libimagerror::trace::trace_error; use libimagcounter::counter::Counter; pub fn create(rt: &Runtime) { @@ -17,7 +17,7 @@ pub fn create(rt: &Runtime) { .and_then(|i| FromStr::from_str(i).ok()) .unwrap_or(0); - match Counter::new(rt.store(), String::from(name.clone()), init) { + match Counter::new(rt.store(), String::from(name), init) { Err(e) => { warn!("Could not create Counter '{}' with initial value '{}'", name, init); trace_error(&e); diff --git a/imag-counter/src/delete.rs b/imag-counter/src/delete.rs index bb68f2f1..4d7e6067 100644 --- a/imag-counter/src/delete.rs +++ b/imag-counter/src/delete.rs @@ -1,7 +1,7 @@ use std::process::exit; use libimagrt::runtime::Runtime; -use libimagutil::trace::trace_error; +use libimagerror::trace::trace_error; use libimagcounter::counter::Counter; pub fn delete(rt: &Runtime) { diff --git a/imag-counter/src/interactive.rs b/imag-counter/src/interactive.rs index 03e6b40e..e07d8e1d 100644 --- a/imag-counter/src/interactive.rs +++ b/imag-counter/src/interactive.rs @@ -10,7 +10,7 @@ use libimagcounter::counter::Counter; use libimagcounter::error::CounterError; use libimagrt::runtime::Runtime; use libimagutil::key_value_split::IntoKeyValue; -use libimagutil::trace::trace_error; +use libimagerror::trace::trace_error; type Result = RResult; @@ -51,7 +51,7 @@ pub fn interactive(rt: &Runtime) { exit(1); } - let cont = if input.len() > 0 { + let cont = if !input.is_empty() { let increment = match input.chars().next() { Some('-') => false, _ => true }; input.chars().all(|chr| { match pairs.get_mut(&chr) { @@ -94,8 +94,8 @@ pub fn interactive(rt: &Runtime) { fn has_quit_binding(pairs: &BTreeMap) -> bool { pairs.iter() .any(|(_, bind)| { - match bind { - &Binding::Function(ref name, _) => name == "quit", + match *bind { + Binding::Function(ref name, _) => name == "quit", _ => false, } }) @@ -109,8 +109,8 @@ enum Binding<'a> { impl<'a> Display for Binding<'a> { fn fmt(&self, fmt: &mut Formatter) -> RResult<(), Error> { - match self { - &Binding::Counter(ref c) => { + match *self { + Binding::Counter(ref c) => { match c.name() { Ok(name) => { try!(write!(fmt, "{}", name)); @@ -122,7 +122,7 @@ impl<'a> Display for Binding<'a> { }, } }, - &Binding::Function(ref name, _) => write!(fmt, "{}()", name), + Binding::Function(ref name, _) => write!(fmt, "{}()", name), } } diff --git a/imag-counter/src/list.rs b/imag-counter/src/list.rs index b7276957..57f1ef32 100644 --- a/imag-counter/src/list.rs +++ b/imag-counter/src/list.rs @@ -1,5 +1,5 @@ use libimagrt::runtime::Runtime; -use libimagutil::trace::trace_error; +use libimagerror::trace::trace_error; use libimagcounter::counter::Counter; pub fn list(rt: &Runtime) { @@ -16,13 +16,10 @@ pub fn list(rt: &Runtime) { if name.is_err() { trace_error(&name.unwrap_err()); + } else if value.is_err() { + trace_error(&value.unwrap_err()); } else { - - if value.is_err() { - trace_error(&value.unwrap_err()); - } else { - println!("{} - {}", name.unwrap(), value.unwrap()); - } + println!("{} - {}", name.unwrap(), value.unwrap()); } }) .map_err(|e| trace_error(&e)) diff --git a/imag-counter/src/main.rs b/imag-counter/src/main.rs index 538149dc..76dcc00c 100644 --- a/imag-counter/src/main.rs +++ b/imag-counter/src/main.rs @@ -19,14 +19,15 @@ extern crate clap; extern crate libimagcounter; extern crate libimagrt; +extern crate libimagerror; extern crate libimagutil; use std::process::exit; use std::str::FromStr; -use libimagrt::runtime::Runtime; +use libimagrt::setup::generate_runtime_setup; use libimagcounter::counter::Counter; -use libimagutil::trace::trace_error; +use libimagerror::trace::trace_error; use libimagutil::key_value_split::IntoKeyValue; mod create; @@ -49,20 +50,10 @@ enum Action { } fn main() { - let name = "imag-counter"; - let version = &version!()[..]; - let about = "Counter tool to count things"; - let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); - let rt = { - let rt = Runtime::new(ui); - if rt.is_ok() { - rt.unwrap() - } else { - println!("Could not set up Runtime"); - println!("{:?}", rt.unwrap_err()); - exit(1); - } - }; + let rt = generate_runtime_setup("imag-counter", + &version!()[..], + "Counter tool to count things", + build_ui); rt.cli() .subcommand_name() diff --git a/imag-counter/src/ui.rs b/imag-counter/src/ui.rs index 2e6d18ef..b942498d 100644 --- a/imag-counter/src/ui.rs +++ b/imag-counter/src/ui.rs @@ -7,26 +7,30 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("i") .takes_value(true) .required(false) - .help("Increment a counter")) + .help("Increment a counter") + .value_name("COUNTER")) .arg(Arg::with_name("decrement") .long("dec") .short("d") .takes_value(true) .required(false) - .help("Decrement a counter")) + .help("Decrement a counter") + .value_name("COUNTER")) .arg(Arg::with_name("reset") .long("reset") .takes_value(true) .required(false) - .help("Reset a counter")) + .help("Reset a counter") + .value_name("COUNTER")) .arg(Arg::with_name("set") .long("set") .takes_value(true) .required(false) - .help("Set a counter")) + .help("Set a counter") + .value_name("COUNTER")) .subcommand(SubCommand::with_name("create") .about("Create a counter") @@ -36,13 +40,15 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("n") .takes_value(true) .required(true) - .help("Create counter with this name")) + .help("Create counter with this name") + .value_name("NAME")) .arg(Arg::with_name("initval") .long("init") .short("i") .takes_value(true) .required(false) - .help("Initial value"))) + .help("Initial value") + .value_name("VALUE"))) .subcommand(SubCommand::with_name("delete") .about("Delete a counter") @@ -52,7 +58,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("n") .takes_value(true) .required(true) - .help("Create counter with this name"))) + .help("Create counter with this name") + .value_name("NAME"))) .subcommand(SubCommand::with_name("list") .about("List counters") @@ -62,28 +69,32 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("n") .takes_value(true) .required(false) - .help("List counters with this name (foo/bar and baz/bar would match 'bar')")) + .help("List counters with this name (foo/bar and baz/bar would match 'bar')") + .value_name("NAME")) .arg(Arg::with_name("greater-than") .long("greater") .short("g") .takes_value(true) .required(false) - .help("List counters which are greater than VALUE")) + .help("List counters which are greater than VALUE") + .value_name("VALUE")) .arg(Arg::with_name("lower-than") .long("lower") .short("l") .takes_value(true) .required(false) - .help("List counters which are lower than VALUE")) + .help("List counters which are lower than VALUE") + .value_name("VALUE")) .arg(Arg::with_name("equals") .long("equal") .short("e") .takes_value(true) .required(false) - .help("List counters which equal VALUE")) + .help("List counters which equal VALUE") + .value_name("VALUE")) ) .subcommand(SubCommand::with_name("interactive") @@ -97,6 +108,6 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .required(true) .help("Specification for key-bindings. Use = where KEY is the key to bind (single character) and VALUE is the path to the counter to bind - to."))) + to.") + .value_name("KEY=VALUE"))) } - diff --git a/imag-diary/Cargo.toml b/imag-diary/Cargo.toml new file mode 100644 index 00000000..2edf2daa --- /dev/null +++ b/imag-diary/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "imag-diary" +version = "0.1.0" +authors = ["Matthias Beyer "] + +[dependencies] +chrono = "0.2" +version = "2.0" +clap = "2.1.1" +log = "0.3.5" + +[dependencies.libimagrt] +path = "../libimagrt" + +[dependencies.libimagdiary] +path = "../libimagdiary" + +[dependencies.libimagentrylist] +path = "../libimagentrylist" + +[dependencies.libimagentryview] +path = "../libimagentryview" + +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimaginteraction] +path = "../libimaginteraction" + +[dependencies.libimagutil] +path = "../libimagutil" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagtimeui] +path = "../libimagtimeui" + diff --git a/imag-diary/src/create.rs b/imag-diary/src/create.rs new file mode 100644 index 00000000..0fb46cb9 --- /dev/null +++ b/imag-diary/src/create.rs @@ -0,0 +1,112 @@ +use std::process::exit; + +use libimagdiary::diary::Diary; +use libimagdiary::diaryid::DiaryId; +use libimagdiary::error::DiaryError as DE; +use libimagdiary::error::DiaryErrorKind as DEK; +use libimagrt::edit::Edit; +use libimagrt::runtime::Runtime; +use libimagerror::trace::trace_error; +use libimagdiary::entry::Entry; +use libimagdiary::result::Result; + +use util::get_diary_name; + +pub fn create(rt: &Runtime) { + + let diaryname = get_diary_name(rt); + if diaryname.is_none() { + warn!("No diary selected. Use either the configuration file or the commandline option"); + exit(1); + } + let diaryname = diaryname.unwrap(); + + let prevent_edit = rt.cli().subcommand_matches("create").unwrap().is_present("no-edit"); + + fn create_entry<'a>(diary: &'a Diary, rt: &Runtime) -> Result> { + 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 mut 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 mut 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!("Unexpected error, cannot continue"); + exit(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) + .map_err(|e| DE::new(DEK::DiaryEditError, Some(Box::new(e)))) + } + }); + + if let Err(e) = res { + trace_error(&e); + } else { + info!("Ok!"); + } +} + diff --git a/imag-diary/src/delete.rs b/imag-diary/src/delete.rs new file mode 100644 index 00000000..26e59900 --- /dev/null +++ b/imag-diary/src/delete.rs @@ -0,0 +1,66 @@ +use std::process::exit; +use chrono::naive::datetime::NaiveDateTime; + +use libimagdiary::diary::Diary; +use libimagdiary::diaryid::DiaryId; +use libimagrt::runtime::Runtime; +use libimagerror::trace::trace_error; +use libimagtimeui::datetime::DateTime; +use libimagtimeui::parse::Parse; + +use util::get_diary_name; + +pub fn delete(rt: &Runtime) { + use libimaginteraction::ask::ask_bool; + + let diaryname = get_diary_name(rt); + if diaryname.is_none() { + warn!("No diary selected. Use either the configuration file or the commandline option"); + exit(1); + } + let diaryname = diaryname.unwrap(); + + let diary = Diary::open(rt.store(), &diaryname[..]); + debug!("Diary opened: {:?}", diary); + + let datetime : Option = rt + .cli() + .subcommand_matches("delete") + .unwrap() + .value_of("datetime") + .map(|dt| { debug!("DateTime = {:?}", dt); dt }) + .and_then(DateTime::parse) + .map(|dt| dt.into()); + + 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(&e); + exit(1); + }, + None => { + warn!("No entry"); + exit(1); + }, + }; + + if !ask_bool(&format!("Deleting {:?}", to_del.get_location())[..], Some(true)) { + info!("Aborting delete action"); + return; + } + + match diary.delete_entry(to_del) { + Ok(_) => info!("Ok"), + Err(e) => { + trace_error(&e); + exit(1); + }, + } +} + diff --git a/imag-diary/src/edit.rs b/imag-diary/src/edit.rs new file mode 100644 index 00000000..6b684855 --- /dev/null +++ b/imag-diary/src/edit.rs @@ -0,0 +1,47 @@ +use std::process::exit; +use chrono::naive::datetime::NaiveDateTime; + +use libimagdiary::diary::Diary; +use libimagdiary::diaryid::DiaryId; +use libimagdiary::error::DiaryError as DE; +use libimagdiary::error::DiaryErrorKind as DEK; +use libimagrt::edit::Edit; +use libimagrt::runtime::Runtime; +use libimagerror::trace::trace_error; +use libimagtimeui::datetime::DateTime; +use libimagtimeui::parse::Parse; + +use util::get_diary_name; + +pub fn edit(rt: &Runtime) { + let diaryname = get_diary_name(rt); + if diaryname.is_none() { + warn!("No diary name"); + exit(1); + } + let diaryname = diaryname.unwrap(); + let diary = Diary::open(rt.store(), &diaryname[..]); + + let datetime : Option = 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).map_err(|e| DE::new(DEK::IOError, Some(Box::new(e)))), + + Some(Err(e)) => Err(e), + None => Err(DE::new(DEK::EntryNotInDiary, None)), + } + .map_err(|e| trace_error(&e)).ok(); +} + + diff --git a/imag-diary/src/list.rs b/imag-diary/src/list.rs new file mode 100644 index 00000000..413213c0 --- /dev/null +++ b/imag-diary/src/list.rs @@ -0,0 +1,52 @@ +use std::path::PathBuf; +use std::process::exit; + +use libimagdiary::diary::Diary; +use libimagdiary::error::DiaryError as DE; +use libimagdiary::error::DiaryErrorKind as DEK; +use libimagentrylist::listers::core::CoreLister; +use libimagentrylist::lister::Lister; +use libimagrt::runtime::Runtime; +use libimagstore::storeid::StoreId; +use libimagerror::trace::trace_error; + +use util::get_diary_name; + +pub fn list(rt: &Runtime) { + let diaryname = get_diary_name(rt); + if diaryname.is_none() { + warn!("No diary selected. Use either the configuration file or the commandline option"); + exit(1); + } + let diaryname = diaryname.unwrap(); + + fn location_to_listing_string(id: &StoreId, base: &PathBuf) -> String { + id.strip_prefix(base) + .map_err(|e| trace_error(&e)) + .ok() + .and_then(|p| p.to_str().map(String::from)) + .unwrap_or(String::from("<>")) + } + + let diary = Diary::open(rt.store(), &diaryname[..]); + debug!("Diary opened: {:?}", diary); + diary.entries() + .and_then(|es| { + debug!("Iterator for listing: {:?}", es); + + let es = es.filter_map(|a| { + debug!("Filtering: {:?}", a); + a.ok() + }).map(|e| e.into()); + + let base = rt.store().path(); + + CoreLister::new(&move |e| location_to_listing_string(e.get_location(), base)) + .list(es) // TODO: Do not ignore non-ok()s + .map_err(|e| DE::new(DEK::IOError, Some(Box::new(e)))) + }) + .map(|_| debug!("Ok")) + .map_err(|e| trace_error(&e)) + .ok(); +} + diff --git a/imag-diary/src/main.rs b/imag-diary/src/main.rs new file mode 100644 index 00000000..647a9b25 --- /dev/null +++ b/imag-diary/src/main.rs @@ -0,0 +1,72 @@ +#[macro_use] extern crate log; +#[macro_use] extern crate version; +extern crate clap; +extern crate chrono; + +extern crate libimagdiary; +extern crate libimagentrylist; +extern crate libimagentryview; +extern crate libimaginteraction; +extern crate libimagrt; +extern crate libimagstore; +extern crate libimagutil; +extern crate libimagtimeui; +#[macro_use] extern crate libimagerror; + +use std::process::exit; + +use libimagrt::runtime::Runtime; + +mod create; +mod delete; +mod edit; +mod list; +mod ui; +mod util; +mod view; + +use create::create; +use delete::delete; +use edit::edit; +use list::list; +use ui::build_ui; +use view::view; + +fn main() { + let name = "imag-diary"; + let version = &version!()[..]; + let about = "Personal Diary/Diaries"; + let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); + let rt = { + let rt = Runtime::new(ui); + if rt.is_ok() { + rt.unwrap() + } else { + println!("Could not set up Runtime"); + println!("{:?}", rt.err().unwrap()); + exit(1); + } + }; + + rt.cli() + .subcommand_name() + .map(|name| { + debug!("Call {}", name); + match name { + "create" => create(&rt), + "delete" => delete(&rt), + "edit" => edit(&rt), + "list" => list(&rt), + "diary" => diary(&rt), + "view" => view(&rt), + _ => { + debug!("Unknown command"); // More error handling + }, + } + }); +} + +fn diary(rt: &Runtime) { + unimplemented!() +} + diff --git a/imag-diary/src/ui.rs b/imag-diary/src/ui.rs new file mode 100644 index 00000000..a38060bd --- /dev/null +++ b/imag-diary/src/ui.rs @@ -0,0 +1,107 @@ +use clap::{Arg, ArgGroup, App, SubCommand}; + +pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { + app + .arg(Arg::with_name("diaryname") + .long("diary") + .short("d") + .takes_value(true) + .required(false) + .help("Use other than default diary")) + + .subcommand(SubCommand::with_name("create") + .about("Create a diary entry") + .version("0.1") + .arg(Arg::with_name("no-edit") + .long("no-edit") + .short("e") + .takes_value(false) + .required(false) + .help("Do not edit after creating")) + + .arg(Arg::with_name("timed") + .long("timed") + .short("t") + .takes_value(true) + .required(false) + .help("By default, one entry is created per day. With --timed=h[ourly] or + --timed=m[inutely] one can create per-hour and per-minute entries (more like + a microblog then")) + + .arg(Arg::with_name("hour") + .long("hour") + .takes_value(true) + .required(false) + .help("When using --timed, override the hour component")) + .arg(Arg::with_name("minute") + .long("minute") + .takes_value(true) + .required(false) + .help("When using --timed, override the minute component")) + + // When using --hour or --minute, --timed must be present + .group(ArgGroup::with_name("timing-hourly") + .args(&["hour"]) + .requires("timed")) + .group(ArgGroup::with_name("timing-minutely") + .args(&["minute"]) + .requires("timed")) + ) + + .subcommand(SubCommand::with_name("edit") + .about("Edit a diary entry") + .version("0.1") + .arg(Arg::with_name("datetime") + .long("datetime") + .short("d") + .takes_value(true) + .required(false) + .help("Specify the date and time which entry should be edited. If none is + specified, the last entry is edited. If the diary entry does not exist for + this time, this fails. Format: YYYY-MM-DDT[HH[:mm[:ss]]]")) + ) + + .subcommand(SubCommand::with_name("list") + .about("List diary entries") + .version("0.1")) + + .subcommand(SubCommand::with_name("delete") + .about("Delete a diary entry") + .version("0.1") + .arg(Arg::with_name("datetime") + .long("datetime") + .short("d") + .takes_value(true) + .required(false) + .help("Specify the date and time which entry should be deleted. If none is + specified, the last entry is deleted. If the diary entry does not exist for + this time, this fails. Format: YYYY-MM-DDT[HH[:mm[:ss]]]")) + + .arg(Arg::with_name("select") + .long("select") + .short("s") + .takes_value(false) + .required(false) + .help("Use interactive selection")) + + .arg(Arg::with_name("yes") + .long("yes") + .short("y") + .takes_value(false) + .required(false) + .help("Do not ask for confirmation.")) + ) + + .subcommand(SubCommand::with_name("view") + .about("View entries, currently only supports plain viewing") + .version("0.1") + + .arg(Arg::with_name("show-header") + .long("header") + .takes_value(false) + .required(false) + .help("Show the header when printing the entries")) + ) + +} + diff --git a/imag-diary/src/util.rs b/imag-diary/src/util.rs new file mode 100644 index 00000000..956062be --- /dev/null +++ b/imag-diary/src/util.rs @@ -0,0 +1,9 @@ +use libimagrt::runtime::Runtime; + +pub fn get_diary_name(rt: &Runtime) -> Option { + use libimagdiary::config::get_default_diary_name; + + get_default_diary_name(rt) + .or(rt.cli().value_of("diaryname").map(String::from)) +} + diff --git a/imag-diary/src/view.rs b/imag-diary/src/view.rs new file mode 100644 index 00000000..fd6e931c --- /dev/null +++ b/imag-diary/src/view.rs @@ -0,0 +1,36 @@ +use std::process::exit; + +use libimagdiary::diary::Diary; +use libimagentryview::viewer::Viewer; +use libimagentryview::builtin::plain::PlainViewer; +use libimagrt::runtime::Runtime; +use libimagerror::trace::trace_error; + +use util::get_diary_name; + +pub fn view(rt: &Runtime) { + let diaryname = get_diary_name(rt); + if diaryname.is_none() { + warn!("No diary name"); + exit(1); + } + let diaryname = diaryname.unwrap(); + let diary = Diary::open(rt.store(), &diaryname[..]); + let show_header = rt.cli().subcommand_matches("view").unwrap().is_present("show-header"); + + match diary.entries() { + Ok(entries) => { + let pv = PlainViewer::new(show_header); + for entry in entries.into_iter().filter_map(Result::ok) { + let id = entry.diary_id(); + println!("{} :\n", id); + pv.view_entry(&entry); + println!("\n---\n"); + } + }, + Err(e) => { + trace_error(&e); + }, + } +} + diff --git a/imag-link/Cargo.toml b/imag-link/Cargo.toml index 70df3df5..59ee82f1 100644 --- a/imag-link/Cargo.toml +++ b/imag-link/Cargo.toml @@ -6,10 +6,10 @@ authors = ["Matthias Beyer "] [dependencies] semver = "0.2.1" clap = "2.1.1" -log = "0.3.5" +log = "0.3" version = "2.0.1" toml = "0.1.25" -url = "0.5.5" +url = "1.1" [dependencies.libimagstore] path = "../libimagstore" @@ -20,6 +20,6 @@ path = "../libimagrt" [dependencies.libimagentrylink] path = "../libimagentrylink" -[dependencies.libimagutil] -path = "../libimagutil" +[dependencies.libimagerror] +path = "../libimagerror" diff --git a/imag-link/src/main.rs b/imag-link/src/main.rs index 70f1d019..fdfdb764 100644 --- a/imag-link/src/main.rs +++ b/imag-link/src/main.rs @@ -23,17 +23,18 @@ extern crate url; extern crate libimagentrylink; extern crate libimagrt; extern crate libimagstore; -extern crate libimagutil; +extern crate libimagerror; use std::process::exit; use std::ops::Deref; use libimagrt::runtime::Runtime; +use libimagrt::setup::generate_runtime_setup; use libimagstore::error::StoreError; use libimagstore::store::Entry; use libimagstore::store::FileLockEntry; use libimagstore::store::Store; -use libimagutil::trace::trace_error; +use libimagerror::trace::trace_error; use libimagentrylink::external::ExternalLinker; use clap::ArgMatches; use url::Url; @@ -43,20 +44,10 @@ mod ui; use ui::build_ui; fn main() { - let name = "imag-link"; - let version = &version!()[..]; - let about = "Link entries"; - let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); - let rt = { - let rt = Runtime::new(ui); - if rt.is_ok() { - rt.unwrap() - } else { - println!("Could not set up Runtime"); - println!("{:?}", rt.unwrap_err()); - exit(1); - } - }; + let rt = generate_runtime_setup("imag-link", + &version!()[..], + "Link entries", + build_ui); rt.cli() .subcommand_name() @@ -74,23 +65,21 @@ fn main() { fn handle_internal_linking(rt: &Runtime) { use libimagentrylink::internal::InternalLinker; - use libimagutil::trace::trace_error; + use libimagerror::trace::trace_error; debug!("Handle internal linking call"); let cmd = rt.cli().subcommand_matches("internal").unwrap(); if cmd.is_present("list") { debug!("List..."); - for entry in cmd.value_of("list").unwrap().split(",") { + for entry in cmd.value_of("list").unwrap().split(',') { debug!("Listing for '{}'", entry); match get_entry_by_name(rt, entry) { Ok(e) => { e.get_internal_links() .map(|links| { - let mut i = 0; - for link in links.iter().map(|l| l.to_str()).filter_map(|x| x) { + for (i, link) in links.iter().map(|l| l.to_str()).filter_map(|x| x).enumerate() { println!("{: <3}: {}", i, link); - i += 1; } }) .map_err(|e| trace_error(&e)) @@ -194,7 +183,7 @@ fn get_entry_by_name<'a>(rt: &'a Runtime, name: &str) -> Result { @@ -299,10 +288,8 @@ fn set_links_for_entry(store: &Store, matches: &ArgMatches, entry: &mut FileLock fn list_links_for_entry(store: &Store, entry: &mut FileLockEntry) { let res = entry.get_external_links(store) .and_then(|links| { - let mut i = 0; - for link in links { + for (i, link) in links.iter().enumerate() { println!("{: <3}: {}", i, link); - i += 1; } Ok(()) }); diff --git a/imag-link/src/ui.rs b/imag-link/src/ui.rs index 95881a58..b54c4982 100644 --- a/imag-link/src/ui.rs +++ b/imag-link/src/ui.rs @@ -13,14 +13,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("f") .takes_value(true) .required(true) - .help("Link from this entry")) + .help("Link from this entry") + .value_name("ENTRY")) .arg(Arg::with_name("to") .long("to") .short("t") .takes_value(true) .required(true) .multiple(true) - .help("Link to this entries")) + .help("Link to this entries") + .value_name("ENTRIES")) ) .subcommand(SubCommand::with_name("remove") @@ -31,14 +33,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("f") .takes_value(true) .required(true) - .help("Remove Link from this entry")) + .help("Remove Link from this entry") + .value_name("ENTRY")) .arg(Arg::with_name("to") .long("to") .short("t") .takes_value(true) .required(true) .multiple(true) - .help("Remove links to these entries")) + .help("Remove links to these entries") + .value_name("ENTRIES")) ) .arg(Arg::with_name("list") @@ -46,7 +50,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("l") .takes_value(true) .required(false) - .help("List links to this entry")) + .help("List links to this entry") + .value_name("ENTRY")) ) .subcommand(SubCommand::with_name("external") .about("Add and remove external links") @@ -57,14 +62,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("i") .takes_value(true) .required(true) - .help("Modify external link of this entry")) + .help("Modify external link of this entry") + .value_name("ENTRY")) .arg(Arg::with_name("add") .long("add") .short("a") .takes_value(true) .required(false) - .help("Add this URI as external link")) + .help("Add this URI as external link") + .value_name("URI")) .arg(Arg::with_name("remove") .long("remove") @@ -78,7 +85,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("s") .takes_value(true) .required(false) - .help("Set these URIs as external link (seperate by comma)")) + .help("Set these URIs as external link (seperate by comma)") + .value_name("URIs")) .arg(Arg::with_name("list") .long("list") @@ -93,4 +101,3 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { ) } - diff --git a/imag-notes/Cargo.toml b/imag-notes/Cargo.toml index fac64742..d6f78bfd 100644 --- a/imag-notes/Cargo.toml +++ b/imag-notes/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Matthias Beyer "] [dependencies] semver = "0.2.1" clap = "2.1.1" -log = "0.3.5" +log = "0.3" version = "2.0.1" [dependencies.libimagrt] @@ -18,6 +18,6 @@ path = "../libimagnotes" [dependencies.libimagentrytag] path = "../libimagentrytag" -[dependencies.libimagutil] -path = "../libimagutil" +[dependencies.libimagerror] +path = "../libimagerror" diff --git a/imag-notes/src/main.rs b/imag-notes/src/main.rs index bea757fa..c73297d9 100644 --- a/imag-notes/src/main.rs +++ b/imag-notes/src/main.rs @@ -6,33 +6,24 @@ extern crate semver; extern crate libimagnotes; extern crate libimagrt; extern crate libimagentrytag; -extern crate libimagutil; +extern crate libimagerror; use std::process::exit; use libimagrt::edit::Edit; use libimagrt::runtime::Runtime; +use libimagrt::setup::generate_runtime_setup; use libimagnotes::note::Note; -use libimagutil::trace::trace_error; +use libimagerror::trace::trace_error; mod ui; use ui::build_ui; fn main() { - let name = "imag-notes"; - let version = &version!()[..]; - let about = "Note taking helper"; - let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); - let rt = { - let rt = Runtime::new(ui); - if rt.is_ok() { - rt.unwrap() - } else { - println!("Could not set up Runtime"); - println!("{:?}", rt.unwrap_err()); - exit(1); - } - }; + let rt = generate_runtime_setup("imag-notes", + &version!()[..], + "Note taking helper", + build_ui); rt.cli() .subcommand_name() @@ -60,10 +51,9 @@ fn create(rt: &Runtime) { .map_err(|e| trace_error(&e)) .ok(); - if rt.cli().subcommand_matches("create").unwrap().is_present("edit") { - if !edit_entry(rt, name) { - exit(1); - } + if rt.cli().subcommand_matches("create").unwrap().is_present("edit") && + !edit_entry(rt, name) { + exit(1); } } @@ -79,14 +69,19 @@ fn edit(rt: &Runtime) { } fn edit_entry(rt: &Runtime, name: String) -> bool { - let note = Note::retrieve(rt.store(), name); - if note.is_err() { - trace_error(¬e.unwrap_err()); - warn!("Cannot edit nonexistent Note"); - return false - } + let mut note = match Note::get(rt.store(), name) { + Ok(Some(note)) => note, + Ok(None) => { + warn!("Cannot edit nonexistent Note"); + return false + }, + Err(e) => { + trace_error(&e); + warn!("Cannot edit nonexistent Note"); + return false + }, + }; - let mut note = note.unwrap(); if let Err(e) = note.edit_content(rt) { trace_error(&e); warn!("Editing failed"); diff --git a/imag-notes/src/ui.rs b/imag-notes/src/ui.rs index bf2047d7..6b3b099f 100644 --- a/imag-notes/src/ui.rs +++ b/imag-notes/src/ui.rs @@ -13,7 +13,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("n") .takes_value(true) .required(true) - .help("Create Note with this name")) + .help("Create Note with this name") + .value_name("NAME")) .arg(Arg::with_name("edit") .long("edit") .short("e") @@ -30,7 +31,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("n") .takes_value(true) .required(true) - .help("Delete Note with this name"))) + .help("Delete Note with this name") + .value_name("NAME"))) .subcommand(SubCommand::with_name("edit") .about("Edit a Note") @@ -40,7 +42,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("n") .takes_value(true) .required(true) - .help("Edit Note with this name")) + .help("Edit Note with this name") + .value_name("NAME")) .arg(tag_argument()) .group(ArgGroup::with_name("editargs") @@ -53,5 +56,3 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .version("0.1")) } - - diff --git a/imag-store/Cargo.toml b/imag-store/Cargo.toml index cadd37d0..6c244322 100644 --- a/imag-store/Cargo.toml +++ b/imag-store/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Matthias Beyer "] [dependencies] clap = "2.1.1" -log = "0.3.5" +log = "0.3" version = "2.0.1" semver = "0.2.1" toml = "0.1.25" @@ -19,3 +19,6 @@ path = "../libimagrt" [dependencies.libimagutil] path = "../libimagutil" +[dependencies.libimagerror] +path = "../libimagerror" + diff --git a/imag-store/src/create.rs b/imag-store/src/create.rs index a8b85ad3..8cb2b2aa 100644 --- a/imag-store/src/create.rs +++ b/imag-store/src/create.rs @@ -14,7 +14,7 @@ use libimagrt::runtime::Runtime; use libimagstore::store::Entry; use libimagstore::store::EntryHeader; use libimagstore::storeid::build_entry_path; -use libimagutil::trace::trace_error; +use libimagerror::trace::trace_error; use error::StoreError; use error::StoreErrorKind; @@ -60,30 +60,24 @@ pub fn create(rt: &Runtime) { fn create_from_cli_spec(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Result<()> { let content = matches.subcommand_matches("entry") - .map(|entry_subcommand| { + .map_or_else(|| { + debug!("Didn't find entry subcommand, getting raw content"); + matches.value_of("from-raw") + .map_or_else(String::new, string_from_raw_src) + }, |entry_subcommand| { debug!("Found entry subcommand, parsing content"); entry_subcommand .value_of("content") - .map(String::from) - .unwrap_or_else(|| { - entry_subcommand - .value_of("content-from") - .map(|src| string_from_raw_src(src)) - .unwrap_or(String::new()) - }) - }) - .unwrap_or_else(|| { - debug!("Didn't find entry subcommand, getting raw content"); - matches.value_of("from-raw") - .map(|raw_src| string_from_raw_src(raw_src)) - .unwrap_or(String::new()) + .map_or_else(|| { + entry_subcommand.value_of("content-from") + .map_or_else(String::new, string_from_raw_src) + }, String::from) }); - debug!("Got content with len = {}", content.len()); let header = matches.subcommand_matches("entry") - .map(|entry_matches| build_toml_header(entry_matches, EntryHeader::new())) - .unwrap_or(EntryHeader::new()); + .map_or_else(EntryHeader::new, + |entry_matches| build_toml_header(entry_matches, EntryHeader::new())); create_with_content_and_header(rt, path, content, header) } @@ -92,7 +86,7 @@ fn create_from_source(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Res let content = matches .value_of("from-raw") .ok_or(StoreError::new(StoreErrorKind::NoCommandlineCall, None)) - .map(|raw_src| string_from_raw_src(raw_src)); + .map(string_from_raw_src); if content.is_err() { return content.map(|_| ()); diff --git a/imag-store/src/delete.rs b/imag-store/src/delete.rs index 0c59fc37..fb994818 100644 --- a/imag-store/src/delete.rs +++ b/imag-store/src/delete.rs @@ -1,6 +1,6 @@ use libimagstore::storeid::build_entry_path; use libimagrt::runtime::Runtime; -use libimagutil::trace::trace_error; +use libimagerror::trace::trace_error; pub fn delete(rt: &Runtime) { use std::process::exit; diff --git a/imag-store/src/error.rs b/imag-store/src/error.rs index a9884250..ae9162f0 100644 --- a/imag-store/src/error.rs +++ b/imag-store/src/error.rs @@ -1,81 +1,10 @@ -use std::error::Error; -use std::fmt::Error as FmtError; -use std::clone::Clone; -use std::fmt::{Display, Formatter}; +generate_error_module!( + generate_error_types!(StoreError, StoreErrorKind, + BackendError => "Backend Error", + NoCommandlineCall => "No commandline call" + ); +); -/** - * Kind of store error - */ -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum StoreErrorKind { - BackendError, - NoCommandlineCall, - // maybe more -} - -fn store_error_type_as_str(e: &StoreErrorKind) -> &'static str { - match e { - &StoreErrorKind::BackendError => "Backend Error", - &StoreErrorKind::NoCommandlineCall => "No commandline call", - } -} - -impl Display for StoreErrorKind { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "{}", store_error_type_as_str(self))); - Ok(()) - } - -} - -#[derive(Debug)] -pub struct StoreError { - err_type: StoreErrorKind, - cause: Option>, -} - -impl StoreError { - - /** - * Build a new StoreError from an StoreErrorKind, optionally with cause - */ - pub fn new(errtype: StoreErrorKind, cause: Option>) - -> StoreError - { - StoreError { - err_type: errtype, - cause: cause, - } - } - - /** - * Get the error type of this StoreError - */ - pub fn err_type(&self) -> StoreErrorKind { - self.err_type.clone() - } - -} - -impl Display for StoreError { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "[{}]", store_error_type_as_str(&self.err_type.clone()))); - Ok(()) - } - -} - -impl Error for StoreError { - - fn description(&self) -> &str { - store_error_type_as_str(&self.err_type.clone()) - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - -} +pub use self::error::StoreError; +pub use self::error::StoreErrorKind; diff --git a/imag-store/src/get.rs b/imag-store/src/get.rs new file mode 100644 index 00000000..813f48a4 --- /dev/null +++ b/imag-store/src/get.rs @@ -0,0 +1,31 @@ +use std::process::exit; + +use libimagstore::storeid::build_entry_path; +use libimagrt::runtime::Runtime; +use libimagerror::trace::trace_error; + +use retrieve::print_entry; + +pub fn get(rt: &Runtime) { + rt.cli() + .subcommand_matches("get") + .map(|scmd| { + scmd.value_of("id") + .map(|id| { + let path = build_entry_path(rt.store(), id); + if path.is_err() { + trace_error(&path.unwrap_err()); + exit(1); + } + let path = path.unwrap(); + debug!("path = {:?}", path); + + match rt.store().get(path) { + Ok(Some(entry)) => print_entry(rt, scmd, entry), + Ok(None) => info!("No entry found"), + Err(e) => trace_error(&e), + } + }) + }); +} + diff --git a/imag-store/src/main.rs b/imag-store/src/main.rs index a1e9e09a..c2996fc1 100644 --- a/imag-store/src/main.rs +++ b/imag-store/src/main.rs @@ -22,39 +22,31 @@ extern crate toml; extern crate libimagrt; extern crate libimagstore; extern crate libimagutil; +#[macro_use] extern crate libimagerror; -use libimagrt::runtime::Runtime; -use std::process::exit; +use libimagrt::setup::generate_runtime_setup; -mod error; -mod ui; mod create; -mod retrieve; -mod update; mod delete; +mod error; +mod get; +mod retrieve; +mod ui; +mod update; mod util; -use ui::build_ui; use create::create; -use retrieve::retrieve; -use update::update; use delete::delete; +use get::get; +use retrieve::retrieve; +use ui::build_ui; +use update::update; fn main() { - let name = "imag-store"; - let version = &version!()[..]; - let about = "Direct interface to the store. Use with great care!"; - let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); - let rt = { - let rt = Runtime::new(ui); - if rt.is_ok() { - rt.unwrap() - } else { - println!("Could not set up Runtime"); - println!("{:?}", rt.unwrap_err()); - exit(1); - } - }; + let rt = generate_runtime_setup("imag-store", + &version!()[..], + "Direct interface to the store. Use with great care!", + build_ui); rt.cli() .subcommand_name() @@ -66,10 +58,11 @@ fn main() { |name| { debug!("Call: {}", name); match name { - "create" => create(&rt), - "retrieve" => retrieve(&rt), - "update" => update(&rt), - "delete" => delete(&rt), + "create" => create(&rt), + "delete" => delete(&rt), + "get" => get(&rt), + "retrieve" => retrieve(&rt), + "update" => update(&rt), _ => { debug!("Unknown command"); // More error handling diff --git a/imag-store/src/retrieve.rs b/imag-store/src/retrieve.rs index c6b6adc8..408cad4e 100644 --- a/imag-store/src/retrieve.rs +++ b/imag-store/src/retrieve.rs @@ -6,7 +6,7 @@ use toml::Value; use libimagstore::store::FileLockEntry; use libimagstore::storeid::build_entry_path; use libimagrt::runtime::Runtime; -use libimagutil::trace::trace_error; +use libimagerror::trace::trace_error; pub fn retrieve(rt: &Runtime) { rt.cli() @@ -35,7 +35,7 @@ pub fn retrieve(rt: &Runtime) { }); } -fn print_entry(rt: &Runtime, scmd: &ArgMatches, e: FileLockEntry) { +pub fn print_entry(rt: &Runtime, scmd: &ArgMatches, e: FileLockEntry) { if do_print_raw(scmd) { debug!("Printing raw content..."); println!("{}", e.to_str()); diff --git a/imag-store/src/ui.rs b/imag-store/src/ui.rs index c6e49432..220c8433 100644 --- a/imag-store/src/ui.rs +++ b/imag-store/src/ui.rs @@ -9,17 +9,20 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("p") .takes_value(true) .required(false) - .help("Create at this store path")) + .help("Create at this store path") + .value_name("PATH")) .arg(Arg::with_name("id") .long("id") .short("i") .takes_value(true) .required(false) - .help("Same as --path, for consistency")) + .help("Same as --path, for consistency") + .value_name("PATH")) .arg(Arg::with_name("from-raw") .long("from-raw") .takes_value(true) - .help("Create a new entry by reading this file ('-' for stdin)")) + .help("Create a new entry by reading this file ('-' for stdin)") + .value_name("FILE")) .group(ArgGroup::with_name("create-destination-group") .args(&["path", "id"]) @@ -32,12 +35,14 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .long("content") .short("c") .takes_value(true) - .help("Content for the Entry from commandline")) + .help("Content for the Entry from commandline") + .value_name("CONTENT")) .arg(Arg::with_name("content-from") .long("content-from") .short("f") .takes_value(true) - .help("Content for the Entry from this file ('-' for stdin)")) + .help("Content for the Entry from this file ('-' for stdin)") + .value_name("CONTENT")) .group(ArgGroup::with_name("create-content-group") .args(&["content", "content-from"]) @@ -48,12 +53,13 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("h") .takes_value(true) .multiple(true) - .help("Set a header field. Specify as 'header.field.value=value', multiple allowed")) + .help("Set a header field. Specify as 'header.field.value=value', multiple allowed") + .value_name("header.field.value=value")) ) ) .subcommand(SubCommand::with_name("retrieve") - .about("Get an entry from the store") + .about("Retrieve an entry from the store (implicitely creates the entry)") .version("0.1") .arg(Arg::with_name("id") .long("id") @@ -95,6 +101,51 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { ) ) + .subcommand(SubCommand::with_name("get") + .about("Get an entry from the store (fails if non-existent)") + .version("0.1") + .arg(Arg::with_name("id") + .long("id") + .short("i") + .takes_value(true) + .required(true) + .help("Retrieve by Store Path, where root (/) is the store itself") + .value_name("PATH")) + .arg(Arg::with_name("content") + .long("content") + .short("c") + .help("Print content")) + .arg(Arg::with_name("header") + .long("header") + .short("h") + .help("Print header")) + .arg(Arg::with_name("header-json") + .long("header-json") + .short("j") + .help("Print header as json")) + .arg(Arg::with_name("raw") + .long("raw") + .short("r") + .help("Print Entries as they are in the store")) + + .subcommand(SubCommand::with_name("filter-header") + .about("Retrieve Entries by filtering") + .version("0.1") + .arg(Arg::with_name("header-field-where") + .long("where") + .short("w") + .takes_value(true) + .help("Filter with 'header.field=foo' where the header field 'header.field' equals 'foo'") + .value_name("header.field=foo") + ) + .arg(Arg::with_name("header-field-grep") + .long("grep") + .short("g") + .takes_value(true) + .help("Filter with 'header.field=[a-zA-Z0-9]*' where the header field 'header.field' matches '[a-zA-Z0-9]*'")) + ) + ) + .subcommand(SubCommand::with_name("update") .about("Get an entry from the store") .version("0.1") @@ -103,12 +154,14 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("i") .takes_value(true) .required(true) - .help("Update Store Entry with this path. Root (/) is the store itself")) + .help("Update Store Entry with this path. Root (/) is the store itself") + .value_name("PATH")) .arg(Arg::with_name("content") .long("content") .short("c") .takes_value(true) - .help("Take the content for the new Entry from this file ('-' for stdin)")) + .help("Take the content for the new Entry from this file ('-' for stdin)") + .value_name("CONTENT")) .arg(Arg::with_name("header") .long("header") .short("h") @@ -125,7 +178,7 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("i") .takes_value(true) .required(true) - .help("Remove Store Entry with this path. Root (/) is the store itself")) + .help("Remove Store Entry with this path. Root (/) is the store itself") + .value_name("PATH")) ) } - diff --git a/imag-store/src/update.rs b/imag-store/src/update.rs index 311ce138..5022aaac 100644 --- a/imag-store/src/update.rs +++ b/imag-store/src/update.rs @@ -3,7 +3,7 @@ use std::process::exit; use libimagrt::runtime::Runtime; use libimagstore::storeid::build_entry_path; -use libimagutil::trace::trace_error; +use libimagerror::trace::trace_error; use util::build_toml_header; diff --git a/imag-store/src/util.rs b/imag-store/src/util.rs index f14d462b..197f2846 100644 --- a/imag-store/src/util.rs +++ b/imag-store/src/util.rs @@ -1,4 +1,5 @@ -use std::collections::BTreeMap; +use std::borrow::Cow; +use std::collections::btree_map::{BTreeMap, Entry}; use std::str::Split; use clap::ArgMatches; @@ -21,10 +22,10 @@ pub fn build_toml_header(matches: &ArgMatches, header: EntryHeader) -> EntryHead for tpl in kvs { let (key, value) = tpl.into(); debug!("Splitting: {:?}", key); - let mut split = key.split("."); + let mut split = key.split('.'); let current = split.next(); if current.is_some() { - insert_key_into(String::from(current.unwrap()), &mut split, value, &mut main); + insert_key_into(String::from(current.unwrap()), &mut split, Cow::Owned(value), &mut main); } } @@ -36,9 +37,9 @@ pub fn build_toml_header(matches: &ArgMatches, header: EntryHeader) -> EntryHead } } -fn insert_key_into(current: String, - rest_path: &mut Split<&str>, - value: String, +fn insert_key_into<'a>(current: String, + rest_path: &mut Split, + value: Cow<'a, str>, map: &mut BTreeMap) { let next = rest_path.next(); @@ -47,27 +48,30 @@ fn insert_key_into(current: String, map.insert(current, parse_value(value)); } else { debug!("Inserting into {:?} ... = {:?}", current, value); - if map.contains_key(¤t) { - match map.get_mut(¤t).unwrap() { - &mut Value::Table(ref mut t) => { - insert_key_into(String::from(next.unwrap()), rest_path, value, t); - }, - _ => unreachable!(), + match map.entry(current) { + Entry::Occupied(ref mut e) => { + match *e.get_mut() { + Value::Table(ref mut t) => { + insert_key_into(String::from(next.unwrap()), rest_path, value, t); + }, + _ => unreachable!(), + } + }, + Entry::Vacant(v) => { v.insert(Value::Table( { + let mut submap = BTreeMap::new(); + insert_key_into(String::from(next.unwrap()), rest_path, value, &mut submap); + debug!("Inserting submap = {:?}", submap); + submap })); } - } else { - let mut submap = BTreeMap::new(); - insert_key_into(String::from(next.unwrap()), rest_path, value, &mut submap); - debug!("Inserting submap = {:?}", submap); - map.insert(current, Value::Table(submap)); } } } -fn parse_value(value: String) -> Value { +fn parse_value(value: Cow) -> Value { use std::str::FromStr; - fn is_ary(v: &String) -> bool { - v.chars().next() == Some('[') && v.chars().last() == Some(']') && v.len() >= 3 + fn is_ary(v: &str) -> bool { + v.starts_with('[') && v.ends_with(']') && v.len() >= 3 } if value == "true" { @@ -79,7 +83,7 @@ fn parse_value(value: String) -> Value { } else if is_ary(&value) { debug!("Building Array out of: {:?}...", value); let sub = &value[1..(value.len()-1)]; - Value::Array(sub.split(",").map(|v| parse_value(String::from(v))).collect()) + Value::Array(sub.split(',').map(|x| parse_value(Cow::from(x))).collect()) } else { FromStr::from_str(&value[..]) .map(|i: i64| { @@ -94,7 +98,7 @@ fn parse_value(value: String) -> Value { }) .unwrap_or_else(|_| { debug!("Building String out of: {:?}...", value); - Value::String(value) + Value::String(value.into_owned()) }) }) } diff --git a/imag-store/tests/utils.sh b/imag-store/tests/utils.sh index adf6cd88..34e92600 100644 --- a/imag-store/tests/utils.sh +++ b/imag-store/tests/utils.sh @@ -1,4 +1,4 @@ -source $(dirname ${BASH_SOURCE[0]})/../tests/utils.sh +source $(dirname ${BASH_SOURCE[0]})/../../tests/utils.sh imag-store() { imag-call-binary "$(dirname ${BASH_SOURCE[0]})/../target/debug/" imag-store $* diff --git a/imag-tag/Cargo.toml b/imag-tag/Cargo.toml index 5cd1df03..b9d7af38 100644 --- a/imag-tag/Cargo.toml +++ b/imag-tag/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Matthias Beyer "] [dependencies] clap = "2.1.1" -log = "0.3.5" +log = "0.3" version = "2.0.1" semver = "0.2.1" toml = "0.1.25" @@ -16,8 +16,8 @@ path = "../libimagstore" [dependencies.libimagrt] path = "../libimagrt" -[dependencies.libimagutil] -path = "../libimagutil" +[dependencies.libimagerror] +path = "../libimagerror" [dependencies.libimagentrytag] path = "../libimagentrytag" diff --git a/imag-tag/src/main.rs b/imag-tag/src/main.rs index e1a35a34..efda69a8 100644 --- a/imag-tag/src/main.rs +++ b/imag-tag/src/main.rs @@ -7,46 +7,36 @@ extern crate toml; extern crate libimagstore; extern crate libimagrt; extern crate libimagentrytag; -extern crate libimagutil; +extern crate libimagerror; use std::process::exit; use libimagrt::runtime::Runtime; +use libimagrt::setup::generate_runtime_setup; use libimagentrytag::tagable::Tagable; +use libimagentrytag::tag::Tag; use libimagstore::storeid::build_entry_path; +use libimagerror::trace::trace_error; +use libimagentrytag::ui::{get_add_tags, get_remove_tags}; mod ui; use ui::build_ui; -use libimagutil::trace::trace_error; - fn main() { - let name = "imag-store"; - let version = &version!()[..]; - let about = "Direct interface to the store. Use with great care!"; - let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); - let rt = { - let rt = Runtime::new(ui); - if rt.is_ok() { - rt.unwrap() - } else { - println!("Could not set up Runtime"); - println!("{:?}", rt.unwrap_err()); - exit(1); - } - }; + let rt = generate_runtime_setup("imag-store", + &version!()[..], + "Direct interface to the store. Use with great care!", + build_ui); let id = rt.cli().value_of("id").unwrap(); // enforced by clap rt.cli() .subcommand_name() .map_or_else( || { - let add = rt.cli().value_of("add"); - let rem = rt.cli().value_of("remove"); - let set = rt.cli().value_of("set"); - - alter(&rt, id, add, rem, set); + let add = get_add_tags(rt.cli()); + let rem = get_remove_tags(rt.cli()); + alter(&rt, id, add, rem); }, |name| { debug!("Call: {}", name); @@ -60,7 +50,7 @@ fn main() { }); } -fn alter(rt: &Runtime, id: &str, add: Option<&str>, rem: Option<&str>, set: Option<&str>) { +fn alter(rt: &Runtime, id: &str, add: Option>, rem: Option>) { let path = { match build_entry_path(rt.store(), id) { Err(e) => { @@ -72,43 +62,36 @@ fn alter(rt: &Runtime, id: &str, add: Option<&str>, rem: Option<&str>, set: Opti }; debug!("path = {:?}", path); - rt.store() - // "id" must be present, enforced via clap spec - .retrieve(path) - .map(|mut e| { + match rt.store().get(path) { + Ok(Some(mut e)) => { add.map(|tags| { - let tags = tags.split(","); for tag in tags { - info!("Adding tag '{}'", tag); - if let Err(e) = e.add_tag(String::from(tag)) { + debug!("Adding tag '{:?}'", tag); + if let Err(e) = e.add_tag(tag) { trace_error(&e); } } - }); + }); // it is okay to ignore a None here rem.map(|tags| { - let tags = tags.split(","); for tag in tags { - info!("Removing tag '{}'", tag); - if let Err(e) = e.remove_tag(String::from(tag)) { + debug!("Removing tag '{:?}'", tag); + if let Err(e) = e.remove_tag(tag) { trace_error(&e); } } - }); + }); // it is okay to ignore a None here + }, - set.map(|tags| { - info!("Setting tags '{}'", tags); - let tags = tags.split(",").map(String::from).collect(); - if let Err(e) = e.set_tags(tags) { - trace_error(&e); - } - }); - }) - .map_err(|e| { + Ok(None) => { + info!("No entry found."); + }, + + Err(e) => { info!("No entry."); trace_error(&e); - }) - .ok(); + }, + } } fn list(id: &str, rt: &Runtime) { @@ -123,14 +106,20 @@ fn list(id: &str, rt: &Runtime) { }; debug!("path = {:?}", path); - let entry = rt.store().retrieve(path.clone()); - if entry.is_err() { - debug!("Could not retrieve '{:?}' => {:?}", id, path); - warn!("Could not retrieve entry '{}'", id); - trace_error(&entry.unwrap_err()); - exit(1); - } - let entry = entry.unwrap(); + let entry = match rt.store().get(path.clone()) { + Ok(Some(e)) => e, + Ok(None) => { + info!("No entry found."); + exit(1); + }, + + Err(e) => { + debug!("Could not get '{:?}' => {:?}", id, path); + warn!("Could not get entry '{}'", id); + trace_error(&e); + exit(1); + }, + }; let scmd = rt.cli().subcommand_matches("list").unwrap(); // safe, we checked in main() diff --git a/imag-tag/src/ui.rs b/imag-tag/src/ui.rs index 4f78bf1f..1355344a 100644 --- a/imag-tag/src/ui.rs +++ b/imag-tag/src/ui.rs @@ -1,36 +1,18 @@ use clap::{Arg, App, ArgGroup, SubCommand}; +use libimagentrytag::ui::{tag_add_arg, tag_remove_arg}; + pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { app.arg(Arg::with_name("id") .long("id") .short("i") .takes_value(true) .required(true) - .help("Use this entry")) + .help("Use this entry") + .value_name("ID")) - .arg(Arg::with_name("add") - .long("add") - .short("a") - .takes_value(true) - .required(false) - .multiple(true) - .help("Add this tag")) - - .arg(Arg::with_name("remove") - .long("remove") - .short("r") - .takes_value(true) - .required(false) - .multiple(true) - .help("Remove this tag")) - - .arg(Arg::with_name("set") - .long("set") - .short("s") - .takes_value(true) - .required(false) - .multiple(true) - .help("Set these tags")) + .arg(tag_add_arg()) + .arg(tag_remove_arg()) .subcommand(SubCommand::with_name("list") .about("List tags (default)") @@ -58,7 +40,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("s") .takes_value(true) .required(false) - .help("Seperated by string")) + .help("Separated by string") + .value_name("SEP")) .group(ArgGroup::with_name("list-group") .args(&[ @@ -71,5 +54,3 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { ) } - - diff --git a/imag-view/Cargo.toml b/imag-view/Cargo.toml index 5dd2504d..2c43920e 100644 --- a/imag-view/Cargo.toml +++ b/imag-view/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Matthias Beyer "] [dependencies] clap = "2.1.1" glob = "0.2.11" -log = "0.3.5" +log = "0.3" rustbox = "0.8.1" semver = "0.2.1" toml = "0.1.25" @@ -18,6 +18,6 @@ path = "../libimagstore" [dependencies.libimagrt] path = "../libimagrt" -[dependencies.libimagutil] -path = "../libimagutil" +[dependencies.libimagerror] +path = "../libimagerror" diff --git a/imag-view/src/error.rs b/imag-view/src/error.rs index bdbb1d76..77c7b95d 100644 --- a/imag-view/src/error.rs +++ b/imag-view/src/error.rs @@ -1,87 +1,12 @@ -use std::error::Error; -use std::fmt::Error as FmtError; -use std::clone::Clone; -use std::fmt::{Display, Formatter}; +generate_error_module!( + generate_error_types!(ViewError, ViewErrorKind, + StoreError => "Store error", + NoVersion => "No version specified", + PatternError => "Error in Pattern", + GlobBuildError => "Could not build glob() Argument" + ); +); -/** - * Kind of store error - */ -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum ViewErrorKind { - StoreError, - NoVersion, - PatternError, - GlobBuildError, -} - -fn view_error_type_as_str(e: &ViewErrorKind) -> &'static str { - match e { - &ViewErrorKind::StoreError => "Store error", - &ViewErrorKind::NoVersion => "No version specified", - &ViewErrorKind::PatternError => "Error in Pattern", - &ViewErrorKind::GlobBuildError => "Could not build glob() Argument", - } -} - -impl Display for ViewErrorKind { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "{}", view_error_type_as_str(self))); - Ok(()) - } - -} - -/** - * View error type - */ -#[derive(Debug)] -pub struct ViewError { - err_type: ViewErrorKind, - cause: Option>, -} - -impl ViewError { - - /** - * Build a new ViewError from an ViewErrorKind, optionally with cause - */ - pub fn new(errtype: ViewErrorKind, cause: Option>) - -> ViewError - { - ViewError { - err_type: errtype, - cause: cause, - } - } - - /** - * Get the error type of this ViewError - */ - pub fn err_type(&self) -> ViewErrorKind { - self.err_type.clone() - } - -} - -impl Display for ViewError { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "[{}]", view_error_type_as_str(&self.err_type.clone()))); - Ok(()) - } - -} - -impl Error for ViewError { - - fn description(&self) -> &str { - view_error_type_as_str(&self.err_type.clone()) - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - -} +pub use self::error::ViewError; +pub use self::error::ViewErrorKind; diff --git a/imag-view/src/main.rs b/imag-view/src/main.rs index f12e639d..2fa7802b 100644 --- a/imag-view/src/main.rs +++ b/imag-view/src/main.rs @@ -22,14 +22,15 @@ extern crate toml; extern crate libimagrt; extern crate libimagstore; -extern crate libimagutil; +#[macro_use] extern crate libimagerror; use std::result::Result as RResult; use std::process::exit; use libimagrt::runtime::Runtime; +use libimagrt::setup::generate_runtime_setup; use libimagstore::store::FileLockEntry; -use libimagutil::trace::trace_error; +use libimagerror::trace::trace_error; mod error; mod ui; @@ -44,20 +45,10 @@ use viewer::stdout::StdoutViewer; type Result = RResult; fn main() { - let name = "imag-view"; - let version = &version!()[..]; - let about = "View entries (readonly)"; - let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); - let rt = { - let rt = Runtime::new(ui); - if rt.is_ok() { - rt.unwrap() - } else { - println!("Could not set up Runtime"); - println!("{:?}", rt.unwrap_err()); - exit(1); // we can afford not-executing destructors here - } - }; + let rt = generate_runtime_setup( "imag-view", + &version!()[..], + "View entries (readonly)", + build_ui); let entry_id = rt.cli().value_of("id").unwrap(); // enforced by clap @@ -129,7 +120,7 @@ fn load_entry<'a>(id: &str, let version = { if version.is_none() { - let r = id.split("~").last(); + let r = id.split('~').last(); if r.is_none() { warn!("No version"); return Err(ViewError::new(ViewErrorKind::NoVersion, None)); @@ -144,7 +135,7 @@ fn load_entry<'a>(id: &str, debug!("Building path from {:?} and {:?}", id, version); let mut path = rt.store().path().clone(); - if id.chars().next() == Some('/') { + if id.starts_with('/') { path.push(format!("{}~{}", &id[1..id.len()], version)); } else { path.push(format!("{}~{}", id, version)); @@ -161,7 +152,7 @@ fn view_versions_of(id: &str, rt: &Runtime) -> Result<()> { let mut path = rt.store().path().clone(); - if id.chars().next() == Some('/') { + if id.starts_with('/') { path.push(format!("{}~*", &id[1..id.len()])); } else { path.push(format!("{}~*", id)); diff --git a/imag-view/src/ui.rs b/imag-view/src/ui.rs index 619e4f27..54979f1f 100644 --- a/imag-view/src/ui.rs +++ b/imag-view/src/ui.rs @@ -7,14 +7,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("i") .takes_value(true) .required(true) - .help("View this entry at this store path")) + .help("View this entry at this store path") + .value_name("ID")) .arg(Arg::with_name("version") .long("version") .short("V") .takes_value(true) .required(false) - .help("View this version (youngest if not specified)")) + .help("View this version (youngest if not specified)") + .value_name("VERSION")) .arg(Arg::with_name("versions") .long("versions") @@ -71,21 +73,24 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("b") .takes_value(true) // optional, which browser .required(false) - .help("View content in $BROWSER (fails if no env variable $BROWSER)")) + .help("View content in $BROWSER (fails if no env variable $BROWSER)") + .value_name("BROWSER")) .arg(Arg::with_name("view-in-texteditor") .long("editor") .short("e") .takes_value(true) // optional, which editor .required(false) - .help("View content in $EDITOR")) + .help("View content in $EDITOR") + .value_name("EDITOR")) .arg(Arg::with_name("view-in-custom") .long("custom") .short("c") .takes_value(true) // non-optional, call-string .required(false) - .help("View content in custom program, for example 'libreoffice %e', replace '%e' with entry path")) + .help("View content in custom program, for example 'libreoffice %e', replace '%e' with entry path") + .value_name("PROGRAM")) .group(ArgGroup::with_name("viewer") .args(&["view-in-stdout", @@ -105,15 +110,15 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .short("f") .takes_value(true) // "markdown" or "textile" or "restructuredtex" .required(true) - .help("Compile from")) + .help("Compile from") + .value_name("FORMAT")) .arg(Arg::with_name("to") .long("to") .short("t") .takes_value(true) // "html" or "HTML" or ... json maybe? .required(true) - .help("Compile to")) + .help("Compile to") + .value_name("FORMAT")) ) } - - diff --git a/imagrc.toml b/imagrc.toml new file mode 100644 index 00000000..148e53d0 --- /dev/null +++ b/imagrc.toml @@ -0,0 +1,31 @@ +# This is a example configuration file for the imag suite. +# It is written in TOML + +[store] + +# Hooks which get executed right before the Store is closed. +# They get the store path as StoreId passed, so they can alter the complete +# store, so these hooks should be chosen carefully. +store-unload-hook-aspects = [ "debug" ] + +pre-create-hook-aspects = [ "debug" ] +post-create-hook-aspects = [ "debug" ] + +pre-retrieve-hook-aspects = [ "debug" ] +post-retrieve-hook-aspects = [ "debug" ] + +pre-update-hook-aspects = [ "debug" ] +post-update-hook-aspects = [ "debug" ] + +pre-delete-hook-aspects = [ "debug" ] +post-delete-hook-aspects = [ "debug" ] + +[store.aspects] + +[[aspects.debug]] +parallel = false + +[store.hooks] + +[[hooks.debug]] +aspect = "debug" diff --git a/libimagcounter/Cargo.toml b/libimagcounter/Cargo.toml index 15ec9add..da194678 100644 --- a/libimagcounter/Cargo.toml +++ b/libimagcounter/Cargo.toml @@ -4,10 +4,13 @@ version = "0.1.0" authors = ["Matthias Beyer "] [dependencies] -log = "0.3.5" +log = "0.3" toml = "0.1.25" semver = "0.2" [dependencies.libimagstore] path = "../libimagstore" +[dependencies.libimagerror] +path = "../libimagerror" + diff --git a/libimagcounter/src/counter.rs b/libimagcounter/src/counter.rs index 2285071f..a93046f7 100644 --- a/libimagcounter/src/counter.rs +++ b/libimagcounter/src/counter.rs @@ -144,12 +144,12 @@ impl<'a> Counter<'a> { } trait FromStoreId { - fn from_storeid<'a>(&'a Store, StoreId) -> Result>; + fn from_storeid(&Store, StoreId) -> Result; } impl<'a> FromStoreId for Counter<'a> { - fn from_storeid<'b>(store: &'b Store, id: StoreId) -> Result> { + fn from_storeid(store: &Store, id: StoreId) -> Result { debug!("Loading counter from storeid: '{:?}'", id); match store.retrieve(id) { Err(e) => Err(CE::new(CEK::StoreReadError, Some(Box::new(e)))), diff --git a/libimagcounter/src/error.rs b/libimagcounter/src/error.rs index f1c842f7..e8016fb6 100644 --- a/libimagcounter/src/error.rs +++ b/libimagcounter/src/error.rs @@ -1,87 +1,12 @@ -use std::error::Error; -use std::fmt::Error as FmtError; -use std::clone::Clone; -use std::fmt::{Display, Formatter}; +generate_error_module!( + generate_error_types!(CounterError, CounterErrorKind, + StoreReadError => "Store read error", + StoreWriteError => "Store write error", + HeaderTypeError => "Header type error", + HeaderFieldMissingError => "Header field missing error" + ); +); -/** - * Kind of error - */ -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum CounterErrorKind { - StoreReadError, - StoreWriteError, - HeaderTypeError, - HeaderFieldMissingError, -} - -fn counter_error_type_as_str(e: &CounterErrorKind) -> &'static str { - match e { - &CounterErrorKind::StoreReadError => "Store read error", - &CounterErrorKind::StoreWriteError => "Store write error", - &CounterErrorKind::HeaderTypeError => "Header type error", - &CounterErrorKind::HeaderFieldMissingError => "Header field missing error", - } -} - -impl Display for CounterErrorKind { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "{}", counter_error_type_as_str(self))); - Ok(()) - } - -} - -/** - * Store error type - */ -#[derive(Debug)] -pub struct CounterError { - err_type: CounterErrorKind, - cause: Option>, -} - -impl CounterError { - - /** - * Build a new CounterError from an CounterErrorKind, optionally with cause - */ - pub fn new(errtype: CounterErrorKind, cause: Option>) - -> CounterError - { - CounterError { - err_type: errtype, - cause: cause, - } - } - - /** - * Get the error type of this CounterError - */ - pub fn err_type(&self) -> CounterErrorKind { - self.err_type.clone() - } - -} - -impl Display for CounterError { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "[{}]", counter_error_type_as_str(&self.err_type.clone()))); - Ok(()) - } - -} - -impl Error for CounterError { - - fn description(&self) -> &str { - counter_error_type_as_str(&self.err_type.clone()) - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - -} +pub use self::error::CounterError; +pub use self::error::CounterErrorKind; diff --git a/libimagcounter/src/lib.rs b/libimagcounter/src/lib.rs index 77033170..0426384d 100644 --- a/libimagcounter/src/lib.rs +++ b/libimagcounter/src/lib.rs @@ -17,6 +17,7 @@ extern crate toml; #[macro_use] extern crate semver; #[macro_use] extern crate libimagstore; +#[macro_use] extern crate libimagerror; module_entry_path_mod!("counter", "0.1.0"); diff --git a/libimagdiary/Cargo.toml b/libimagdiary/Cargo.toml new file mode 100644 index 00000000..087c4922 --- /dev/null +++ b/libimagdiary/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "libimagdiary" +version = "0.1.0" +authors = ["Matthias Beyer "] + +[dependencies] +chrono = "0.2" +log = "0.3.5" +semver = "0.2" +toml = "0.1.25" +regex = "0.1" +lazy_static = "0.2" +itertools = "0.4" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimagutil] +path = "../libimagutil" + +[dependencies.libimagrt] +path = "../libimagrt" + diff --git a/libimagdiary/src/config.rs b/libimagdiary/src/config.rs new file mode 100644 index 00000000..93749289 --- /dev/null +++ b/libimagdiary/src/config.rs @@ -0,0 +1,19 @@ +use toml::Value; + +use libimagrt::runtime::Runtime; + +pub fn get_default_diary_name(rt: &Runtime) -> Option { + get_diary_config_section(rt) + .and_then(|config| { + match config.lookup("default_diary") { + Some(&Value::String(ref s)) => Some(s.clone()), + _ => None, + } + }) +} + +pub fn get_diary_config_section<'a>(rt: &'a Runtime) -> Option<&'a Value> { + rt.config() + .map(|config| config.config()) + .and_then(|config| config.lookup("diary")) +} diff --git a/libimagdiary/src/diary.rs b/libimagdiary/src/diary.rs new file mode 100644 index 00000000..40eaca88 --- /dev/null +++ b/libimagdiary/src/diary.rs @@ -0,0 +1,109 @@ +use std::cmp::Ordering; + +use libimagstore::store::Store; +use libimagstore::storeid::IntoStoreId; +use libimagerror::trace::trace_error; + +use chrono::offset::local::Local; +use chrono::Datelike; +use itertools::Itertools; +use chrono::naive::datetime::NaiveDateTime; + +use entry::Entry; +use diaryid::DiaryId; +use error::DiaryError as DE; +use error::DiaryErrorKind as DEK; +use result::Result; +use iter::DiaryEntryIterator; +use is_in_diary::IsInDiary; + +#[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, + } + } + + // create or get a new entry for today + pub fn new_entry_today(&self) -> Result { + 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) + } + + pub fn new_entry_by_id(&self, id: DiaryId) -> Result { + self.retrieve(id.with_diary_name(String::from(self.name))) + } + + pub fn retrieve(&self, id: DiaryId) -> Result { + self.store + .retrieve(id.into_storeid()) + .map(|fle| Entry::new(fle)) + .map_err(|e| DE::new(DEK::StoreWriteError, Some(Box::new(e)))) + } + + // Get an iterator for iterating over all entries + pub fn entries(&self) -> Result> { + self.store + .retrieve_for_module("diary") + .map(|iter| DiaryEntryIterator::new(self.name, self.store, iter)) + .map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e)))) + } + + pub fn delete_entry(&self, entry: Entry) -> Result<()> { + if !entry.is_in_diary(self.name) { + return Err(DE::new(DEK::EntryNotInDiary, None)); + } + let id = entry.get_location().clone(); + drop(entry); + + self.store.delete(id) + .map_err(|e| DE::new(DEK::StoreWriteError, Some(Box::new(e)))) + } + + pub fn get_youngest_entry(&self) -> Option> { + match self.entries() { + 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(); + + 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() + } + } + } + + pub fn name(&self) -> &'a str { + &self.name + } +} + diff --git a/libimagdiary/src/diaryid.rs b/libimagdiary/src/diaryid.rs new file mode 100644 index 00000000..5549ac17 --- /dev/null +++ b/libimagdiary/src/diaryid.rs @@ -0,0 +1,217 @@ +use std::convert::Into; +use std::fmt::{Display, Formatter, Error as FmtError}; + +use chrono::naive::datetime::NaiveDateTime; +use chrono::naive::time::NaiveTime; +use chrono::naive::date::NaiveDate; +use chrono::Datelike; +use chrono::Timelike; + +use libimagstore::storeid::StoreId; +use libimagstore::storeid::IntoStoreId; + +use module_path::ModuleEntryPath; + +#[derive(Debug, Clone)] +pub struct DiaryId { + name: String, + year: i32, + month: u32, + day: u32, + hour: u32, + minute: u32, +} + +impl DiaryId { + + pub fn new(name: String, y: i32, m: u32, d: u32, h: u32, min: u32) -> DiaryId { + DiaryId { + name: name, + year: y, + month: m, + day: d, + hour: h, + minute: min, + } + } + + pub fn from_datetime(diary_name: String, dt: DT) -> DiaryId { + DiaryId::new(diary_name, dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute()) + } + + pub fn diary_name(&self) -> &String { + &self.name + } + + pub fn year(&self) -> i32 { + self.year + } + + pub fn month(&self) -> u32 { + self.month + } + + pub fn day(&self) -> u32 { + self.day + } + + pub fn hour(&self) -> u32 { + self.hour + } + + pub fn minute(&self) -> u32 { + self.minute + } + + pub fn with_diary_name(mut self, name: String) -> DiaryId { + self.name = name; + self + } + + pub fn with_year(mut self, year: i32) -> DiaryId { + self.year = year; + self + } + + pub fn with_month(mut self, month: u32) -> DiaryId { + self.month = month; + self + } + + pub fn with_day(mut self, day: u32) -> DiaryId { + self.day = day; + self + } + + pub fn with_hour(mut self, hour: u32) -> DiaryId { + self.hour = hour; + self + } + + pub fn with_minute(mut self, minute: u32) -> DiaryId { + self.minute = minute; + self + } + + pub fn now(name: String) -> DiaryId { + use chrono::offset::local::Local; + + let now = Local::now(); + let now_date = now.date().naive_local(); + let now_time = now.time(); + let dt = NaiveDateTime::new(now_date, now_time); + + DiaryId::new(name, dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute()) + } + +} + +impl Default for DiaryId { + + /// Create a default DiaryId which is a diaryid for a diary named "default" with + /// time = 0000-00-00 00:00:00 + fn default() -> DiaryId { + let dt = NaiveDateTime::new(NaiveDate::from_ymd(0, 0, 0), NaiveTime::from_hms(0, 0, 0)); + DiaryId::from_datetime(String::from("default"), dt) + } +} + +impl IntoStoreId for DiaryId { + + fn into_storeid(self) -> StoreId { + let s : String = self.into(); + ModuleEntryPath::new(s).into_storeid() + } + +} + +impl Into for DiaryId { + + fn into(self) -> String { + format!("{}/{:0>4}/{:0>2}/{:0>2}/{:0>2}:{:0>2}", + self.name, self.year, self.month, self.day, self.hour, self.minute) + } + +} + +impl Display for DiaryId { + + fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { + write!(fmt, "{}/{:0>4}/{:0>2}/{:0>2}/{:0>2}:{:0>2}", + self.name, self.year, self.month, self.day, self.hour, self.minute) + } + +} + +impl Into for DiaryId { + + fn into(self) -> NaiveDateTime { + let d = NaiveDate::from_ymd(self.year, self.month, self.day); + let t = NaiveTime::from_hms(self.hour, self.minute, 0); + NaiveDateTime::new(d, t) + } + +} + +pub trait FromStoreId : Sized { + + fn from_storeid(&StoreId) -> Option; + +} + +use std::path::Component; + +fn component_to_str<'a>(com: Component<'a>) -> Option<&'a str> { + match com { + Component::Normal(s) => Some(s), + _ => None + }.and_then(|s| s.to_str()) +} + +impl FromStoreId for DiaryId { + + fn from_storeid(s: &StoreId) -> Option { + use std::str::FromStr; + + let mut cmps = s.components().rev(); + let (hour, minute) = match cmps.next().and_then(component_to_str) + .and_then(|time| { + let mut time = time.split(":"); + let hour = time.next().and_then(|s| FromStr::from_str(s).ok()); + let minute = time.next() + .and_then(|s| s.split("~").next()) + .and_then(|s| FromStr::from_str(s).ok()); + + debug!("Hour = {:?}", hour); + debug!("Minute = {:?}", minute); + + match (hour, minute) { + (Some(h), Some(m)) => Some((h, m)), + _ => None, + } + }) + { + Some(s) => s, + None => return None, + }; + + let day :Option = cmps.next().and_then(component_to_str).and_then(|s| FromStr::from_str(s).ok()); + let month :Option = cmps.next().and_then(component_to_str).and_then(|s| FromStr::from_str(s).ok()); + let year :Option = cmps.next().and_then(component_to_str).and_then(|s| FromStr::from_str(s).ok()); + let name = cmps.next().and_then(component_to_str).map(String::from); + + debug!("Day = {:?}", day); + debug!("Month = {:?}", month); + debug!("Year = {:?}", year); + debug!("Name = {:?}", name); + + let day = if day.is_none() { return None; } else { day.unwrap() }; + let month = if month.is_none() { return None; } else { month.unwrap() }; + let year = if year.is_none() { return None; } else { year.unwrap() }; + let name = if name.is_none() { return None; } else { name.unwrap() }; + + Some(DiaryId::new(name, year, month, day, hour, minute)) + } + +} + diff --git a/libimagdiary/src/entry.rs b/libimagdiary/src/entry.rs new file mode 100644 index 00000000..d5bddf9d --- /dev/null +++ b/libimagdiary/src/entry.rs @@ -0,0 +1,71 @@ +use std::ops::Deref; +use std::ops::DerefMut; + +use libimagstore::store::FileLockEntry; +use libimagrt::edit::Edit; +use libimagrt::edit::EditResult; +use libimagrt::runtime::Runtime; + +use diaryid::DiaryId; +use diaryid::FromStoreId; + +#[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 + } + +} + +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) + } + + /// 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() + } + +} + +impl<'a> Into> for Entry<'a> { + + fn into(self) -> FileLockEntry<'a> { + self.0 + } + +} + +impl<'a> From> 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) + } + +} + + diff --git a/libimagdiary/src/error.rs b/libimagdiary/src/error.rs new file mode 100644 index 00000000..53a79f42 --- /dev/null +++ b/libimagdiary/src/error.rs @@ -0,0 +1,16 @@ +generate_error_module!( + generate_error_types!(DiaryError, DiaryErrorKind, + StoreWriteError => "Error writing store", + StoreReadError => "Error reading store", + CannotFindDiary => "Cannot find diary", + CannotCreateNote => "Cannot create Note object for diary entry", + DiaryEditError => "Cannot edit diary entry", + PathConversionError => "Error while converting paths internally", + EntryNotInDiary => "Entry not in Diary", + IOError => "IO Error" + ); +); + +pub use self::error::DiaryError; +pub use self::error::DiaryErrorKind; + diff --git a/libimagdiary/src/is_in_diary.rs b/libimagdiary/src/is_in_diary.rs new file mode 100644 index 00000000..0d0cfccb --- /dev/null +++ b/libimagdiary/src/is_in_diary.rs @@ -0,0 +1,26 @@ +use std::path::PathBuf; + +use libimagstore::store::Entry; + +pub trait IsInDiary { + + fn is_in_diary(&self, name: &str) -> bool; + +} + +impl IsInDiary for Entry { + + fn is_in_diary(&self, name: &str) -> bool { + self.get_location().is_in_diary(name) + } + +} + +impl IsInDiary for PathBuf { + + fn is_in_diary(&self, name: &str) -> bool { + self.to_str().map(|s| s.contains(name)).unwrap_or(false) + } + +} + diff --git a/libimagdiary/src/iter.rs b/libimagdiary/src/iter.rs new file mode 100644 index 00000000..1b2b16e5 --- /dev/null +++ b/libimagdiary/src/iter.rs @@ -0,0 +1,111 @@ +use std::fmt::{Debug, Formatter, Error as FmtError}; +use std::result::Result as RResult; + +use libimagstore::store::Store; +use libimagstore::storeid::StoreIdIterator; + +use diaryid::DiaryId; +use diaryid::FromStoreId; +use is_in_diary::IsInDiary; +use entry::Entry as DiaryEntry; +use error::DiaryError as DE; +use error::DiaryErrorKind as DEK; +use result::Result; + +/// A iterator for iterating over diary entries +pub struct DiaryEntryIterator<'a> { + store: &'a Store, + name: &'a str, + iter: StoreIdIterator, + + year: Option, + month: Option, + day: Option, +} + +impl<'a> Debug for DiaryEntryIterator<'a> { + + fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> { + write!(fmt, "DiaryEntryIterator", + self.name, self.year, self.month, self.day) + } + +} + +impl<'a> DiaryEntryIterator<'a> { + + pub fn new(diaryname: &'a str, store: &'a Store, iter: StoreIdIterator) -> DiaryEntryIterator<'a> { + DiaryEntryIterator { + store: store, + name: diaryname, + iter: iter, + + year: None, + month: None, + day: None, + } + } + + // Filter by year, get all diary entries for this year + pub fn year(mut self, year: i32) -> DiaryEntryIterator<'a> { + self.year = Some(year); + self + } + + // Filter by month, get all diary entries for this month (every year) + pub fn month(mut self, month: u32) -> DiaryEntryIterator<'a> { + self.month = Some(month); + self + } + + // Filter by day, get all diary entries for this day (every year, every year) + pub fn day(mut self, day: u32) -> DiaryEntryIterator<'a> { + self.day = Some(day); + self + } + +} + +impl<'a> Iterator for DiaryEntryIterator<'a> { + type Item = Result>; + + fn next(&mut self) -> Option>> { + loop { + let next = self.iter.next(); + debug!("Next element: {:?}", next); + if next.is_none() { + return None; + } + let next = next.unwrap(); + + if next.is_in_diary(self.name) { + debug!("Seems to be in diary: {:?}", next); + let id = DiaryId::from_storeid(&next); + if id.is_none() { + debug!("Couldn't parse {:?} into DiaryId", next); + continue; + } + let id = id.unwrap(); + debug!("Success parsing id = {:?}", id); + + let y = match self.year { None => true, Some(y) => y == id.year() }; + let m = match self.month { None => true, Some(m) => m == id.month() }; + let d = match self.day { None => true, Some(d) => d == id.day() }; + + if y && m && d { + debug!("Return = {:?}", id); + return Some(self + .store + .retrieve(next) + .map(|fle| DiaryEntry::new(fle)) + .map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e)))) + ); + } + } else { + debug!("Not in the requested diary ({}): {:?}", self.name, next); + } + } + } + +} + diff --git a/libimagdiary/src/lib.rs b/libimagdiary/src/lib.rs new file mode 100644 index 00000000..a13dc428 --- /dev/null +++ b/libimagdiary/src/lib.rs @@ -0,0 +1,39 @@ +#![deny( + dead_code, + non_camel_case_types, + non_snake_case, + path_statements, + trivial_numeric_casts, + unstable_features, + unused_allocation, + unused_import_braces, + unused_imports, + unused_must_use, + unused_mut, + unused_qualifications, + while_true, +)] + +extern crate chrono; +#[macro_use] extern crate log; +#[macro_use] extern crate lazy_static; +extern crate semver; +extern crate toml; +extern crate regex; +extern crate itertools; + +#[macro_use] extern crate libimagstore; +#[macro_use] extern crate libimagutil; +#[macro_use] extern crate libimagerror; +extern crate libimagrt; + +module_entry_path_mod!("diary", "0.1.0"); + +pub mod config; +pub mod error; +pub mod diaryid; +pub mod diary; +pub mod is_in_diary; +pub mod entry; +pub mod iter; +pub mod result; diff --git a/libimagdiary/src/result.rs b/libimagdiary/src/result.rs new file mode 100644 index 00000000..5348c44b --- /dev/null +++ b/libimagdiary/src/result.rs @@ -0,0 +1,5 @@ +use std::result::Result as RResult; + +use error::DiaryError; + +pub type Result = RResult; diff --git a/libimagentryfilter/Cargo.toml b/libimagentryfilter/Cargo.toml index e4ff3aa0..f746a223 100644 --- a/libimagentryfilter/Cargo.toml +++ b/libimagentryfilter/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Matthias Beyer "] [dependencies] clap = "2.1.1" itertools = "0.4" -log = "0.3.4" +log = "0.3" regex = "0.1" toml = "0.1.27" semver = "0.2.1" diff --git a/libimagentryfilter/src/builtin/header/field_gt.rs b/libimagentryfilter/src/builtin/header/field_gt.rs index 6975737b..7067399d 100644 --- a/libimagentryfilter/src/builtin/header/field_gt.rs +++ b/libimagentryfilter/src/builtin/header/field_gt.rs @@ -14,15 +14,15 @@ struct EqGt { impl Predicate for EqGt { fn evaluate(&self, v: Value) -> bool { - match &self.comp { - &Value::Integer(i) => { + match self.comp { + Value::Integer(i) => { match v { Value::Integer(j) => i > j, Value::Float(f) => (i as f64) > f, _ => false, } }, - &Value::Float(f) => { + Value::Float(f) => { match v { Value::Integer(i) => f > (i as f64), Value::Float(d) => f > d, diff --git a/libimagentryfilter/src/builtin/header/field_isempty.rs b/libimagentryfilter/src/builtin/header/field_isempty.rs index a7ff4ab1..3da9360f 100644 --- a/libimagentryfilter/src/builtin/header/field_isempty.rs +++ b/libimagentryfilter/src/builtin/header/field_isempty.rs @@ -27,11 +27,11 @@ impl Filter for FieldIsEmpty { .map(|v| { match v { Some(Value::Array(a)) => a.is_empty(), - Some(Value::Boolean(_)) => false, - Some(Value::Float(_)) => false, - Some(Value::Integer(_)) => false, Some(Value::String(s)) => s.is_empty(), Some(Value::Table(t)) => t.is_empty(), + Some(Value::Boolean(_)) | + Some(Value::Float(_)) | + Some(Value::Integer(_)) => false, _ => true, } }) diff --git a/libimagentryfilter/src/builtin/header/field_istype.rs b/libimagentryfilter/src/builtin/header/field_istype.rs index 8423c0ab..5af73623 100644 --- a/libimagentryfilter/src/builtin/header/field_istype.rs +++ b/libimagentryfilter/src/builtin/header/field_istype.rs @@ -21,11 +21,11 @@ impl Type { fn matches(&self, v: &Value) -> bool { match (self, v) { - (&Type::String, &Value::String(_)) => true, - (&Type::Integer, &Value::Integer(_)) => true, - (&Type::Float, &Value::Float(_)) => true, - (&Type::Boolean, &Value::Boolean(_)) => true, - (&Type::Array, &Value::Array(_)) => true, + (&Type::String, &Value::String(_)) | + (&Type::Integer, &Value::Integer(_)) | + (&Type::Float, &Value::Float(_)) | + (&Type::Boolean, &Value::Boolean(_)) | + (&Type::Array, &Value::Array(_)) | (&Type::Table, &Value::Table(_)) => true, _ => false, } diff --git a/libimagentryfilter/src/builtin/header/field_lt.rs b/libimagentryfilter/src/builtin/header/field_lt.rs index 15558d4b..dee98476 100644 --- a/libimagentryfilter/src/builtin/header/field_lt.rs +++ b/libimagentryfilter/src/builtin/header/field_lt.rs @@ -14,15 +14,15 @@ struct EqLt { impl Predicate for EqLt { fn evaluate(&self, v: Value) -> bool { - match &self.comp { - &Value::Integer(i) => { + match self.comp { + Value::Integer(i) => { match v { Value::Integer(j) => i < j, Value::Float(f) => (i as f64) < f, _ => false, } }, - &Value::Float(f) => { + Value::Float(f) => { match v { Value::Integer(i) => f < (i as f64), Value::Float(d) => f < d, diff --git a/libimagentryfilter/src/builtin/header/version/eq.rs b/libimagentryfilter/src/builtin/header/version/eq.rs index 0e28cfb2..e6b93893 100644 --- a/libimagentryfilter/src/builtin/header/version/eq.rs +++ b/libimagentryfilter/src/builtin/header/version/eq.rs @@ -23,7 +23,7 @@ impl Filter for VersionEq { e.get_header() .read("imag.version") .map(|val| { - val.map(|v| { + val.map_or(false, |v| { match v { Value::String(s) => { match Version::parse(&s[..]) { @@ -34,7 +34,6 @@ impl Filter for VersionEq { _ => false, } }) - .unwrap_or(false) }) .unwrap_or(false) } diff --git a/libimagentryfilter/src/builtin/header/version/gt.rs b/libimagentryfilter/src/builtin/header/version/gt.rs index 119e64a8..f03b0fa9 100644 --- a/libimagentryfilter/src/builtin/header/version/gt.rs +++ b/libimagentryfilter/src/builtin/header/version/gt.rs @@ -23,7 +23,7 @@ impl Filter for VersionGt { e.get_header() .read("imag.version") .map(|val| { - val.map(|v| { + val.map_or(false, |v| { match v { Value::String(s) => { match Version::parse(&s[..]) { @@ -34,7 +34,6 @@ impl Filter for VersionGt { _ => false, } }) - .unwrap_or(false) }) .unwrap_or(false) } diff --git a/libimagentryfilter/src/builtin/header/version/lt.rs b/libimagentryfilter/src/builtin/header/version/lt.rs index 6263f6de..5be6a191 100644 --- a/libimagentryfilter/src/builtin/header/version/lt.rs +++ b/libimagentryfilter/src/builtin/header/version/lt.rs @@ -23,7 +23,7 @@ impl Filter for VersionLt { e.get_header() .read("imag.version") .map(|val| { - val.map(|v| { + val.map_or(false, |v| { match v { Value::String(s) => { match Version::parse(&s[..]) { @@ -34,7 +34,6 @@ impl Filter for VersionLt { _ => false, } }) - .unwrap_or(false) }) .unwrap_or(false) } diff --git a/libimagentrylink/Cargo.toml b/libimagentrylink/Cargo.toml index 266f9571..e4aeb4a6 100644 --- a/libimagentrylink/Cargo.toml +++ b/libimagentrylink/Cargo.toml @@ -5,12 +5,18 @@ authors = ["Matthias Beyer "] [dependencies] itertools = "0.4" -log = "0.3.4" +log = "0.3" toml = "0.1.27" semver = "0.2" -url = "0.5.5" +url = "1.1" rust-crypto = "0.2.35" [dependencies.libimagstore] path = "../libimagstore" +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimagutil] +path = "../libimagutil" + diff --git a/libimagentrylink/src/error.rs b/libimagentrylink/src/error.rs index bdf7ccb1..2802c414 100644 --- a/libimagentrylink/src/error.rs +++ b/libimagentrylink/src/error.rs @@ -1,90 +1,16 @@ -use std::error::Error; -use std::fmt::Error as FmtError; -use std::fmt::{Display, Formatter}; +generate_error_module!( + generate_error_types!(LinkError, LinkErrorKind, + EntryHeaderReadError => "Error while reading an entry header", + EntryHeaderWriteError => "Error while writing an entry header", + ExistingLinkTypeWrong => "Existing link entry has wrong type", + LinkTargetDoesNotExist => "Link target does not exist in the store", + InternalConversionError => "Error while converting values internally", + InvalidUri => "URI is not valid", + StoreReadError => "Store read error", + StoreWriteError => "Store write error" + ); +); -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum LinkErrorKind { - EntryHeaderReadError, - EntryHeaderWriteError, - ExistingLinkTypeWrong, - LinkTargetDoesNotExist, - InternalConversionError, - InvalidUri, - StoreReadError, - StoreWriteError, -} +pub use self::error::LinkError; +pub use self::error::LinkErrorKind; -fn link_error_type_as_str(e: &LinkErrorKind) -> &'static str { - match e { - &LinkErrorKind::EntryHeaderReadError - => "Error while reading an entry header", - - &LinkErrorKind::EntryHeaderWriteError - => "Error while writing an entry header", - - &LinkErrorKind::ExistingLinkTypeWrong - => "Existing link entry has wrong type", - - &LinkErrorKind::LinkTargetDoesNotExist - => "Link target does not exist in the store", - - &LinkErrorKind::InternalConversionError - => "Error while converting values internally", - - &LinkErrorKind::InvalidUri - => "URI is not valid", - - &LinkErrorKind::StoreReadError - => "Store read error", - - &LinkErrorKind::StoreWriteError - => "Store write error", - } -} - -impl Display for LinkErrorKind { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "{}", link_error_type_as_str(self))); - Ok(()) - } - -} - -#[derive(Debug)] -pub struct LinkError { - kind: LinkErrorKind, - cause: Option>, -} - -impl LinkError { - - pub fn new(errtype: LinkErrorKind, cause: Option>) -> LinkError { - LinkError { - kind: errtype, - cause: cause, - } - } - -} - -impl Display for LinkError { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "[{}]", link_error_type_as_str(&self.kind))); - Ok(()) - } - -} - -impl Error for LinkError { - - fn description(&self) -> &str { - link_error_type_as_str(&self.kind) - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - -} diff --git a/libimagentrylink/src/external.rs b/libimagentrylink/src/external.rs index e99abf42..01f6badb 100644 --- a/libimagentrylink/src/external.rs +++ b/libimagentrylink/src/external.rs @@ -31,8 +31,8 @@ use url::Url; use crypto::sha1::Sha1; use crypto::digest::Digest; -/// "Link" Type, just an abstraction over FileLockEntry to have some convenience internally. -struct Link<'a> { +/// "Link" Type, just an abstraction over `FileLockEntry` to have some convenience internally. +pub struct Link<'a> { link: FileLockEntry<'a> } @@ -45,28 +45,18 @@ impl<'a> Link<'a> { /// For interal use only. Load an Link from a store id, if this is actually a Link fn retrieve(store: &'a Store, id: StoreId) -> Result>> { store.retrieve(id) - .map(|fle| { - if let Some(_) = Link::get_link_uri_from_filelockentry(&fle) { - Some(Link { - link: fle - }) - } else { - None - } - }) + .map(|fle| Link::get_link_uri_from_filelockentry(&fle).map(|_| Link { link: fle })) .map_err(|e| LE::new(LEK::StoreReadError, Some(Box::new(e)))) } - /// Get a link Url object from a FileLockEntry, ignore errors. + /// Get a link Url object from a `FileLockEntry`, ignore errors. fn get_link_uri_from_filelockentry(file: &FileLockEntry<'a>) -> Option { file.get_header() .read("imag.content.uri") .ok() - .and_then(|opt| { - match opt { - Some(Value::String(s)) => Url::parse(&s[..]).ok(), - _ => None - } + .and_then(|opt| match opt { + Some(Value::String(s)) => Url::parse(&s[..]).ok(), + _ => None }) } @@ -78,7 +68,7 @@ impl<'a> Link<'a> { match opt { Ok(Some(Value::String(s))) => { Url::parse(&s[..]) - .map(|s| Some(s)) + .map(Some) .map_err(|e| LE::new(LEK::EntryHeaderReadError, Some(Box::new(e)))) }, Ok(None) => Ok(None), @@ -105,7 +95,7 @@ pub trait ExternalLinker : InternalLinker { } /// Check whether the StoreId starts with `/link/external/` -fn is_link_store_id(id: &StoreId) -> bool { +pub fn is_external_link_storeid(id: &StoreId) -> bool { debug!("Checking whether this is a /link/external/*: '{:?}'", id); id.starts_with("/link/external/") } @@ -115,7 +105,7 @@ fn get_external_link_from_file(entry: &FileLockEntry) -> Result { .ok_or(LE::new(LEK::StoreReadError, None)) } -/// Implement ExternalLinker for Entry, hiding the fact that there is no such thing as an external +/// 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. impl ExternalLinker for Entry { @@ -129,7 +119,7 @@ impl ExternalLinker for Entry { .map(|vect| { debug!("Getting external links"); vect.into_iter() - .filter(is_link_store_id) + .filter(is_external_link_storeid) .map(|id| { debug!("Retrieving entry for id: '{:?}'", id); match store.retrieve(id.clone()) { @@ -156,7 +146,7 @@ impl ExternalLinker for Entry { for link in links { // for all links let hash = { let mut s = Sha1::new(); - s.input_str(&link.serialize()[..]); + s.input_str(&link.as_str()[..]); s.result_str() }; let file_id = ModuleEntryPath::new(format!("external/{}", hash)).into_storeid(); @@ -189,7 +179,7 @@ impl ExternalLinker for Entry { Err(e) => return Err(LE::new(LEK::StoreWriteError, Some(Box::new(e)))), }; - let v = Value::String(link.serialize()); + let v = Value::String(link.into_string()); debug!("setting URL = '{:?}", v); table.insert(String::from("url"), v); @@ -231,7 +221,7 @@ impl ExternalLinker for Entry { .and_then(|links| { debug!("Removing link = '{:?}' from links = {:?}", link, links); let links = links.into_iter() - .filter(|l| l.serialize() != link.serialize()) + .filter(|l| l.as_str() != link.as_str()) .collect(); self.set_external_links(store, links) }) diff --git a/libimagentrylink/src/internal.rs b/libimagentrylink/src/internal.rs index a410349a..02be0e7e 100644 --- a/libimagentrylink/src/internal.rs +++ b/libimagentrylink/src/internal.rs @@ -4,8 +4,9 @@ use libimagstore::storeid::StoreId; use libimagstore::store::Entry; use libimagstore::store::EntryHeader; use libimagstore::store::Result as StoreResult; +use libimagerror::into::IntoError; -use error::{LinkError, LinkErrorKind}; +use error::LinkErrorKind as LEK; use result::Result; use toml::Value; @@ -50,7 +51,7 @@ impl InternalLinker for Entry { let new_links = links_into_values(new_links); if new_links.iter().any(|o| o.is_none()) { - return Err(LinkError::new(LinkErrorKind::InternalConversionError, None)); + return Err(LEK::InternalConversionError.into()); } let new_links = new_links.into_iter().map(|o| o.unwrap()).collect(); process_rw_result(self.get_header_mut().set("imag.links", Value::Array(new_links))) @@ -92,9 +93,9 @@ impl InternalLinker for Entry { fn links_into_values(links: Vec) -> Vec> { links .into_iter() - .map(|s| s.to_str().map(|s| String::from(s))) + .map(|s| s.to_str().map(String::from)) .unique() - .map(|elem| elem.map(|s| Value::String(s))) + .map(|elem| elem.map(Value::String)) .sorted_by(|a, b| { match (a, b) { (&Some(Value::String(ref a)), &Some(Value::String(ref b))) => Ord::cmp(a, b), @@ -109,7 +110,7 @@ fn rewrite_links(header: &mut EntryHeader, links: Vec) -> Result<()> { if links.iter().any(|o| o.is_none()) { // if any type convert failed we fail as well - Err(LinkError::new(LinkErrorKind::InternalConversionError, None)) + Err(LEK::InternalConversionError.into()) } else { // I know it is ugly let links = links.into_iter().map(|opt| opt.unwrap()).collect(); @@ -126,7 +127,7 @@ fn add_foreign_link(target: &mut Entry, from: StoreId) -> Result<()> { links.push(from); let links = links_into_values(links); if links.iter().any(|o| o.is_none()) { - Err(LinkError::new(LinkErrorKind::InternalConversionError, None)) + Err(LEK::InternalConversionError.into()) } else { let links = links.into_iter().map(|opt| opt.unwrap()).collect(); process_rw_result(target.get_header_mut().set("imag.links", Value::Array(links))) @@ -136,34 +137,26 @@ fn add_foreign_link(target: &mut Entry, from: StoreId) -> Result<()> { } fn process_rw_result(links: StoreResult>) -> Result> { - if links.is_err() { - debug!("RW action on store failed. Generating LinkError"); - let lerr = LinkError::new(LinkErrorKind::EntryHeaderReadError, - Some(Box::new(links.unwrap_err()))); - return Err(lerr); - } - let links = links.unwrap(); - - if links.is_none() { - debug!("We got no value from the header!"); - return Ok(vec![]) - } - let links = links.unwrap(); - - let links = { - match links { - Value::Array(a) => a, - _ => { - debug!("We expected an Array for the links, but there was a non-Array!"); - return Err(LinkError::new(LinkErrorKind::ExistingLinkTypeWrong, None)); - }, + let links = match links { + Err(e) => { + debug!("RW action on store failed. Generating LinkError"); + return Err(LEK::EntryHeaderReadError.into_error_with_cause(Box::new(e))) + }, + Ok(None) => { + debug!("We got no value from the header!"); + return Ok(vec![]) + }, + Ok(Some(Value::Array(l))) => l, + Ok(Some(_)) => { + debug!("We expected an Array for the links, but there was a non-Array!"); + return Err(LEK::ExistingLinkTypeWrong.into()); } }; - if !links.iter().all(|l| match l { &Value::String(_) => true, _ => false }) { + if !links.iter().all(|l| is_match!(*l, Value::String(_))) { debug!("At least one of the Values which were expected in the Array of links is a non-String!"); debug!("Generating LinkError"); - return Err(LinkError::new(LinkErrorKind::ExistingLinkTypeWrong, None)); + return Err(LEK::ExistingLinkTypeWrong.into()); } let links : Vec = links.into_iter() diff --git a/libimagentrylink/src/lib.rs b/libimagentrylink/src/lib.rs index 448bb853..4f3b6ee7 100644 --- a/libimagentrylink/src/lib.rs +++ b/libimagentrylink/src/lib.rs @@ -20,6 +20,8 @@ extern crate url; extern crate crypto; #[macro_use] extern crate libimagstore; +#[macro_use] extern crate libimagerror; +#[macro_use] extern crate libimagutil; module_entry_path_mod!("links", "0.1.0"); diff --git a/libimagentrylist/Cargo.toml b/libimagentrylist/Cargo.toml index 8093905d..462331c7 100644 --- a/libimagentrylist/Cargo.toml +++ b/libimagentrylist/Cargo.toml @@ -5,9 +5,12 @@ authors = ["Matthias Beyer "] [dependencies] clap = "2.1.1" -log = "0.3.5" +log = "0.3" toml = "0.1.25" [dependencies.libimagstore] path = "../libimagstore" +[dependencies.libimagerror] +path = "../libimagerror" + diff --git a/libimagentrylist/src/error.rs b/libimagentrylist/src/error.rs index f4b3c159..2cd5cde5 100644 --- a/libimagentrylist/src/error.rs +++ b/libimagentrylist/src/error.rs @@ -1,85 +1,12 @@ -use std::error::Error; -use std::fmt::Error as FmtError; -use std::clone::Clone; -use std::fmt::{Display, Formatter}; +generate_error_module!( + generate_error_types!(ListError, ListErrorKind, + FormatError => "FormatError", + EntryError => "EntryError", + IterationError => "IterationError", + CLIError => "No CLI subcommand for listing entries" + ); +); -/** - * Kind of error - */ -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum ListErrorKind { - FormatError, - EntryError, - IterationError, - CLIError, -} - -fn counter_error_type_as_str(err: &ListErrorKind) -> &'static str{ - match err { - &ListErrorKind::FormatError => "FormatError", - &ListErrorKind::EntryError => "EntryError", - &ListErrorKind::IterationError => "IterationError", - &ListErrorKind::CLIError => "No CLI subcommand for listing entries", - } -} - -impl Display for ListErrorKind { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "{}", counter_error_type_as_str(self))); - Ok(()) - } - -} - -/** - * Store error type - */ -#[derive(Debug)] -pub struct ListError { - err_type: ListErrorKind, - cause: Option>, -} - -impl ListError { - - /** - * Build a new ListError from an ListErrorKind, optionally with cause - */ - pub fn new(errtype: ListErrorKind, cause: Option>) -> ListError { - ListError { - err_type: errtype, - cause: cause, - } - } - - /** - * Get the error type of this ListError - */ - pub fn err_type(&self) -> ListErrorKind { - self.err_type.clone() - } - -} - -impl Display for ListError { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "[{}]", counter_error_type_as_str(&self.err_type.clone()))); - Ok(()) - } - -} - -impl Error for ListError { - - fn description(&self) -> &str { - counter_error_type_as_str(&self.err_type.clone()) - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - -} +pub use self::error::ListError; +pub use self::error::ListErrorKind; diff --git a/libimagentrylist/src/lib.rs b/libimagentrylist/src/lib.rs index b364e4d9..082b6fa3 100644 --- a/libimagentrylist/src/lib.rs +++ b/libimagentrylist/src/lib.rs @@ -19,6 +19,7 @@ extern crate clap; extern crate toml; extern crate libimagstore; +#[macro_use] extern crate libimagerror; pub mod cli; pub mod error; diff --git a/libimagentrylist/src/listers/core.rs b/libimagentrylist/src/listers/core.rs index 8c7ccf72..50913258 100644 --- a/libimagentrylist/src/listers/core.rs +++ b/libimagentrylist/src/listers/core.rs @@ -27,12 +27,19 @@ impl<'a> Lister for CoreLister<'a> { use error::ListError as LE; use error::ListErrorKind as LEK; - entries.fold(Ok(()), |accu, entry| { - accu.and_then(|_| { - write!(stdout(), "{:?}\n", (self.lister)(&entry)) - .map_err(|e| LE::new(LEK::FormatError, Some(Box::new(e)))) - }) - }) + debug!("Called list()"); + let (r, n) = entries + .fold((Ok(()), 0), |(accu, i), entry| { + debug!("fold({:?}, {:?})", accu, entry); + let r = accu.and_then(|_| { + debug!("Listing Entry: {:?}", entry); + write!(stdout(), "{:?}\n", (self.lister)(&entry)) + .map_err(|e| LE::new(LEK::FormatError, Some(Box::new(e)))) + }); + (r, i + 1) + }); + debug!("Iterated over {} entries", n); + r } } diff --git a/libimagentrylist/src/listers/path.rs b/libimagentrylist/src/listers/path.rs index 0f1deac5..e3602770 100644 --- a/libimagentrylist/src/listers/path.rs +++ b/libimagentrylist/src/listers/path.rs @@ -32,7 +32,7 @@ impl Lister for PathLister { if self.absolute { pb.canonicalize().map_err(|e| LE::new(LEK::FormatError, Some(Box::new(e)))) } else { - Ok(pb) + Ok(pb.into()) } }) .and_then(|pb| { diff --git a/libimagentrymarkdown/Cargo.toml b/libimagentrymarkdown/Cargo.toml new file mode 100644 index 00000000..c752ccc7 --- /dev/null +++ b/libimagentrymarkdown/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "libimagentrymarkdown" +version = "0.1.0" +authors = ["Matthias Beyer "] + +[dependencies] +log = "0.3" +hoedown = "5.0.0" +crossbeam = "0.2" +url = "1.1" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagerror] +path = "../libimagerror" + diff --git a/libimagentrymarkdown/README.md b/libimagentrymarkdown/README.md new file mode 100644 index 00000000..0f858534 --- /dev/null +++ b/libimagentrymarkdown/README.md @@ -0,0 +1,9 @@ +# libimagentrymarkdown + +Helper crate to add useful functionality in a wrapper around +[hoedown](https://crates.io/crates/hoedown) for imag. + +Adds functionality to extract links, parse content into HTML and other things +which might be useful for markdown rendering in imag. + + diff --git a/libimagentrymarkdown/src/error.rs b/libimagentrymarkdown/src/error.rs new file mode 100644 index 00000000..3e42e21b --- /dev/null +++ b/libimagentrymarkdown/src/error.rs @@ -0,0 +1,11 @@ +generate_error_module!( + generate_error_types!(MarkdownError, MarkdownErrorKind, + MarkdownRenderError => "Markdown render error", + LinkParsingError => "Link parsing error" + ); +); + +pub use self::error::MarkdownError; +pub use self::error::MarkdownErrorKind; + + diff --git a/libimagentrymarkdown/src/html.rs b/libimagentrymarkdown/src/html.rs new file mode 100644 index 00000000..f9d251a8 --- /dev/null +++ b/libimagentrymarkdown/src/html.rs @@ -0,0 +1,82 @@ +use hoedown::{Markdown, Html as MdHtml}; +use hoedown::renderer::html::Flags as HtmlFlags; +use hoedown::renderer::Render; + +use result::Result; +use error::MarkdownErrorKind; +use libimagerror::into::IntoError; + +pub type HTML = String; + +pub fn to_html(buffer: &str) -> Result { + let md = Markdown::new(buffer); + let mut html = MdHtml::new(HtmlFlags::empty(), 0); + html.render(&md) + .to_str() + .map(String::from) + .map_err(Box::new) + .map_err(|e| MarkdownErrorKind::MarkdownRenderError.into_error_with_cause(e)) +} + +pub mod iter { + use result::Result; + use libimagstore::store::Entry; + use super::HTML; + use super::to_html; + + pub struct ToHtmlIterator> { + i: I + } + + impl> ToHtmlIterator { + + fn new(i: I) -> ToHtmlIterator { + ToHtmlIterator { i: i } + } + + } + + impl> Iterator for ToHtmlIterator { + type Item = Result; + + fn next(&mut self) -> Option { + self.i.next().map(|entry| to_html(&entry.get_content()[..])) + } + + } + + impl> From for ToHtmlIterator { + + fn from(obj: I) -> ToHtmlIterator { + ToHtmlIterator::new(obj) + } + + } + + + /// Iterate over `(Entry, Result)` tuples + pub struct WithHtmlIterator> { + i: I + } + + impl> WithHtmlIterator { + + fn new(i: I) -> WithHtmlIterator { + WithHtmlIterator { i: i } + } + + } + + impl> Iterator for WithHtmlIterator { + type Item = (Entry, Result); + + fn next(&mut self) -> Option { + self.i.next().map(|entry| { + let html = to_html(&entry.get_content()[..]); + (entry, html) + }) + } + + } + +} diff --git a/libimagentrymarkdown/src/lib.rs b/libimagentrymarkdown/src/lib.rs new file mode 100644 index 00000000..0f130c96 --- /dev/null +++ b/libimagentrymarkdown/src/lib.rs @@ -0,0 +1,12 @@ +#[macro_use] extern crate log; +extern crate crossbeam; +extern crate hoedown; +extern crate url; +extern crate libimagstore; +#[macro_use] extern crate libimagerror; + +pub mod error; +pub mod html; +pub mod link; +pub mod result; + diff --git a/libimagentrymarkdown/src/link.rs b/libimagentrymarkdown/src/link.rs new file mode 100644 index 00000000..8ac3500c --- /dev/null +++ b/libimagentrymarkdown/src/link.rs @@ -0,0 +1,143 @@ +use error::MarkdownErrorKind as MEK; +use result::Result; + +use hoedown::renderer::Render; +use hoedown::Buffer; +use hoedown::Markdown; +use url::Url; + +use libimagerror::into::IntoError; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Link { + pub title: String, + pub link: String, +} + +impl Link { + + /// Translate a `Link` into a `UrlLink` + fn into_urllink(self) -> Result { + Url::parse(&self.link[..]) + .map(move |link| UrlLink { title: self.title, link: link, }) + .map_err(Box::new) + .map_err(|e| MEK::LinkParsingError.into_error_with_cause(e)) + } + +} + +pub struct UrlLink { + pub title: String, + pub link: Url, +} + +struct LinkExtractor { + links: Vec, +} + +impl LinkExtractor { + + pub fn new() -> LinkExtractor { + LinkExtractor { links: vec![] } + } + + pub fn links(self) -> Vec { + self.links + } + +} + +impl Render for LinkExtractor { + + fn link(&mut self, + _: &mut Buffer, + content: Option<&Buffer>, + link: Option<&Buffer>, + _: Option<&Buffer>) + -> bool + { + let link = link.and_then(|l| l.to_str().ok()).map(String::from); + let content = content.and_then(|l| l.to_str().ok()).map(String::from); + + match (link, content) { + (Some(link), Some(content)) => { + self.links.push(Link { link: link, title: content }); + false + }, + + (_, _) => { + false + }, + } + + } + +} + +pub fn extract_links(buf: &str) -> Vec { + let mut le = LinkExtractor::new(); + le.render(&Markdown::new(buf)); + le.links() +} + +#[cfg(test)] +mod test { + use super::{Link, extract_links}; + + #[test] + fn test_one_link() { + let testtext = "Some [example text](http://example.com)."; + + let exp = Link { + title: String::from("example text"), + link: String::from("http://example.com"), + }; + + let mut links = extract_links(testtext); + assert_eq!(1, links.len()); + assert_eq!(exp, links.pop().unwrap()) + } + + #[test] + fn test_two_similar_links() { + let testtext = r#" +Some [example text](http://example.com). +Some more [example text](http://example.com). + "#; + + let exp = Link { + title: String::from("example text"), + link: String::from("http://example.com"), + }; + + let mut links = extract_links(&testtext[..]); + assert_eq!(2, links.len()); + assert_eq!(exp, links.pop().unwrap()); + assert_eq!(exp, links.pop().unwrap()); + } + + #[test] + fn test_two_links() { + let testtext = r#" +Some [example text](http://example.com). +Some more [foo](http://example.com/foo). + "#; + + let exp1 = Link { + title: String::from("example text"), + link: String::from("http://example.com"), + }; + + let exp2 = Link { + title: String::from("foo"), + link: String::from("http://example.com/foo"), + }; + + let mut links = extract_links(&testtext[..]); + assert_eq!(2, links.len()); + assert_eq!(exp2, links.pop().unwrap()); + assert_eq!(exp1, links.pop().unwrap()); + } + +} + diff --git a/libimagentrymarkdown/src/result.rs b/libimagentrymarkdown/src/result.rs new file mode 100644 index 00000000..55aa68e5 --- /dev/null +++ b/libimagentrymarkdown/src/result.rs @@ -0,0 +1,6 @@ +use std::result::Result as RResult; + +use error::MarkdownError; + +pub type Result = RResult; + diff --git a/libimagentryselect/Cargo.toml b/libimagentryselect/Cargo.toml new file mode 100644 index 00000000..ccd52838 --- /dev/null +++ b/libimagentryselect/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "libimagentryselect" +version = "0.1.0" +authors = ["Matthias Beyer "] + +[dependencies] +clap = "2" +log = "0.3" +interactor = "0.1" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagerror] +path = "../libimagerror" + diff --git a/libimagentryselect/src/lib.rs b/libimagentryselect/src/lib.rs new file mode 100644 index 00000000..182aecdb --- /dev/null +++ b/libimagentryselect/src/lib.rs @@ -0,0 +1,9 @@ +extern crate clap; +extern crate log; +extern crate interactor; + +extern crate libimagstore; +extern crate libimagerror; + +pub mod ui; + diff --git a/libimagentryselect/src/ui.rs b/libimagentryselect/src/ui.rs new file mode 100644 index 00000000..c8c524d5 --- /dev/null +++ b/libimagentryselect/src/ui.rs @@ -0,0 +1,53 @@ +use std::path::PathBuf; + +use clap::{Arg, ArgMatches}; + +use libimagstore::storeid::StoreId; +use libimagerror::trace::trace_error; + +pub fn id_argument<'a, 'b>() -> Arg<'a, 'b> { + Arg::with_name(id_argument_name()) + .short(id_argument_short()) + .long(id_argument_long()) + .takes_value(true) + .multiple(true) + .help("Specify the Store-Id") +} + +pub fn id_argument_name() -> &'static str { + "id-argument" +} + +pub fn id_argument_short() -> &'static str { + "i" +} + +pub fn id_argument_long() -> &'static str { + "id" +} + +pub fn get_id(matches: &ArgMatches) -> Option> { + matches.values_of(id_argument_name()) + .map(|vals| { + vals.into_iter() + .map(String::from) + .map(StoreId::from) + .collect() + }) +} + +pub fn get_or_select_id(matches: &ArgMatches, store_path: &PathBuf) -> Option> { + use interactor::{pick_file, default_menu_cmd}; + + get_id(matches).or_else(|| { + match pick_file(default_menu_cmd, store_path.clone()) { + Err(e) => { + trace_error(&e); + None + }, + + Ok(p) => Some(vec![StoreId::from(p)]), + } + }) +} + diff --git a/libimagentrytag/Cargo.toml b/libimagentrytag/Cargo.toml index 9b5d0b64..64d62192 100644 --- a/libimagentrytag/Cargo.toml +++ b/libimagentrytag/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Matthias Beyer "] [dependencies] clap = "2.1.1" -log = "0.3.5" +log = "0.3" regex = "0.1" toml = "0.1.25" itertools = "0.4" @@ -13,3 +13,9 @@ itertools = "0.4" [dependencies.libimagstore] path = "../libimagstore" +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimagutil] +path = "../libimagutil" + diff --git a/libimagentrytag/src/error.rs b/libimagentrytag/src/error.rs index 63d149e4..45895019 100644 --- a/libimagentrytag/src/error.rs +++ b/libimagentrytag/src/error.rs @@ -1,69 +1,12 @@ -use std::error::Error; -use std::fmt::Error as FmtError; -use std::clone::Clone; -use std::fmt::{Display, Formatter}; +generate_error_module!( + generate_error_types!(TagError, TagErrorKind, + TagTypeError => "Entry Header Tag Type wrong", + HeaderReadError => "Error while reading entry header", + HeaderWriteError => "Error while writing entry header", + NotATag => "String is not a tag" + ); +); -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum TagErrorKind { - TagTypeError, - HeaderReadError, - HeaderWriteError, - NotATag, -} - -fn tag_error_type_as_str(e: &TagErrorKind) -> &'static str { - match e { - &TagErrorKind::TagTypeError => "Entry Header Tag Type wrong", - &TagErrorKind::HeaderReadError => "Error while reading entry header", - &TagErrorKind::HeaderWriteError => "Error while writing entry header", - &TagErrorKind::NotATag => "String is not a tag", - } -} - -impl Display for TagErrorKind { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "{}", tag_error_type_as_str(self))); - Ok(()) - } - -} - -#[derive(Debug)] -pub struct TagError { - kind: TagErrorKind, - cause: Option>, -} - -impl TagError { - - pub fn new(errtype: TagErrorKind, cause: Option>) -> TagError { - TagError { - kind: errtype, - cause: cause, - } - } - -} - -impl Display for TagError { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "[{}]", tag_error_type_as_str(&self.kind.clone()))); - Ok(()) - } - -} - -impl Error for TagError { - - fn description(&self) -> &str { - tag_error_type_as_str(&self.kind.clone()) - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - -} +pub use self::error::TagError; +pub use self::error::TagErrorKind; diff --git a/libimagentrytag/src/exec.rs b/libimagentrytag/src/exec.rs index e9603d87..8ea45142 100644 --- a/libimagentrytag/src/exec.rs +++ b/libimagentrytag/src/exec.rs @@ -7,22 +7,20 @@ use tagable::*; use ui::{get_add_tags, get_remove_tags}; pub fn exec_cli_for_entry(matches: &ArgMatches, entry: &mut FileLockEntry) -> Result<()> { - match get_add_tags(matches) { - Some(ts) => for t in ts { + if let Some(ts) = get_add_tags(matches) { + for t in ts { if let Err(e) = entry.add_tag(t) { return Err(e); } - }, - None => { }, + } } - match get_remove_tags(matches) { - Some(ts) => for t in ts { + if let Some(ts) = get_remove_tags(matches) { + for t in ts { if let Err(e) = entry.remove_tag(t) { return Err(e); } - }, - None => { }, + } } Ok(()) diff --git a/libimagentrytag/src/lib.rs b/libimagentrytag/src/lib.rs index d90f5fa5..87e4fe87 100644 --- a/libimagentrytag/src/lib.rs +++ b/libimagentrytag/src/lib.rs @@ -19,6 +19,8 @@ extern crate regex; extern crate toml; extern crate libimagstore; +#[macro_use] extern crate libimagerror; +#[macro_use] extern crate libimagutil; pub mod error; pub mod exec; diff --git a/libimagentrytag/src/tag.rs b/libimagentrytag/src/tag.rs index cba2322e..ec72161c 100644 --- a/libimagentrytag/src/tag.rs +++ b/libimagentrytag/src/tag.rs @@ -1 +1,2 @@ pub type Tag = String; +pub type TagSlice<'a> = &'a str; diff --git a/libimagentrytag/src/tagable.rs b/libimagentrytag/src/tagable.rs index 4dbe4efb..c9584f8a 100644 --- a/libimagentrytag/src/tagable.rs +++ b/libimagentrytag/src/tagable.rs @@ -4,10 +4,11 @@ use std::ops::DerefMut; use itertools::Itertools; use libimagstore::store::{Entry, EntryHeader, FileLockEntry}; +use libimagerror::into::IntoError; -use error::{TagError, TagErrorKind}; +use error::TagErrorKind; use result::Result; -use tag::Tag; +use tag::{Tag, TagSlice}; use util::is_tag; use toml::Value; @@ -15,13 +16,13 @@ use toml::Value; pub trait Tagable { fn get_tags(&self) -> Result>; - fn set_tags(&mut self, ts: Vec) -> Result<()>; + fn set_tags(&mut self, ts: &[Tag]) -> Result<()>; fn add_tag(&mut self, t: Tag) -> Result<()>; fn remove_tag(&mut self, t: Tag) -> Result<()>; - fn has_tag(&self, t: &Tag) -> Result; - fn has_tags(&self, ts: &Vec) -> Result; + fn has_tag(&self, t: TagSlice) -> Result; + fn has_tags(&self, ts: &[Tag]) -> Result; } @@ -31,20 +32,20 @@ impl Tagable for EntryHeader { let tags = self.read("imag.tags"); if tags.is_err() { let kind = TagErrorKind::HeaderReadError; - return Err(TagError::new(kind, Some(Box::new(tags.unwrap_err())))); + return Err(kind.into_error_with_cause(Box::new(tags.unwrap_err()))); } let tags = tags.unwrap(); match tags { Some(Value::Array(tags)) => { - if !tags.iter().all(|t| match t { &Value::String(_) => true, _ => false }) { - return Err(TagError::new(TagErrorKind::TagTypeError, None)); + if !tags.iter().all(|t| is_match!(*t, Value::String(_))) { + return Err(TagErrorKind::TagTypeError.into()); } - if tags.iter().any(|t| match t { - &Value::String(ref s) => !is_tag(&s), + if tags.iter().any(|t| match *t { + Value::String(ref s) => !is_tag(s), _ => unreachable!()}) { - return Err(TagError::new(TagErrorKind::NotATag, None)); + return Err(TagErrorKind::NotATag.into()); } Ok(tags.iter() @@ -58,32 +59,33 @@ impl Tagable for EntryHeader { .collect()) }, None => Ok(vec![]), - _ => Err(TagError::new(TagErrorKind::TagTypeError, None)), + _ => Err(TagErrorKind::TagTypeError.into()), } } - fn set_tags(&mut self, ts: Vec) -> Result<()> { + fn set_tags(&mut self, ts: &[Tag]) -> Result<()> { if ts.iter().any(|tag| !is_tag(tag)) { debug!("Not a tag: '{}'", ts.iter().filter(|t| !is_tag(t)).next().unwrap()); - return Err(TagError::new(TagErrorKind::NotATag, None)); + return Err(TagErrorKind::NotATag.into()); } let a = ts.iter().unique().map(|t| Value::String(t.clone())).collect(); self.set("imag.tags", Value::Array(a)) .map(|_| ()) - .map_err(|e| TagError::new(TagErrorKind::HeaderWriteError, Some(Box::new(e)))) + .map_err(Box::new) + .map_err(|e| TagErrorKind::HeaderWriteError.into_error_with_cause(e)) } fn add_tag(&mut self, t: Tag) -> Result<()> { if !is_tag(&t) { debug!("Not a tag: '{}'", t); - return Err(TagError::new(TagErrorKind::NotATag, None)); + return Err(TagErrorKind::NotATag.into()); } self.get_tags() .map(|mut tags| { tags.push(t); - self.set_tags(tags.into_iter().unique().collect()) + self.set_tags(&tags.into_iter().unique().collect::>()[..]) }) .map(|_| ()) } @@ -91,40 +93,40 @@ impl Tagable for EntryHeader { fn remove_tag(&mut self, t: Tag) -> Result<()> { if !is_tag(&t) { debug!("Not a tag: '{}'", t); - return Err(TagError::new(TagErrorKind::NotATag, None)); + return Err(TagErrorKind::NotATag.into()); } self.get_tags() .map(|mut tags| { tags.retain(|tag| tag.clone() != t); - self.set_tags(tags) + self.set_tags(&tags[..]) }) .map(|_| ()) } - fn has_tag(&self, t: &Tag) -> Result { + fn has_tag(&self, t: TagSlice) -> Result { let tags = self.read("imag.tags"); if tags.is_err() { let kind = TagErrorKind::HeaderReadError; - return Err(TagError::new(kind, Some(Box::new(tags.unwrap_err())))); + return Err(kind.into_error_with_cause(Box::new(tags.unwrap_err()))); } let tags = tags.unwrap(); - if !tags.iter().all(|t| match t { &Value::String(_) => true, _ => false }) { - return Err(TagError::new(TagErrorKind::TagTypeError, None)); + if !tags.iter().all(|t| is_match!(*t, Value::String(_))) { + return Err(TagErrorKind::TagTypeError.into()); } Ok(tags .iter() .any(|tag| { - match tag { - &Value::String(ref s) => { s == t }, + match *tag { + Value::String(ref s) => { s == t }, _ => unreachable!() } })) } - fn has_tags(&self, tags: &Vec) -> Result { + fn has_tags(&self, tags: &[Tag]) -> Result { let mut result = true; for tag in tags { let check = self.has_tag(tag); @@ -147,7 +149,7 @@ impl Tagable for Entry { self.get_header().get_tags() } - fn set_tags(&mut self, ts: Vec) -> Result<()> { + fn set_tags(&mut self, ts: &[Tag]) -> Result<()> { self.get_header_mut().set_tags(ts) } @@ -159,11 +161,11 @@ impl Tagable for Entry { self.get_header_mut().remove_tag(t) } - fn has_tag(&self, t: &Tag) -> Result { + fn has_tag(&self, t: TagSlice) -> Result { self.get_header().has_tag(t) } - fn has_tags(&self, ts: &Vec) -> Result { + fn has_tags(&self, ts: &[Tag]) -> Result { self.get_header().has_tags(ts) } @@ -175,7 +177,7 @@ impl<'a> Tagable for FileLockEntry<'a> { self.deref().get_tags() } - fn set_tags(&mut self, ts: Vec) -> Result<()> { + fn set_tags(&mut self, ts: &[Tag]) -> Result<()> { self.deref_mut().set_tags(ts) } @@ -187,11 +189,11 @@ impl<'a> Tagable for FileLockEntry<'a> { self.deref_mut().remove_tag(t) } - fn has_tag(&self, t: &Tag) -> Result { + fn has_tag(&self, t: TagSlice) -> Result { self.deref().has_tag(t) } - fn has_tags(&self, ts: &Vec) -> Result { + fn has_tags(&self, ts: &[Tag]) -> Result { self.deref().has_tags(ts) } diff --git a/libimagentrytag/src/ui.rs b/libimagentrytag/src/ui.rs index 9f9cf2c8..e74b1ac9 100644 --- a/libimagentrytag/src/ui.rs +++ b/libimagentrytag/src/ui.rs @@ -2,27 +2,35 @@ use clap::{Arg, ArgMatches, App, SubCommand}; use tag::Tag; -/// Generates a clap::SubCommand to be integrated in the commandline-ui builder for building a +/// 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 ") .version("0.1") .about("Add or remove tags") + .arg(tag_add_arg()) + .arg(tag_remove_arg()) +} - .arg(Arg::with_name(tag_subcommand_add_arg_name()) - .short("a") - .long("add") - .takes_value(true) - .multiple(true) - .help("Add tags, seperated by comma or by specifying multiple times")) +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) + .help("Add tags, seperated by comma or by specifying multiple times") +} - .arg(Arg::with_name(tag_subcommand_remove_arg_name()) - .short("r") - .long("remove") - .takes_value(true) - .multiple(true) - .help("Remove 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) + .help("Remove tags, seperated by comma or by specifying multiple times") } pub fn tag_subcommand_name() -> &'static str { @@ -41,7 +49,7 @@ 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 +/// 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()) @@ -60,14 +68,18 @@ pub fn tag_argument_name() -> &'static str { /// /// Returns none if the argument was not specified pub fn get_add_tags(matches: &ArgMatches) -> Option> { - extract_tags(matches, "add-tags", '+') + 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> { - extract_tags(matches, "remove-tags", '-') + 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> { @@ -79,7 +91,7 @@ fn extract_tags(matches: &ArgMatches, specifier: &str, specchar: char) -> Option .map(|argmatches| { argmatches .map(String::from) - .filter(|s| s.chars().next() == Some(specchar)) + .filter(|s| s.starts_with(specchar)) .map(|s| { String::from(s.split_at(1).1) }) diff --git a/libimagentrytag/src/util.rs b/libimagentrytag/src/util.rs index caa32539..16b76a1d 100644 --- a/libimagentrytag/src/util.rs +++ b/libimagentrytag/src/util.rs @@ -1,5 +1,5 @@ use regex::Regex; -pub fn is_tag(s: &String) -> bool { - Regex::new("^[a-zA-Z]([a-zA-Z0-9_-]*)$").unwrap().captures(&s[..]).is_some() +pub fn is_tag(s: &str) -> bool { + Regex::new("^[a-zA-Z]([a-zA-Z0-9_-]*)$").unwrap().captures(s).is_some() } diff --git a/libimagentryview/Cargo.toml b/libimagentryview/Cargo.toml index e9711be0..7c0124a3 100644 --- a/libimagentryview/Cargo.toml +++ b/libimagentryview/Cargo.toml @@ -8,3 +8,6 @@ authors = ["Matthias Beyer "] [dependencies.libimagstore] path = "../libimagstore" +[dependencies.libimagerror] +path = "../libimagerror" + diff --git a/libimagentryview/src/error.rs b/libimagentryview/src/error.rs index 108529cc..e54d2fcc 100644 --- a/libimagentryview/src/error.rs +++ b/libimagentryview/src/error.rs @@ -1,80 +1,9 @@ -use std::error::Error; -use std::fmt::Error as FmtError; -use std::clone::Clone; -use std::fmt::{Display, Formatter}; +generate_error_module!( + generate_error_types!(ViewError, ViewErrorKind, + Unknown => "Unknown view error" + ); +); -/** - * Kind of error - */ -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum ViewErrorKind { -} - -fn counter_error_type_as_str(e: &ViewErrorKind) -> &'static str { - match e { - _ => "", - } -} - -impl Display for ViewErrorKind { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "{}", counter_error_type_as_str(self))); - Ok(()) - } - -} - -/** - * Store error type - */ -#[derive(Debug)] -pub struct ViewError { - err_type: ViewErrorKind, - cause: Option>, -} - -impl ViewError { - - /** - * Build a new ViewError from an ViewErrorKind, optionally with cause - */ - pub fn new(errtype: ViewErrorKind, cause: Option>) - -> ViewError - { - ViewError { - err_type: errtype, - cause: cause, - } - } - - /** - * Get the error type of this ViewError - */ - pub fn err_type(&self) -> ViewErrorKind { - self.err_type.clone() - } - -} - -impl Display for ViewError { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "[{}]", counter_error_type_as_str(&self.err_type.clone()))); - Ok(()) - } - -} - -impl Error for ViewError { - - fn description(&self) -> &str { - counter_error_type_as_str(&self.err_type.clone()) - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - -} +pub use self::error::ViewError; +pub use self::error::ViewErrorKind; diff --git a/libimagentryview/src/lib.rs b/libimagentryview/src/lib.rs index 73833b7d..950332c5 100644 --- a/libimagentryview/src/lib.rs +++ b/libimagentryview/src/lib.rs @@ -15,6 +15,7 @@ )] extern crate libimagstore; +#[macro_use] extern crate libimagerror; pub mod error; pub mod builtin; diff --git a/libimagerror/Cargo.toml b/libimagerror/Cargo.toml new file mode 100644 index 00000000..2227ce9e --- /dev/null +++ b/libimagerror/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "libimagerror" +version = "0.1.0" +authors = ["Matthias Beyer "] + +[dependencies] +log = "0.3" +ansi_term = "0.7" diff --git a/libimagerror/src/error_gen.rs b/libimagerror/src/error_gen.rs new file mode 100644 index 00000000..3a84fd90 --- /dev/null +++ b/libimagerror/src/error_gen.rs @@ -0,0 +1,395 @@ +use into::IntoError; + +#[macro_export] +macro_rules! generate_error_imports { + () => { + use std::error::Error; + use std::fmt::Error as FmtError; + use std::fmt::{Display, Formatter}; + + use $crate::into::IntoError; + } +} + +#[macro_export] +macro_rules! generate_error_module { + ( $exprs:item ) => { + pub mod error { + generate_error_imports!(); + $exprs + } + } +} + +#[macro_export] +macro_rules! generate_custom_error_types { + { + $name: ident, + $kindname: ident, + $customMemberTypeName: ident, + $($kind:ident => $string:expr),* + } => { + #[derive(Clone, Copy, Debug, PartialEq)] + pub enum $kindname { + $( $kind ),* + } + + impl Display for $kindname { + + fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { + let s = match *self { + $( $kindname::$kind => $string ),* + }; + try!(write!(fmt, "{}", s)); + Ok(()) + } + + } + + impl IntoError for $kindname { + type Target = $name; + + fn into_error(self) -> Self::Target { + $name::new(self, None) + } + + fn into_error_with_cause(self, cause: Box) -> Self::Target { + $name::new(self, Some(cause)) + } + + } + + #[derive(Debug)] + pub struct $name { + err_type: $kindname, + cause: Option>, + custom_data: Option<$customMemberTypeName>, + } + + impl $name { + + pub fn new(errtype: $kindname, cause: Option>) -> $name { + $name { + err_type: errtype, + cause: cause, + custom_data: None, + } + } + + pub fn err_type(&self) -> $kindname { + self.err_type + } + + } + + impl Into<$name> for $kindname { + + fn into(self) -> $name { + $name::new(self, None) + } + + } + + impl Display for $name { + + fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { + try!(write!(fmt, "[{}]", self.err_type)); + Ok(()) + } + + } + + impl Error for $name { + + fn description(&self) -> &str { + match self.err_type { + $( $kindname::$kind => $string ),* + } + } + + fn cause(&self) -> Option<&Error> { + self.cause.as_ref().map(|e| &**e) + } + + } + + } +} + +#[macro_export] +macro_rules! generate_result_helper { + ( + $name: ident, + $kindname: ident + ) => { + /// Trait to replace + /// + /// ```ignore + /// foo.map_err(Box::new).map_err(|e| SomeType::SomeErrorKind.into_error_with_cause(e)) + /// // or: + /// foo.map_err(|e| SomeType::SomeErrorKind.into_error_with_cause(Box::new(e))) + /// ``` + /// + /// with much nicer + /// + /// ```ignore + /// foo.map_err_into(SomeType::SomeErrorKind) + /// ``` + /// + pub trait MapErrInto { + fn map_err_into(self, error_kind: $kindname) -> Result; + } + + impl MapErrInto for Result { + + fn map_err_into(self, error_kind: $kindname) -> Result { + self.map_err(Box::new) + .map_err(|e| error_kind.into_error_with_cause(e)) + } + + } + } +} + +#[macro_export] +macro_rules! generate_option_helper { + ( + $name: ident, + $kindname: ident + ) => { + /// Trait to replace + /// + /// ```ignore + /// foo.ok_or(SomeType::SomeErrorKind.into_error()) + /// ``` + /// + /// with + /// + /// ```ignore + /// foo.ok_or_errkind(SomeType::SomeErrorKind) + /// ``` + pub trait OkOrErr { + fn ok_or_errkind(self, kind: $kindname) -> Result; + } + + impl OkOrErr for Option { + + fn ok_or_errkind(self, kind: $kindname) -> Result { + self.ok_or(kind.into_error()) + } + + } + } +} + +#[macro_export] +macro_rules! generate_error_types { + ( + $name: ident, + $kindname: ident, + $($kind:ident => $string:expr),* + ) => { + #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Copy)] + pub struct SomeNotExistingTypeWithATypeNameNoOneWillEverChoose {} + generate_custom_error_types!($name, $kindname, + SomeNotExistingTypeWithATypeNameNoOneWillEverChoose, + $($kind => $string),*); + + generate_result_helper!($name, $kindname); + generate_option_helper!($name, $kindname); + } +} + + +#[cfg(test)] +mod test { + + generate_error_module!( + generate_error_types!(TestError, TestErrorKind, + TestErrorKindA => "testerrorkind a", + TestErrorKindB => "testerrorkind B"); + ); + + #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Copy)] + pub struct CustomData { + pub test: i32, + pub othr: i64, + } + + generate_error_imports!(); + + generate_custom_error_types!(CustomTestError, CustomTestErrorKind, + CustomData, + CustomErrorKindA => "customerrorkind a", + CustomErrorKindB => "customerrorkind B"); + + impl CustomTestError { + pub fn test(&self) -> i32 { + match self.custom_data { + Some(t) => t.test, + None => 0, + } + } + + pub fn bar(&self) -> i64 { + match self.custom_data { + Some(t) => t.othr, + None => 0, + } + } + } + + + #[test] + fn test_a() { + use self::error::{TestError, TestErrorKind}; + + let kind = TestErrorKind::TestErrorKindA; + assert_eq!(String::from("testerrorkind a"), format!("{}", kind)); + + let e = TestError::new(kind, None); + assert_eq!(String::from("[testerrorkind a]"), format!("{}", e)); + } + + #[test] + fn test_b() { + use self::error::{TestError, TestErrorKind}; + + let kind = TestErrorKind::TestErrorKindB; + assert_eq!(String::from("testerrorkind B"), format!("{}", kind)); + + let e = TestError::new(kind, None); + assert_eq!(String::from("[testerrorkind B]"), format!("{}", e)); + + } + + #[test] + fn test_ab() { + use std::error::Error; + use self::error::{TestError, TestErrorKind}; + + let kinda = TestErrorKind::TestErrorKindA; + let kindb = TestErrorKind::TestErrorKindB; + assert_eq!(String::from("testerrorkind a"), format!("{}", kinda)); + assert_eq!(String::from("testerrorkind B"), format!("{}", kindb)); + + let e = TestError::new(kinda, Some(Box::new(TestError::new(kindb, None)))); + assert_eq!(String::from("[testerrorkind a]"), format!("{}", e)); + assert_eq!(TestErrorKind::TestErrorKindA, e.err_type()); + assert_eq!(String::from("[testerrorkind B]"), format!("{}", e.cause().unwrap())); + } + + pub mod anothererrormod { + generate_error_imports!(); + generate_error_types!(TestError, TestErrorKind, + TestErrorKindA => "testerrorkind a", + TestErrorKindB => "testerrorkind B"); + } + + #[test] + fn test_other_a() { + use self::anothererrormod::{TestError, TestErrorKind}; + + let kind = TestErrorKind::TestErrorKindA; + assert_eq!(String::from("testerrorkind a"), format!("{}", kind)); + + let e = TestError::new(kind, None); + assert_eq!(String::from("[testerrorkind a]"), format!("{}", e)); + } + + #[test] + fn test_other_b() { + use self::anothererrormod::{TestError, TestErrorKind}; + + let kind = TestErrorKind::TestErrorKindB; + assert_eq!(String::from("testerrorkind B"), format!("{}", kind)); + + let e = TestError::new(kind, None); + assert_eq!(String::from("[testerrorkind B]"), format!("{}", e)); + + } + + #[test] + fn test_other_ab() { + use std::error::Error; + use self::anothererrormod::{TestError, TestErrorKind}; + + let kinda = TestErrorKind::TestErrorKindA; + let kindb = TestErrorKind::TestErrorKindB; + assert_eq!(String::from("testerrorkind a"), format!("{}", kinda)); + assert_eq!(String::from("testerrorkind B"), format!("{}", kindb)); + + let e = TestError::new(kinda, Some(Box::new(TestError::new(kindb, None)))); + assert_eq!(String::from("[testerrorkind a]"), format!("{}", e)); + assert_eq!(TestErrorKind::TestErrorKindA, e.err_type()); + assert_eq!(String::from("[testerrorkind B]"), format!("{}", e.cause().unwrap())); + } + + #[test] + fn test_error_kind_mapping() { + use std::io::{Error, ErrorKind}; + use self::error::{OkOrErr, MapErrInto}; + use self::error::TestErrorKind; + + let err : Result<(), _> = Err(Error::new(ErrorKind::Other, "")); + let err : Result<(), _> = err.map_err_into(TestErrorKind::TestErrorKindA); + + assert!(err.is_err()); + let err = err.unwrap_err(); + + match err.err_type() { + TestErrorKind::TestErrorKindA => assert!(true), + _ => assert!(false), + } + } + + #[test] + fn test_error_kind_double_mapping() { + use std::io::{Error, ErrorKind}; + use self::error::{OkOrErr, MapErrInto}; + use self::error::TestErrorKind; + + let err : Result<(), _> = Err(Error::new(ErrorKind::Other, "")); + let err : Result<(), _> = err.map_err_into(TestErrorKind::TestErrorKindA) + .map_err_into(TestErrorKind::TestErrorKindB); + + assert!(err.is_err()); + let err = err.unwrap_err(); + match err.err_type() { + TestErrorKind::TestErrorKindB => assert!(true), + _ => assert!(false), + } + + // not sure how to test that the inner error is of TestErrorKindA, actually... + match err.cause() { + Some(_) => assert!(true), + None => assert!(false), + } + + } + + #[test] + fn test_error_option_good() { + use self::error::{OkOrErr, MapErrInto}; + use self::error::TestErrorKind; + + let something = Some(1); + match something.ok_or_errkind(TestErrorKind::TestErrorKindA) { + Ok(1) => assert!(true), + _ => assert!(false), + } + } + + #[test] + fn test_error_option_bad() { + use self::error::{OkOrErr, MapErrInto}; + use self::error::TestErrorKind; + + let something : Option = None; + match something.ok_or_errkind(TestErrorKind::TestErrorKindA) { + Ok(_) => assert!(false), + Err(e) => assert!(true), + } + } + +} diff --git a/libimagerror/src/into.rs b/libimagerror/src/into.rs new file mode 100644 index 00000000..a959abd4 --- /dev/null +++ b/libimagerror/src/into.rs @@ -0,0 +1,14 @@ +use std::error::Error; + +/// Trait to help converting Error kinds into Error instances +pub trait IntoError { + type Target: Error; + + /// Convert the type into an error with no cause + fn into_error(self) -> Self::Target; + + /// Convert the type into an error with cause + fn into_error_with_cause(self, cause: Box) -> Self::Target; + +} + diff --git a/libimagerror/src/lib.rs b/libimagerror/src/lib.rs new file mode 100644 index 00000000..1d1f75e2 --- /dev/null +++ b/libimagerror/src/lib.rs @@ -0,0 +1,6 @@ +#[macro_use] extern crate log; +extern crate ansi_term; + +pub mod into; +pub mod error_gen; +pub mod trace; diff --git a/libimagutil/src/trace.rs b/libimagerror/src/trace.rs similarity index 82% rename from libimagutil/src/trace.rs rename to libimagerror/src/trace.rs index 5b22c84c..e04b292f 100644 --- a/libimagutil/src/trace.rs +++ b/libimagerror/src/trace.rs @@ -2,6 +2,8 @@ use std::error::Error; use std::io::Write; use std::io::stderr; +use ansi_term::Colour::Red; + /// Print an Error type and its cause recursively /// /// The error is printed with "Error NNNN :" as prefix, where "NNNN" is a number which increases @@ -29,8 +31,9 @@ pub fn trace_error(e: &Error) { /// Output is the same as for `trace_error()`, though there are only `max` levels printed. pub fn trace_error_maxdepth(e: &Error, max: u64) { let n = count_error_causes(e); - write!(stderr(), - "{}/{} Levels of errors will be printed\n", (if max > n { n } else { max }), n).ok(); + let msg = Red.blink().paint(format!("{}/{} Levels of errors will be printed\n", + (if max > n { n } else { max }), n)); + write!(stderr(), "{}", msg).ok(); print_trace_maxdepth(n, e, max); write!(stderr(), "").ok(); } @@ -55,17 +58,17 @@ fn print_trace_maxdepth(idx: u64, e: &Error, max: u64) -> Option<&Error> { } else { write!(stderr(), "\n").ok(); } - write!(stderr(), "ERROR[{:>4}]: {}", idx, e.description()).ok(); + write!(stderr(), "{}: {}", Red.paint(format!("ERROR[{:>4}]", idx)), e.description()).ok(); e.cause() } -/// Count errors in Error::cause() recursively +/// Count errors in `Error::cause()` recursively fn count_error_causes(e: &Error) -> u64 { 1 + if e.cause().is_some() { count_error_causes(e.cause().unwrap()) } else { 0 } } fn print_trace_dbg(idx: u64, e: &Error) { - debug!("ERROR[{:>4}]: {}", idx, e.description()); + debug!("{}: {}", Red.blink().paint(format!("ERROR[{:>4}]", idx)), e.description()); if e.cause().is_some() { print_trace_dbg(idx + 1, e.cause().unwrap()); } diff --git a/libimaginteraction/Cargo.toml b/libimaginteraction/Cargo.toml index f1457ac1..1ee8e63f 100644 --- a/libimaginteraction/Cargo.toml +++ b/libimaginteraction/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Matthias Beyer "] [dependencies] spinner = "0.4" interactor = "0.1" -log = "0.3.5" +log = "0.3" ansi_term = "0.7.2" regex = "0.1" lazy_static = "0.1.15" @@ -20,3 +20,6 @@ path = "../libimagentryfilter" [dependencies.libimagutil] path = "../libimagutil" +[dependencies.libimagerror] +path = "../libimagerror" + diff --git a/libimaginteraction/src/ask.rs b/libimaginteraction/src/ask.rs index 88d44b6a..7a2c905d 100644 --- a/libimaginteraction/src/ask.rs +++ b/libimaginteraction/src/ask.rs @@ -40,13 +40,10 @@ fn ask_bool_(s: &str, default: Option, input: &mut R) -> bool return true } else if R_NO.is_match(&s[..]) { return false - } else { - if default.is_some() { - return default.unwrap(); - } - - // else again... + } else if default.is_some() { + return default.unwrap(); } + // else again... } } @@ -123,26 +120,24 @@ pub fn ask_string_(s: &str, let _ = input.read_line(&mut s); if permit_multiline { - if permit_multiline && eof.map(|e| e == s).unwrap_or(false) { + if permit_multiline && eof.map_or(false, |e| e == s) { return v.join("\n"); } - if permit_empty || v.len() != 0 { + if permit_empty || !v.is_empty() { v.push(s); } print!("{}", prompt); - } else { - if s.len() == 0 && permit_empty { - return s; - } else if s.len() == 0 && !permit_empty { - if default.is_some() { - return default.unwrap(); - } else { - continue; - } + } else if s.is_empty() && permit_empty { + return s; + } else if s.is_empty() && !permit_empty { + if default.is_some() { + return default.unwrap(); } else { - return s; + continue; } + } else { + return s; } } } diff --git a/libimaginteraction/src/error.rs b/libimaginteraction/src/error.rs index a31e3a53..9285f0fd 100644 --- a/libimaginteraction/src/error.rs +++ b/libimaginteraction/src/error.rs @@ -1,78 +1,9 @@ -use std::error::Error; -use std::fmt::Error as FmtError; -use std::clone::Clone; -use std::fmt::{Display, Formatter}; +generate_error_module!( + generate_error_types!(InteractionError, InteractionErrorKind, + Unknown => "Unknown Error" + ); +); -/** - * Kind of error - */ -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum InteractionErrorKind { - Unknown -} - -fn interaction_error_type_as_str(e: &InteractionErrorKind) -> &'static str { - match e { - &InteractionErrorKind::Unknown => "Unknown Error", - } -} - -impl Display for InteractionErrorKind { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "{}", interaction_error_type_as_str(self))); - Ok(()) - } - -} - -#[derive(Debug)] -pub struct InteractionError { - err_type: InteractionErrorKind, - cause: Option>, -} - -impl InteractionError { - - /** - * Build a new InteractionError from an InteractionErrorKind, optionally with cause - */ - pub fn new(errtype: InteractionErrorKind, cause: Option>) - -> InteractionError - { - InteractionError { - err_type: errtype, - cause: cause, - } - } - - /** - * Get the error type of this InteractionError - */ - pub fn err_type(&self) -> InteractionErrorKind { - self.err_type.clone() - } - -} - -impl Display for InteractionError { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "[{}]", interaction_error_type_as_str(&self.err_type.clone()))); - Ok(()) - } - -} - -impl Error for InteractionError { - - fn description(&self) -> &str { - interaction_error_type_as_str(&self.err_type.clone()) - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - -} +pub use self::error::InteractionError; +pub use self::error::InteractionErrorKind; diff --git a/libimaginteraction/src/lib.rs b/libimaginteraction/src/lib.rs index ff0fff06..3600b942 100644 --- a/libimaginteraction/src/lib.rs +++ b/libimaginteraction/src/lib.rs @@ -8,6 +8,7 @@ extern crate regex; extern crate libimagentryfilter; extern crate libimagstore; #[macro_use] extern crate libimagutil; +#[macro_use] extern crate libimagerror; pub mod ask; pub mod error; diff --git a/libimagnotes/Cargo.toml b/libimagnotes/Cargo.toml index a6f41952..f13cf218 100644 --- a/libimagnotes/Cargo.toml +++ b/libimagnotes/Cargo.toml @@ -5,12 +5,15 @@ authors = ["Matthias Beyer "] [dependencies] semver = "0.2" -log = "0.3.5" +log = "0.3" toml = "0.1.25" [dependencies.libimagstore] path = "../libimagstore" +[dependencies.libimagerror] +path = "../libimagerror" + [dependencies.libimagrt] path = "../libimagrt" diff --git a/libimagnotes/src/error.rs b/libimagnotes/src/error.rs index b55659f6..bbb02d0c 100644 --- a/libimagnotes/src/error.rs +++ b/libimagnotes/src/error.rs @@ -1,86 +1,12 @@ -use std::error::Error; -use std::fmt::Error as FmtError; -use std::clone::Clone; -use std::fmt::{Display, Formatter}; +generate_error_module!( + generate_error_types!(NoteError, NoteErrorKind, + StoreWriteError => "Error writing store", + StoreReadError => "Error reading store", + HeaderTypeError => "Header type error", + NoteToEntryConversion => "Error converting Note instance to Entry instance" + ); +); -/** - * Kind of error - */ -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum NoteErrorKind { - StoreWriteError, - StoreReadError, - HeaderTypeError, - NoteToEntryConversion, - // Nothing here yet -} - -fn note_error_type_as_str(e: &NoteErrorKind) -> &'static str { - match e { - &NoteErrorKind::StoreWriteError => "Error writing store", - &NoteErrorKind::StoreReadError => "Error reading store", - &NoteErrorKind::HeaderTypeError => "Header type error", - &NoteErrorKind::NoteToEntryConversion => "Error converting Note instance to Entry instance", - } -} - -impl Display for NoteErrorKind { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "{}", note_error_type_as_str(self))); - Ok(()) - } - -} - -/** - * Store error type - */ -#[derive(Debug)] -pub struct NoteError { - err_type: NoteErrorKind, - cause: Option>, -} - -impl NoteError { - - /** - * Build a new NoteError from an NoteErrorKind, optionally with cause - */ - pub fn new(errtype: NoteErrorKind, cause: Option>) -> NoteError { - NoteError { - err_type: errtype, - cause: cause, - } - } - - /** - * Get the error type of this NoteError - */ - pub fn err_type(&self) -> NoteErrorKind { - self.err_type.clone() - } - -} - -impl Display for NoteError { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "[{}]", note_error_type_as_str(&self.err_type.clone()))); - Ok(()) - } - -} - -impl Error for NoteError { - - fn description(&self) -> &str { - note_error_type_as_str(&self.err_type.clone()) - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - -} +pub use self::error::NoteError; +pub use self::error::NoteErrorKind; diff --git a/libimagnotes/src/lib.rs b/libimagnotes/src/lib.rs index 64ef75a2..ee43c9a3 100644 --- a/libimagnotes/src/lib.rs +++ b/libimagnotes/src/lib.rs @@ -20,6 +20,7 @@ extern crate toml; extern crate libimagrt; #[macro_use] extern crate libimagstore; +#[macro_use] extern crate libimagerror; extern crate libimagentrytag; module_entry_path_mod!("notes", "0.1.0"); diff --git a/libimagnotes/src/note.rs b/libimagnotes/src/note.rs index 2728b768..fc0a507d 100644 --- a/libimagnotes/src/note.rs +++ b/libimagnotes/src/note.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; -use std::ops::{DerefMut, Deref}; +use std::ops::Deref; use toml::Value; @@ -10,7 +10,7 @@ use libimagstore::storeid::StoreId; use libimagstore::storeid::StoreIdIterator; use libimagstore::store::FileLockEntry; use libimagstore::store::Store; -use libimagentrytag::tag::Tag; +use libimagentrytag::tag::{Tag, TagSlice}; use libimagentrytag::tagable::Tagable; use libimagentrytag::result::Result as TagResult; @@ -102,6 +102,16 @@ impl<'a> Note<'a> { .map(|entry| Note { entry: entry }) } + pub fn get(store: &Store, name: String) -> Result> { + use libimagerror::into::IntoError; + + match store.get(ModuleEntryPath::new(name).into_storeid()) { + Ok(Some(entry)) => Ok(Some(Note { entry: entry })), + Ok(None) => Ok(None), + Err(e) => Err(NEK::StoreWriteError.into_error_with_cause(Box::new(e))), + } + } + pub fn all_notes(store: &Store) -> Result { store.retrieve_for_module("notes") .map(|iter| NoteIterator::new(store, iter)) @@ -124,7 +134,7 @@ impl<'a> Tagable for Note<'a> { self.entry.get_tags() } - fn set_tags(&mut self, ts: Vec) -> TagResult<()> { + fn set_tags(&mut self, ts: &[Tag]) -> TagResult<()> { self.entry.set_tags(ts) } @@ -136,23 +146,23 @@ impl<'a> Tagable for Note<'a> { self.entry.remove_tag(t) } - fn has_tag(&self, t: &Tag) -> TagResult { + fn has_tag(&self, t: TagSlice) -> TagResult { self.entry.has_tag(t) } - fn has_tags(&self, ts: &Vec) -> TagResult { + fn has_tags(&self, ts: &[Tag]) -> TagResult { self.entry.has_tags(ts) } } trait FromStoreId { - fn from_storeid<'a>(&'a Store, StoreId) -> Result>; + fn from_storeid(&Store, StoreId) -> Result; } impl<'a> FromStoreId for Note<'a> { - fn from_storeid<'b>(store: &'b Store, id: StoreId) -> Result> { + fn from_storeid(store: &Store, id: StoreId) -> Result { debug!("Loading note from storeid: '{:?}'", id); match store.retrieve(id) { Err(e) => Err(NE::new(NEK::StoreReadError, Some(Box::new(e)))), diff --git a/libimagrt/Cargo.toml b/libimagrt/Cargo.toml index bb37744a..4e3dd673 100644 --- a/libimagrt/Cargo.toml +++ b/libimagrt/Cargo.toml @@ -5,11 +5,13 @@ authors = ["Matthias Beyer "] [dependencies] clap = "2.1.1" +env_logger = "0.3" toml = "0.1.27" -log = "0.3.4" +log = "0.3" xdg-basedir = "0.2.2" itertools = "0.4" tempfile = "2.1.1" +ansi_term = "0.7" [dependencies.libimagstore] path = "../libimagstore" @@ -20,3 +22,6 @@ path = "../libimagstorestdhook" [dependencies.libimagutil] path = "../libimagutil" +[dependencies.libimagerror] +path = "../libimagerror" + diff --git a/libimagrt/src/configuration.rs b/libimagrt/src/configuration.rs index 0913d90a..a2ee1fed 100644 --- a/libimagrt/src/configuration.rs +++ b/libimagrt/src/configuration.rs @@ -4,133 +4,48 @@ use std::ops::Deref; use toml::{Parser, Value}; -/** - * Errors which are related to configuration-file loading - */ -pub mod error { - use std::error::Error; - use std::fmt::{Display, Formatter}; - use std::fmt::Error as FmtError; - - /** - * The kind of an error - */ - #[derive(Clone, Debug, PartialEq)] - pub enum ConfigErrorKind { - NoConfigFileFound, - } - - /** - * Configuration error type - */ - #[derive(Debug)] - pub struct ConfigError { - kind: ConfigErrorKind, - cause: Option>, - } - - impl ConfigError { - - /** - * Instantiate a new ConfigError, optionally with cause - */ - pub fn new(kind: ConfigErrorKind, cause: Option>) -> ConfigError { - ConfigError { - kind: kind, - cause: cause, - } - } - - /** - * get the Kind of the Error - */ - pub fn err_type(&self) -> ConfigErrorKind { - self.kind.clone() - } - - /** - * Get the string, the ConfigError can be described with - */ - pub fn as_str(e: &ConfigError) -> &'static str { - match e.err_type() { - ConfigErrorKind::NoConfigFileFound => "No config file found", - } - } - - } - - impl Display for ConfigError { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "{}", ConfigError::as_str(self))); - Ok(()) - } - - } - - impl Error for ConfigError { - - fn description(&self) -> &str { - ConfigError::as_str(self) - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - - } - -} +generate_error_module!( + generate_error_types!(ConfigError, ConfigErrorKind, + NoConfigFileFound => "No config file found" + ); +); use self::error::{ConfigError, ConfigErrorKind}; - /** - * Result type of this module. Either T or ConfigError + * Result type of this module. Either `T` or `ConfigError` */ pub type Result = RResult; -/** - * Configuration object - * - * Holds all config variables which are globally available plus the configuration object from the - * config parser, which can be accessed. - */ +/// `Configuration` object +/// +/// Holds all config variables which are globally available plus the configuration object from the +/// config parser, which can be accessed. #[derive(Debug)] pub struct Configuration { - /** - * The plain configuration object for direct access if necessary - */ + /// The plain configuration object for direct access if necessary config: Value, - /** - * The verbosity the program should run with - */ + /// The verbosity the program should run with verbosity: bool, - /** - * The editor which should be used - */ + /// The editor which should be used editor: Option, - /** - * The options the editor should get when opening some file - */ + ///The options the editor should get when opening some file editor_opts: String, } impl Configuration { - /** - * Get a new configuration object. - * - * The passed runtimepath is used for searching the configuration file, whereas several file - * names are tested. If that does not work, the home directory and the XDG basedir are tested - * with all variants. - * - * If that doesn't work either, an error is returned. - */ + /// Get a new configuration object. + /// + /// The passed runtimepath is used for searching the configuration file, whereas several file + /// names are tested. If that does not work, the home directory and the XDG basedir are tested + /// with all variants. + /// + /// If that doesn't work either, an error is returned. pub fn new(rtp: &PathBuf) -> Result { fetch_config(&rtp).map(|cfg| { let verbosity = get_verbosity(&cfg); @@ -161,8 +76,8 @@ impl Configuration { } pub fn store_config(&self) -> Option<&Value> { - match &self.config { - &Value::Table(ref tabl) => tabl.get("store"), + match self.config { + Value::Table(ref tabl) => tabl.get("store"), _ => None, } } @@ -179,27 +94,26 @@ impl Deref for Configuration { } fn get_verbosity(v: &Value) -> bool { - match v { - &Value::Table(ref t) => t.get("verbose") - .map(|v| match v { &Value::Boolean(b) => b, _ => false, }) - .unwrap_or(false), + match *v { + Value::Table(ref t) => t.get("verbose") + .map_or(false, |v| match *v { Value::Boolean(b) => b, _ => false, }), _ => false, } } fn get_editor(v: &Value) -> Option { - match v { - &Value::Table(ref t) => t.get("editor") - .and_then(|v| match v { &Value::String(ref s) => Some(s.clone()), _ => None, }), + match *v { + Value::Table(ref t) => t.get("editor") + .and_then(|v| match *v { Value::String(ref s) => Some(s.clone()), _ => None, }), _ => None, } } fn get_editor_opts(v: &Value) -> String { - match v { - &Value::Table(ref t) => t.get("editor-opts") - .and_then(|v| match v { &Value::String(ref s) => Some(s.clone()), _ => None, }) - .unwrap_or(String::new()), + match *v { + Value::Table(ref t) => t.get("editor-opts") + .and_then(|v| match *v { Value::String(ref s) => Some(s.clone()), _ => None, }) + .unwrap_or_default(), _ => String::new(), } } @@ -224,11 +138,12 @@ fn fetch_config(rtp: &PathBuf) -> Result { let variants = vec!["config", "config.toml", "imagrc", "imagrc.toml"]; let modifier = |base: &PathBuf, v: &'static str| { let mut base = base.clone(); - base.push(format!("{}", v)); + base.push(String::from(v)); base }; vec![ + vec![rtp.clone()], gen_vars(rtp.clone(), variants.clone(), &modifier), env::var("HOME").map(|home| gen_vars(PathBuf::from(home), variants.clone(), &modifier)) @@ -244,13 +159,16 @@ fn fetch_config(rtp: &PathBuf) -> Result { let mut s = String::new(); let f = File::open(path); if f.is_err() { + return None } let mut f = f.unwrap(); f.read_to_string(&mut s).ok(); s }; + let mut parser = Parser::new(&content[..]); let res = parser.parse(); + if res.is_none() { write!(stderr(), "Config file parser error:").ok(); for error in parser.errors { @@ -265,6 +183,6 @@ fn fetch_config(rtp: &PathBuf) -> Result { .filter(|loaded| loaded.is_some()) .nth(0) .map(|inner| Value::Table(inner.unwrap())) - .ok_or(ConfigError::new(ConfigErrorKind::NoConfigFileFound, None)) + .ok_or(ConfigErrorKind::NoConfigFileFound.into()) } diff --git a/libimagrt/src/edit.rs b/libimagrt/src/edit.rs index 93fa1a6b..b04bec06 100644 --- a/libimagrt/src/edit.rs +++ b/libimagrt/src/edit.rs @@ -7,6 +7,8 @@ use error::RuntimeErrorKind; use libimagstore::store::FileLockEntry; use libimagstore::store::Entry; +use libimagerror::into::IntoError; + pub type EditResult = Result; pub trait Edit { @@ -55,18 +57,24 @@ pub fn edit_in_tmpfile(rt: &Runtime, s: &mut String) -> EditResult<()> { if let Some(mut editor) = rt.editor() { let exit_status = editor.arg(file_path).status(); - match exit_status.map(|s| s.success()) { + match exit_status.map(|s| s.success()).map_err(Box::new) { Ok(true) => { file.sync_data() .and_then(|_| file.seek(SeekFrom::Start(0))) - .and_then(|_| file.read_to_string(s)) + .and_then(|_| { + let mut new_s = String::new(); + let res = file.read_to_string(&mut new_s); + *s = new_s; + res + }) .map(|_| ()) - .map_err(|e| RuntimeError::new(RuntimeErrorKind::IOError, Some(Box::new(e)))) + .map_err(Box::new) + .map_err(|e| RuntimeErrorKind::IOError.into_error_with_cause(e)) }, - Ok(false) => Err(RuntimeError::new(RuntimeErrorKind::ProcessExitFailure, None)), - Err(e) => Err(RuntimeError::new(RuntimeErrorKind::IOError, Some(Box::new(e)))), + Ok(false) => Err(RuntimeErrorKind::ProcessExitFailure.into()), + Err(e) => Err(RuntimeErrorKind::IOError.into_error_with_cause(e)), } } else { - Err(RuntimeError::new(RuntimeErrorKind::Instantiate, None)) + Err(RuntimeErrorKind::Instantiate.into()) } } diff --git a/libimagrt/src/error.rs b/libimagrt/src/error.rs index 5ad5ea3d..e5f26baa 100644 --- a/libimagrt/src/error.rs +++ b/libimagrt/src/error.rs @@ -1,68 +1,16 @@ -use std::error::Error; -use std::fmt::Display; -use std::fmt::Formatter; -use std::fmt::Error as FmtError; +generate_error_imports!(); use std::io::Error as IOError; -#[derive(Debug, PartialEq, Clone, Copy)] -pub enum RuntimeErrorKind { - Instantiate, - IOError, - ProcessExitFailure, - - // more? -} - -#[derive(Debug)] -pub struct RuntimeError { - kind: RuntimeErrorKind, - cause: Option>, -} - -impl RuntimeError { - - pub fn new(kind: RuntimeErrorKind, cause: Option>) -> RuntimeError { - RuntimeError { - kind: kind, - cause: cause, - } - } - -} - -fn runtime_error_kind_as_str(e: &RuntimeErrorKind) -> &'static str { - match e { - &RuntimeErrorKind::Instantiate => "Could not instantiate", - &RuntimeErrorKind::IOError => "IO Error", - &RuntimeErrorKind::ProcessExitFailure => "Process exited with failure", - } -} - -impl Display for RuntimeError { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "{}", runtime_error_kind_as_str(&self.kind))); - Ok(()) - } - -} - -impl Error for RuntimeError { - - fn description(&self) -> &str { - runtime_error_kind_as_str(&self.kind) - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - -} +generate_error_types!(RuntimeError, RuntimeErrorKind, + Instantiate => "Could not instantiate", + IOError => "IO Error", + ProcessExitFailure => "Process exited with failure" +); impl From for RuntimeError { fn from(ioe: IOError) -> RuntimeError { - RuntimeError::new(RuntimeErrorKind::IOError, Some(Box::new(ioe))) + RuntimeErrorKind::IOError.into_error_with_cause(Box::new(ioe)) } } diff --git a/libimagrt/src/lib.rs b/libimagrt/src/lib.rs index 2e3d307c..d1d7e249 100644 --- a/libimagrt/src/lib.rs +++ b/libimagrt/src/lib.rs @@ -17,7 +17,9 @@ #[macro_use] extern crate log; #[macro_use] extern crate itertools; #[cfg(unix)] extern crate xdg_basedir; +extern crate env_logger; extern crate tempfile; +extern crate ansi_term; extern crate clap; extern crate toml; @@ -25,6 +27,7 @@ extern crate toml; extern crate libimagstore; extern crate libimagstorestdhook; extern crate libimagutil; +#[macro_use] extern crate libimagerror; mod configuration; mod logger; @@ -32,4 +35,5 @@ mod logger; pub mod edit; pub mod error; pub mod runtime; +pub mod setup; diff --git a/libimagrt/src/logger.rs b/libimagrt/src/logger.rs index 302d790a..e637163d 100644 --- a/libimagrt/src/logger.rs +++ b/libimagrt/src/logger.rs @@ -24,14 +24,37 @@ impl Log for ImagLogger { } fn log(&self, record: &LogRecord) { + use ansi_term::Colour::Red; + use ansi_term::Colour::Yellow; + use ansi_term::Colour::Cyan; + if self.enabled(record.metadata()) { // TODO: This is just simple logging. Maybe we can enhance this lateron - if record.metadata().level() == LogLevel::Debug { - let loc = record.location(); - writeln!(stderr(), "[imag][{: <5}][{}][{: >5}]: {}", - record.level(), loc.file(), loc.line(), record.args()).ok(); - } else { - writeln!(stderr(), "[imag][{: <5}]: {}", record.level(), record.args()).ok(); + let loc = record.location(); + match record.metadata().level() { + LogLevel::Debug => { + let lvl = Cyan.paint(format!("{}", record.level())); + let file = Cyan.paint(format!("{}", loc.file())); + let ln = Cyan.paint(format!("{}", loc.line())); + let args = Cyan.paint(format!("{}", record.args())); + + writeln!(stderr(), "[imag][{: <5}][{}][{: >5}]: {}", lvl, file, ln, args).ok(); + }, + LogLevel::Warn | LogLevel::Error => { + let lvl = Red.blink().paint(format!("{}", record.level())); + let args = Red.paint(format!("{}", record.args())); + + writeln!(stderr(), "[imag][{: <5}]: {}", lvl, args).ok(); + }, + LogLevel::Info => { + let lvl = Yellow.paint(format!("{}", record.level())); + let args = Yellow.paint(format!("{}", record.args())); + + writeln!(stderr(), "[imag][{: <5}]: {}", lvl, args).ok(); + }, + _ => { + writeln!(stderr(), "[imag][{: <5}]: {}", record.level(), record.args()).ok(); + }, } } } diff --git a/libimagrt/src/runtime.rs b/libimagrt/src/runtime.rs index 7319958f..a5f9d80d 100644 --- a/libimagrt/src/runtime.rs +++ b/libimagrt/src/runtime.rs @@ -13,6 +13,7 @@ use log::LogLevelFilter; use configuration::Configuration; use error::RuntimeError; use error::RuntimeErrorKind; +use error::MapErrInto; use logger::ImagLogger; use libimagstore::store::Store; @@ -37,13 +38,13 @@ impl<'a> Runtime<'a> { */ pub fn new(cli_spec: App<'a, 'a>) -> Result, RuntimeError> { use std::env; - use std::error::Error; use libimagstore::hook::position::HookPosition; use libimagstore::error::StoreErrorKind; use libimagstorestdhook::debug::DebugHook; - use libimagutil::trace::trace_error; - use libimagutil::trace::trace_error_dbg; + use libimagerror::trace::trace_error; + use libimagerror::trace::trace_error_dbg; + use libimagerror::into::IntoError; use configuration::error::ConfigErrorKind; @@ -55,42 +56,43 @@ impl<'a> Runtime<'a> { Runtime::init_logger(is_debugging, is_verbose); let rtp : PathBuf = matches.value_of("runtimepath") - .map(PathBuf::from) - .unwrap_or_else(|| { + .map_or_else(|| { env::var("HOME") .map(PathBuf::from) .map(|mut p| { p.push(".imag"); p}) .unwrap_or_else(|_| { panic!("You seem to be $HOME-less. Please get a $HOME before using this software. We are sorry for you and hope you have some accommodation anyways."); }) - }); + }, PathBuf::from); let storepath = matches.value_of("storepath") - .map(PathBuf::from) - .unwrap_or({ + .map_or_else(|| { let mut spath = rtp.clone(); spath.push("store"); spath - }); + }, PathBuf::from); - let cfg = Configuration::new(&rtp); - let cfg = if cfg.is_err() { - let e = cfg.unwrap_err(); - if e.err_type() != ConfigErrorKind::NoConfigFileFound { - let cause : Option> = Some(Box::new(e)); - return Err(RuntimeError::new(RuntimeErrorKind::Instantiate, cause)); + let configpath = matches.value_of("config") + .map_or_else(|| { + let mut spath = rtp.clone(); + spath.push("store"); + spath + }, PathBuf::from); + + let cfg = match Configuration::new(&configpath) { + Err(e) => if e.err_type() != ConfigErrorKind::NoConfigFileFound { + return Err(RuntimeErrorKind::Instantiate.into_error_with_cause(Box::new(e))); } else { - trace_error(&e); + warn!("No config file found."); + warn!("Continuing without configuration file"); None - } - } else { - Some(cfg.unwrap()) + }, + + Ok(cfg) => Some(cfg), }; - let store_config = { - match &cfg { - &Some(ref c) => c.store_config().map(|c| c.clone()), - &None => None, - } + let store_config = match cfg { + Some(ref c) => c.store_config().cloned(), + None => None, }; if is_debugging { @@ -133,9 +135,7 @@ impl<'a> Runtime<'a> { store: store, } }) - .map_err(|e| { - RuntimeError::new(RuntimeErrorKind::Instantiate, Some(Box::new(e))) - }) + .map_err_into(RuntimeErrorKind::Instantiate) } /** @@ -202,23 +202,30 @@ impl<'a> Runtime<'a> { * Initialize the internal logger */ fn init_logger(is_debugging: bool, is_verbose: bool) { - let lvl = if is_debugging { - LogLevelFilter::Debug - } else if is_verbose { - LogLevelFilter::Info - } else { - LogLevelFilter::Error - }; + use std::env::var as env_var; + use env_logger; - 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())) - }) - .map_err(|_| { - panic!("Could not setup logger"); - }) - .ok(); + 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::Error + }; + + 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())) + }) + .map_err(|_| { + panic!("Could not setup logger"); + }) + .ok(); + } } /** @@ -268,8 +275,8 @@ impl<'a> Runtime<'a> { .value_of("editor") .map(String::from) .or({ - match &self.configuration { - &Some(ref c) => c.editor().map(|s| s.clone()), + match self.configuration { + Some(ref c) => c.editor().cloned(), _ => None, } }) diff --git a/libimagrt/src/setup.rs b/libimagrt/src/setup.rs new file mode 100644 index 00000000..620a81cf --- /dev/null +++ b/libimagrt/src/setup.rs @@ -0,0 +1,27 @@ +use clap::App; + +use runtime::Runtime; + +pub type Name = &'static str; +pub type Version<'a> = &'a str; +pub type About = &'static str; + +/// Helper to generate the Runtime object +/// +/// exit()s the program if the runtime couldn't be build, prints error with println!() before +/// exiting +pub fn generate_runtime_setup<'a, B>(name: Name, version: Version<'a>, about: About, builder: B) + -> Runtime<'a> + where B: FnOnce(App<'a, 'a>) -> App<'a, 'a> +{ + use std::process::exit; + use libimagerror::trace::trace_error_dbg; + + Runtime::new(builder(Runtime::get_default_cli_builder(name, version, about))) + .unwrap_or_else(|e| { + println!("Could not set up Runtime"); + println!("{:?}", e); + trace_error_dbg(&e); + exit(1); + }) +} diff --git a/libimagstore/Cargo.toml b/libimagstore/Cargo.toml index 6cde98bf..c35004c0 100644 --- a/libimagstore/Cargo.toml +++ b/libimagstore/Cargo.toml @@ -4,15 +4,22 @@ version = "0.1.0" authors = ["Matthias Beyer "] [dependencies] -fs2 = "0.2.2" +fs2 = "0.2" glob = "0.2.11" lazy_static = "0.1.15" -log = "0.3.5" +log = "0.3" regex = "0.1" semver = "0.2" toml = "0.1.25" version = "2.0.1" crossbeam = "0.2.8" +walkdir = "0.1.5" + +[dependencies.libimagerror] +path = "../libimagerror" + +[dependencies.libimagutil] +path = "../libimagutil" [dev-dependencies] tempdir = "0.3.4" diff --git a/libimagstore/src/configuration.rs b/libimagstore/src/configuration.rs index 95e8b2fc..f5ba495f 100644 --- a/libimagstore/src/configuration.rs +++ b/libimagstore/src/configuration.rs @@ -51,10 +51,13 @@ pub fn config_is_valid(config: &Option) -> bool { fn has_key_with_string_ary(v: &BTreeMap, key: &str) -> bool { v.get(key) - .map(|t| match t { - &Value::Array(ref a) => a.iter().all(|elem| { - match elem { - &Value::String(_) => true, + .map_or_else(|| { + write!(stderr(), "Required key '{}' is not in store config", key).ok(); + false + }, |t| match *t { + Value::Array(ref a) => a.iter().all(|elem| { + match *elem { + Value::String(_) => true, _ => false, } }), @@ -63,9 +66,6 @@ pub fn config_is_valid(config: &Option) -> bool { .ok(); false } - }).unwrap_or_else(|| { - write!(stderr(), "Required key '{}' is not in store config", key).ok(); - false }) } @@ -84,18 +84,21 @@ pub fn config_is_valid(config: &Option) -> bool { where F: Fn(&Value) -> bool { store_config.get(section) // The store config has the section `section` - .map(|section_table| { - match section_table { // which is - &Value::Table(ref section_table) => // a table + .map_or_else(|| { + write!(stderr(), "Store config expects section '{}' to be present, but isn't.", + section).ok(); + false + }, |section_table| { + match *section_table { // which is + Value::Table(ref section_table) => // a table section_table .iter() // which has values, .all(|(inner_key, cfg)| { // and all of these values - match cfg { - &Value::Table(ref hook_config) => { // are tables + match *cfg { + Value::Table(ref hook_config) => { // are tables hook_config.get(key) // with a key // fullfilling this constraint - .map(|hook_aspect| f(&hook_aspect)) - .unwrap_or(false) + .map_or(false, |hook_aspect| f(&hook_aspect)) }, _ => { write!(stderr(), "Store config expects '{}' to be in '{}.{}', but isn't.", @@ -111,17 +114,12 @@ pub fn config_is_valid(config: &Option) -> bool { } } }) - .unwrap_or_else(|| { - write!(stderr(), "Store config expects section '{}' to be present, but isn't.", - section).ok(); - false - }) } - match config { - &Some(Value::Table(ref t)) => { - has_key_with_string_ary(t, "pre-read-hook-aspects") && - has_key_with_string_ary(t, "post-read-hook-aspects") && + match *config { + Some(Value::Table(ref t)) => { + has_key_with_string_ary(t, "store-unload-hook-aspects") && + has_key_with_string_ary(t, "pre-create-hook-aspects") && has_key_with_string_ary(t, "post-create-hook-aspects") && has_key_with_string_ary(t, "pre-retrieve-hook-aspects") && @@ -133,17 +131,13 @@ pub fn config_is_valid(config: &Option) -> bool { // The section "hooks" has maps which have a key "aspect" which has a value of type // String - check_all_inner_maps_have_key_with(t, "hooks", "aspect", |asp| { - let res = match asp { &Value::String(_) => true, _ => false }; - res - }) && + check_all_inner_maps_have_key_with(t, "hooks", "aspect", + |asp| is_match!(asp, &Value::String(_))) && // The section "aspects" has maps which have a key "parllel" which has a value of type // Boolean - check_all_inner_maps_have_key_with(t, "aspects", "parallel", |asp| { - let res = match asp { &Value::Boolean(_) => true, _ => false, }; - res - }) + check_all_inner_maps_have_key_with(t, "aspects", "parallel", + |asp| is_match!(asp, &Value::Boolean(_))) } _ => { write!(stderr(), "Store config is no table").ok(); @@ -152,6 +146,10 @@ pub fn config_is_valid(config: &Option) -> bool { } } +pub fn get_store_unload_aspect_names(value: &Option) -> Vec { + get_aspect_names_for_aspect_position("store-unload-hook-aspects", value) +} + pub fn get_pre_create_aspect_names(value: &Option) -> Vec { get_aspect_names_for_aspect_position("pre-create-hook-aspects", value) } @@ -184,6 +182,14 @@ pub fn get_post_delete_aspect_names(value: &Option) -> Vec { get_aspect_names_for_aspect_position("post-delete-hook-aspects", value) } +pub fn get_pre_move_aspect_names(value: &Option) -> Vec { + get_aspect_names_for_aspect_position("pre-move-hook-aspects", value) +} + +pub fn get_post_move_aspect_names(value: &Option) -> Vec { + get_aspect_names_for_aspect_position("post-move-hook-aspects", value) +} + #[derive(Debug)] pub struct AspectConfig { parallel: bool, @@ -201,16 +207,15 @@ impl AspectConfig { } fn is_parallel(init: &Value) -> bool { - match init { - &Value::Table(ref t) => + match *init { + Value::Table(ref t) => t.get("parallel") - .map(|value| { - match value { - &Value::Boolean(b) => b, + .map_or(false, |value| { + match *value { + Value::Boolean(b) => b, _ => false, } - }) - .unwrap_or(false), + }), _ => false, } } @@ -221,8 +226,8 @@ impl AspectConfig { /// /// Returns `None` if one of the keys in the chain is not available pub fn get_for(v: &Option, a_name: String) -> Option { - match v { - &Some(Value::Table(ref tabl)) => tabl.get(&a_name[..]) + match *v { + Some(Value::Table(ref tabl)) => tabl.get(&a_name[..]) .map(|asp| AspectConfig::new(asp.clone())), _ => None, } @@ -233,13 +238,13 @@ impl AspectConfig { fn get_aspect_names_for_aspect_position(config_name: &'static str, value: &Option) -> Vec { let mut v = vec![]; - match value { - &Some(Value::Table(ref t)) => { + match *value { + Some(Value::Table(ref t)) => { match t.get(config_name) { Some(&Value::Array(ref a)) => { for elem in a { - match elem { - &Value::String(ref s) => v.push(s.clone()), + match *elem { + Value::String(ref s) => v.push(s.clone()), _ => warn!("Non-String in configuration, inside '{}'", config_name), } } @@ -247,7 +252,7 @@ fn get_aspect_names_for_aspect_position(config_name: &'static str, value: &Optio _ => warn!("'{}' configuration key should contain Array, does not", config_name), }; }, - &None => warn!("No store configuration"), + None => warn!("No store configuration"), _ => warn!("Configuration is not a table"), } v diff --git a/libimagstore/src/error.rs b/libimagstore/src/error.rs index 4fea66f0..a3fb7d5c 100644 --- a/libimagstore/src/error.rs +++ b/libimagstore/src/error.rs @@ -1,145 +1,69 @@ -use std::error::Error; -use std::fmt::Error as FmtError; -use std::clone::Clone; -use std::fmt::{Debug, Display, Formatter}; -use std::fmt; +generate_error_imports!(); use std::convert::From; -/** - * Kind of store error - */ -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum StoreErrorKind { - ConfigurationError, - FileError, - IdLocked, - IdNotFound, - OutOfMemory, - FileNotFound, - FileNotCreated, - IoError, - StorePathExists, - StorePathCreate, - LockError, - LockPoisoned, - EntryAlreadyBorrowed, - EntryAlreadyExists, - MalformedEntry, - HeaderPathSyntaxError, - HeaderPathTypeFailure, - HeaderKeyNotFound, - HeaderTypeFailure, - HookRegisterError, - AspectNameNotFoundError, - HookExecutionError, - PreHookExecuteError, - PostHookExecuteError, - StorePathLacksVersion, - GlobError, - EncodingError, - // maybe more -} +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Copy)] +pub struct CustomErrorData {} -fn store_error_type_as_str(e: &StoreErrorKind) -> &'static str { - match e { - &StoreErrorKind::ConfigurationError => "Store Configuration Error", - &StoreErrorKind::FileError => "File Error", - &StoreErrorKind::IdLocked => "ID locked", - &StoreErrorKind::IdNotFound => "ID not found", - &StoreErrorKind::OutOfMemory => "Out of Memory", - &StoreErrorKind::FileNotFound => "File corresponding to ID not found", - &StoreErrorKind::FileNotCreated => "File corresponding to ID could not be created", - &StoreErrorKind::IoError => "File Error", - &StoreErrorKind::StorePathExists => "Store path exists", - &StoreErrorKind::StorePathCreate => "Store path create", - &StoreErrorKind::LockError => "Error locking datastructure", - &StoreErrorKind::LockPoisoned - => "The internal Store Lock has been poisoned", - &StoreErrorKind::EntryAlreadyBorrowed => "Entry is already borrowed", - &StoreErrorKind::EntryAlreadyExists => "Entry already exists", - &StoreErrorKind::MalformedEntry => "Entry has invalid formatting, missing header", - &StoreErrorKind::HeaderPathSyntaxError => "Syntax error in accessor string", - &StoreErrorKind::HeaderPathTypeFailure => "Header has wrong type for path", - &StoreErrorKind::HeaderKeyNotFound => "Header Key not found", - &StoreErrorKind::HeaderTypeFailure => "Header type is wrong", - &StoreErrorKind::HookRegisterError => "Hook register error", - &StoreErrorKind::AspectNameNotFoundError => "Aspect name not found", - &StoreErrorKind::HookExecutionError => "Hook execution error", - &StoreErrorKind::PreHookExecuteError => "Pre-Hook execution error", - &StoreErrorKind::PostHookExecuteError => "Post-Hook execution error", - &StoreErrorKind::StorePathLacksVersion => "The supplied store path has no version part", - &StoreErrorKind::GlobError => "glob() error", - &StoreErrorKind::EncodingError => "Encoding error", - } -} +generate_custom_error_types!(StoreError, StoreErrorKind, CustomErrorData, + ConfigurationError => "Store Configuration Error", + FileError => "File Error", + IoError => "IO Error", + IdLocked => "ID locked", + IdNotFound => "ID not found", + OutOfMemory => "Out of Memory", + FileNotFound => "File corresponding to ID not found", + FileNotCreated => "File corresponding to ID could not be created", + StorePathExists => "Store path exists", + StorePathCreate => "Store path create", + LockError => "Error locking datastructure", + LockPoisoned => "The internal Store Lock has been poisoned", + EntryAlreadyBorrowed => "Entry is already borrowed", + EntryAlreadyExists => "Entry already exists", + MalformedEntry => "Entry has invalid formatting, missing header", + HeaderPathSyntaxError => "Syntax error in accessor string", + HeaderPathTypeFailure => "Header has wrong type for path", + HeaderKeyNotFound => "Header Key not found", + HeaderTypeFailure => "Header type is wrong", + HookRegisterError => "Hook register error", + AspectNameNotFoundError => "Aspect name not found", + HookExecutionError => "Hook execution error", + PreHookExecuteError => "Pre-Hook execution error", + PostHookExecuteError => "Post-Hook execution error", + StorePathLacksVersion => "The supplied store path has no version part", + GlobError => "glob() error", + EncodingError => "Encoding error", + StorePathError => "Store Path error", + EntryRenameError => "Entry rename error", -impl Display for StoreErrorKind { + CreateCallError => "Error when calling create()", + RetrieveCallError => "Error when calling retrieve()", + GetCallError => "Error when calling get()", + GetAllVersionsCallError => "Error when calling get_all_versions()", + RetrieveForModuleCallError => "Error when calling retrieve_for_module()", + UpdateCallError => "Error when calling update()", + RetrieveCopyCallError => "Error when calling retrieve_copy()", + DeleteCallError => "Error when calling delete()", + MoveCallError => "Error when calling move()", + MoveByIdCallError => "Error when calling move_by_id()" +); - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "{}", store_error_type_as_str(self))); - Ok(()) - } +generate_result_helper!(StoreError, StoreErrorKind); +generate_option_helper!(StoreError, StoreErrorKind); -} - -/** - * Store error type - */ -#[derive(Debug)] -pub struct StoreError { - err_type: StoreErrorKind, - cause: Option>, -} - -impl StoreError { - - /** - * Build a new StoreError from an StoreErrorKind, optionally with cause - */ - pub fn new(errtype: StoreErrorKind, cause: Option>) - -> StoreError - { - StoreError { - err_type: errtype, - cause: cause, - } - } - - /** - * Get the error type of this StoreError - */ - pub fn err_type(&self) -> StoreErrorKind { - self.err_type.clone() - } - -} - -impl Display for StoreError { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "[{}]", store_error_type_as_str(&self.err_type.clone()))); - Ok(()) - } - -} - -impl Error for StoreError { - - fn description(&self) -> &str { - store_error_type_as_str(&self.err_type.clone()) - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - -} +generate_custom_error_types!(ParserError, ParserErrorKind, CustomErrorData, + TOMLParserErrors => "Several TOML-Parser-Errors", + MissingMainSection => "Missing main section", + MissingVersionInfo => "Missing version information in main section", + NonTableInBaseTable => "A non-table was found in the base table", + HeaderInconsistency => "The header is inconsistent" +); impl From for StoreError { fn from(ps: ParserError) -> StoreError { StoreError { err_type: StoreErrorKind::MalformedEntry, cause: Some(Box::new(ps)), + custom_data: None, } } } @@ -149,68 +73,8 @@ impl From<::std::io::Error> for StoreError { StoreError { err_type: StoreErrorKind::IoError, cause: Some(Box::new(ps)), + custom_data: None, } } } -#[derive(Clone)] -pub enum ParserErrorKind { - TOMLParserErrors, - MissingMainSection, - MissingVersionInfo, - NonTableInBaseTable, - HeaderInconsistency, -} - -pub struct ParserError { - kind: ParserErrorKind, - cause: Option>, -} - -impl ParserError { - - pub fn new(k: ParserErrorKind, cause: Option>) -> ParserError { - ParserError { - kind: k, - cause: cause, - } - } - -} - -impl Debug for ParserError { - - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - try!(write!(f, "{:?}", self.description())); - Ok(()) - } - -} - -impl Display for ParserError { - - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - try!(write!(f, "{}", self.description())); - Ok(()) - } - -} - -impl Error for ParserError { - - fn description(&self) -> &str { - match self.kind { - ParserErrorKind::TOMLParserErrors => "Several TOML-Parser-Errors", - ParserErrorKind::MissingMainSection => "Missing main section", - ParserErrorKind::MissingVersionInfo => "Missing version information in main section", - ParserErrorKind::NonTableInBaseTable => "A non-table was found in the base table", - ParserErrorKind::HeaderInconsistency => "The header is inconsistent", - } - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - -} - diff --git a/libimagstore/src/hook/accessor.rs b/libimagstore/src/hook/accessor.rs index 88c8153b..e9d39915 100644 --- a/libimagstore/src/hook/accessor.rs +++ b/libimagstore/src/hook/accessor.rs @@ -1,19 +1,22 @@ +use std::fmt::Debug; + use hook::result::HookResult; use store::FileLockEntry; use storeid::StoreId; -pub trait StoreIdAccessor : Send + Sync { +pub trait StoreIdAccessor : Debug + Send + Sync { fn access(&self, &StoreId) -> HookResult<()>; } -pub trait MutableHookDataAccessor : Send + Sync { +pub trait MutableHookDataAccessor : Debug + Send + Sync { fn access_mut(&self, &mut FileLockEntry) -> HookResult<()>; } -pub trait NonMutableHookDataAccessor : Send + Sync { +pub trait NonMutableHookDataAccessor : Debug + Send + Sync { fn access(&self, &FileLockEntry) -> HookResult<()>; } +#[derive(Debug)] pub enum HookDataAccessor<'a> { StoreIdAccess(&'a StoreIdAccessor), MutableAccess(&'a MutableHookDataAccessor), diff --git a/libimagstore/src/hook/aspect.rs b/libimagstore/src/hook/aspect.rs index 8a9e81c8..8c80b6cb 100644 --- a/libimagstore/src/hook/aspect.rs +++ b/libimagstore/src/hook/aspect.rs @@ -1,3 +1,5 @@ +use libimagerror::trace::trace_error; + use store::FileLockEntry; use storeid::StoreId; use hook::Hook; @@ -38,34 +40,32 @@ impl Aspect { impl StoreIdAccessor for Aspect { fn access(&self, id: &StoreId) -> HookResult<()> { - use crossbeam; - let accessors : Vec = self.hooks.iter().map(|h| h.accessor()).collect(); - if !accessors.iter().all(|a| match a { &HDA::StoreIdAccess(_) => true, _ => false }) { + if !accessors.iter().all(|a| is_match!(*a, HDA::StoreIdAccess(_))) { return Err(HE::new(HEK::AccessTypeViolation, None)); } - let threads : Vec> = accessors + accessors .iter() - .map(|accessor| { - crossbeam::scope(|scope| { - scope.spawn(|| { - match accessor { - &HDA::StoreIdAccess(accessor) => accessor.access(id), - _ => unreachable!(), - } - .map_err(|_| ()) // TODO: We're losing the error cause here - }) - }) - }) - .map(|i| i.join().map_err(|_| HE::new(HEK::HookExecutionError, None))) - .collect(); + .fold(Ok(()), |acc, accessor| { + acc.and_then(|_| { + let res = match accessor { + &HDA::StoreIdAccess(accessor) => accessor.access(id), + _ => unreachable!(), + }; - threads - .into_iter() - .fold(Ok(()), |acc, elem| { - acc.and_then(|a| { - elem.map(|_| a).map_err(|_| HE::new(HEK::HookExecutionError, None)) + match res { + Ok(res) => Ok(res), + Err(e) => { + if !e.is_aborting() { + trace_error(&e); + // ignore error if it is not aborting, as we printed it already + Ok(()) + } else { + Err(e) + } + } + } }) }) } @@ -76,63 +76,70 @@ impl MutableHookDataAccessor for Aspect { let accessors : Vec = self.hooks.iter().map(|h| h.accessor()).collect(); fn is_file_accessor(a: &HDA) -> bool { - match a { - &HDA::MutableAccess(_) => true, - &HDA::NonMutableAccess(_) => true, - _ => false, - } + is_match!(*a, HDA::MutableAccess(_) | HDA::NonMutableAccess(_)) } if !accessors.iter().all(|a| is_file_accessor(a)) { return Err(HE::new(HEK::AccessTypeViolation, None)); } - for accessor in accessors { - match accessor { - HDA::MutableAccess(accessor) => try!(accessor.access_mut(fle)), + // TODO: Naiive implementation. + // More sophisticated version would check whether there are _chunks_ of + // NonMutableAccess accessors and execute these chunks in parallel. We do not have + // performance concerns yet, so this is okay. + accessors.iter().fold(Ok(()), |acc, accessor| { + acc.and_then(|_| { + let res = match accessor { + &HDA::MutableAccess(ref accessor) => accessor.access_mut(fle), + &HDA::NonMutableAccess(ref accessor) => accessor.access(fle), + _ => unreachable!(), + }; - // TODO: Naiive implementation. - // More sophisticated version would check whether there are _chunks_ of - // NonMutableAccess accessors and execute these chunks in parallel. We do not have - // performance concerns yet, so this is okay. - HDA::NonMutableAccess(accessor) => try!(accessor.access(fle)), - _ => unreachable!(), - } - } - Ok(()) + match res { + Ok(res) => Ok(res), + Err(e) => { + if !e.is_aborting() { + trace_error(&e); + // ignore error if it is not aborting, as we printed it already + Ok(()) + } else { + Err(e) + } + } + } + }) + }) } } impl NonMutableHookDataAccessor for Aspect { fn access(&self, fle: &FileLockEntry) -> HookResult<()> { - use crossbeam; - let accessors : Vec = self.hooks.iter().map(|h| h.accessor()).collect(); - if !accessors.iter().all(|a| match a { &HDA::NonMutableAccess(_) => true, _ => false }) { + if !accessors.iter().all(|a| is_match!(*a, HDA::NonMutableAccess(_))) { return Err(HE::new(HEK::AccessTypeViolation, None)); } - let threads : Vec> = accessors + accessors .iter() - .map(|accessor| { - crossbeam::scope(|scope| { - scope.spawn(|| { - match accessor { - &HDA::NonMutableAccess(accessor) => accessor.access(fle), - _ => unreachable!(), - } - .map_err(|_| ()) // TODO: We're losing the error cause here - }) - }) - }) - .map(|i| i.join().map_err(|_| HE::new(HEK::HookExecutionError, None))) - .collect(); + .fold(Ok(()), |acc, accessor| { + acc.and_then(|_| { + let res = match accessor { + &HDA::NonMutableAccess(accessor) => accessor.access(fle), + _ => unreachable!(), + }; - threads - .into_iter() - .fold(Ok(()), |acc, elem| { - acc.and_then(|a| { - elem.map(|_| a).map_err(|_| HE::new(HEK::HookExecutionError, None)) + match res { + Ok(res) => Ok(res), + Err(e) => { + if !e.is_aborting() { + trace_error(&e); + // ignore error if it is not aborting, as we printed it already + Ok(()) + } else { + Err(e) + } + } + } }) }) } diff --git a/libimagstore/src/hook/error.rs b/libimagstore/src/hook/error.rs index a4769af3..77095844 100644 --- a/libimagstore/src/hook/error.rs +++ b/libimagstore/src/hook/error.rs @@ -1,106 +1,23 @@ -use std::error::Error; -use std::fmt::Error as FmtError; -use std::clone::Clone; -use std::fmt::{Display, Formatter}; use std::convert::Into; +generate_error_imports!(); -/** - * Kind of error - */ -#[derive(Clone, Copy, Debug)] -pub enum HookErrorKind { - HookExecutionError, - AccessTypeViolation, -} +generate_custom_error_types!(HookError, HookErrorKind, CustomData, + HookExecutionError => "Hook exec error", + AccessTypeViolation => "Hook access type violation" +); -pub trait IntoHookError { - fn into_hookerror(self) -> HookError; - fn into_hookerror_with_cause(self, cause: Box) -> HookError; -} - -impl Into for HookErrorKind { - - fn into(self) -> HookError { - HookError::new(self, None) - } - -} - -impl Into for (HookErrorKind, Box) { - - fn into(self) -> HookError { - HookError::new(self.0, Some(self.1)) - } - -} - -fn hook_error_type_as_str(e: &HookErrorKind) -> &'static str { - match e { - &HookErrorKind::HookExecutionError => "Hook exec error", - &HookErrorKind::AccessTypeViolation => "Hook access type violation", - } -} - -impl Display for HookErrorKind { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "{}", hook_error_type_as_str(self))); - Ok(()) - } - -} - -/** - * Error type - */ -#[derive(Debug)] -pub struct HookError { - err_type: HookErrorKind, - cause: Option>, +#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Copy)] +pub struct CustomData { + aborting: bool, } impl HookError { - /** - * Build a new HookError from an HookErrorKind, optionally with cause - */ - pub fn new(errtype: HookErrorKind, cause: Option>) - -> HookError - { - HookError { - err_type: errtype, - cause: cause, - } + pub fn is_aborting(&self) -> bool { + match self.custom_data { + Some(b) => b.aborting, + None => true } - - /** - * Get the error type of this HookError - */ - pub fn err_type(&self) -> HookErrorKind { - self.err_type.clone() } } - -impl Display for HookError { - - fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { - try!(write!(fmt, "[{}]", hook_error_type_as_str(&self.err_type.clone()))); - Ok(()) - } - -} - -impl Error for HookError { - - fn description(&self) -> &str { - hook_error_type_as_str(&self.err_type.clone()) - } - - fn cause(&self) -> Option<&Error> { - self.cause.as_ref().map(|e| &**e) - } - -} - - diff --git a/libimagstore/src/hook/position.rs b/libimagstore/src/hook/position.rs index 6e74436d..0ccb563e 100644 --- a/libimagstore/src/hook/position.rs +++ b/libimagstore/src/hook/position.rs @@ -1,5 +1,7 @@ #[derive(Debug, Clone)] pub enum HookPosition { + StoreUnload, + PreCreate, PostCreate, PreRetrieve, diff --git a/libimagstore/src/lazyfile.rs b/libimagstore/src/lazyfile.rs index 0b4fa031..4e62d354 100644 --- a/libimagstore/src/lazyfile.rs +++ b/libimagstore/src/lazyfile.rs @@ -1,14 +1,11 @@ - -use error::{StoreError, StoreErrorKind}; +use error::{MapErrInto, StoreError as SE, StoreErrorKind as SEK}; use std::io::{Seek, SeekFrom}; use std::path::{Path, PathBuf}; use std::fs::{File, OpenOptions, create_dir_all}; -/** - * LazyFile type - * - * A lazy file is either absent, but a path to it is available, or it is present. - */ +/// `LazyFile` type +/// +/// A lazy file is either absent, but a path to it is available, or it is present. #[derive(Debug)] pub enum LazyFile { Absent(PathBuf), @@ -34,22 +31,17 @@ impl LazyFile { /** * Get the mutable file behind a LazyFile object */ - pub fn get_file_mut(&mut self) -> Result<&mut File, StoreError> { + pub fn get_file_mut(&mut self) -> Result<&mut File, SE> { debug!("Getting lazy file: {:?}", self); let file = match *self { LazyFile::File(ref mut f) => return { // We seek to the beginning of the file since we expect each // access to the file to be in a different context f.seek(SeekFrom::Start(0)) - .map_err(|e| StoreError::new(StoreErrorKind::FileNotCreated, Some(Box::new(e)))) + .map_err_into(SEK::FileNotCreated) .map(|_| f) }, - LazyFile::Absent(ref p) => { - try!(open_file(p).map_err(|e| { - StoreError::new(StoreErrorKind::FileNotFound, - Some(Box::new(e))) - })) - } + LazyFile::Absent(ref p) => try!(open_file(p).map_err_into(SEK::FileNotFound)), }; *self = LazyFile::File(file); if let LazyFile::File(ref mut f) = *self { @@ -61,16 +53,11 @@ impl LazyFile { /** * Create a file out of this LazyFile object */ - pub fn create_file(&mut self) -> Result<&mut File, StoreError> { + pub fn create_file(&mut self) -> Result<&mut File, SE> { debug!("Creating lazy file: {:?}", self); let file = match *self { LazyFile::File(ref mut f) => return Ok(f), - LazyFile::Absent(ref p) => { - try!(create_file(p).map_err(|e| { - StoreError::new(StoreErrorKind::FileNotFound, - Some(Box::new(e))) - })) - } + LazyFile::Absent(ref p) => try!(create_file(p).map_err_into(SEK::FileNotFound)), }; *self = LazyFile::File(file); if let LazyFile::File(ref mut f) = *self { diff --git a/libimagstore/src/lib.rs b/libimagstore/src/lib.rs index 2396a672..1be684b3 100644 --- a/libimagstore/src/lib.rs +++ b/libimagstore/src/lib.rs @@ -22,6 +22,10 @@ extern crate toml; #[cfg(test)] extern crate tempdir; extern crate semver; extern crate crossbeam; +extern crate walkdir; + +#[macro_use] extern crate libimagerror; +#[macro_use] extern crate libimagutil; pub mod storeid; pub mod error; diff --git a/libimagstore/src/store.rs b/libimagstore/src/store.rs index 9d174e24..82dd1a2e 100644 --- a/libimagstore/src/store.rs +++ b/libimagstore/src/store.rs @@ -7,7 +7,6 @@ use std::sync::Arc; use std::sync::RwLock; use std::collections::BTreeMap; use std::io::{Seek, SeekFrom}; -use std::io::Write; use std::convert::From; use std::convert::Into; use std::sync::Mutex; @@ -20,21 +19,30 @@ use std::fmt::Error as FMTError; use toml::{Table, Value}; use regex::Regex; use glob::glob; +use walkdir::WalkDir; +use walkdir::Iter as WalkDirIter; use error::{ParserErrorKind, ParserError}; -use error::{StoreError, StoreErrorKind}; -use storeid::{StoreId, StoreIdIterator}; +use error::{StoreError as SE, StoreErrorKind as SEK}; +use error::MapErrInto; +use storeid::{IntoStoreId, StoreId, StoreIdIterator}; use lazyfile::LazyFile; use hook::aspect::Aspect; +use hook::error::HookErrorKind; +use hook::result::HookResult; use hook::accessor::{ MutableHookDataAccessor, - NonMutableHookDataAccessor, StoreIdAccessor}; use hook::position::HookPosition; use hook::Hook; +use libimagerror::trace::trace_error; + +use libimagerror::into::IntoError; + +use self::glob_store_iter::*; /// The Result Type returned by any interaction with the store that could fail -pub type Result = RResult; +pub type Result = RResult; #[derive(Debug, PartialEq)] @@ -52,12 +60,63 @@ struct StoreEntry { status: StoreEntryStatus, } +pub enum StoreObject { + Id(StoreId), + Collection(PathBuf), +} + +pub struct Walk { + dirwalker: WalkDirIter, +} + +impl Walk { + + fn new(mut store_path: PathBuf, mod_name: &str) -> Walk { + store_path.push(mod_name); + Walk { + dirwalker: WalkDir::new(store_path).into_iter(), + } + } +} + +impl ::std::ops::Deref for Walk { + type Target = WalkDirIter; + + fn deref(&self) -> &Self::Target { + &self.dirwalker + } +} + +impl Iterator for Walk { + type Item = StoreObject; + + fn next(&mut self) -> Option { + while let Some(something) = self.dirwalker.next() { + match something { + Ok(next) => if next.file_type().is_dir() { + return Some(StoreObject::Collection(next.path().to_path_buf())) + } else if next.file_type().is_file() { + return Some(StoreObject::Id(next.path().to_path_buf().into())) + }, + Err(e) => { + warn!("Error in Walker"); + debug!("{:?}", e); + return None; + } + } + } + + return None; + } +} + + impl StoreEntry { fn new(id: StoreId) -> StoreEntry { StoreEntry { id: id.clone(), - file: LazyFile::Absent(id), + file: LazyFile::Absent(id.into()), status: StoreEntryStatus::Present, } } @@ -72,7 +131,7 @@ impl StoreEntry { if !self.is_borrowed() { let file = self.file.get_file_mut(); if let Err(err) = file { - if err.err_type() == StoreErrorKind::FileNotFound { + if err.err_type() == SEK::FileNotFound { Ok(Entry::new(self.id.clone())) } else { Err(err) @@ -85,7 +144,7 @@ impl StoreEntry { entry } } else { - return Err(StoreError::new(StoreErrorKind::EntryAlreadyBorrowed, None)) + Err(SE::new(SEK::EntryAlreadyBorrowed, None)) } } @@ -95,10 +154,8 @@ impl StoreEntry { let file = try!(self.file.create_file()); assert_eq!(self.id, entry.location); - try!(file.set_len(0) - .map_err(|e| StoreError::new(StoreErrorKind::FileError, Some(Box::new(e))))); - file.write_all(entry.to_str().as_bytes()) - .map_err(|e| StoreError::new(StoreErrorKind::FileError, Some(Box::new(e)))) + try!(file.set_len(0).map_err_into(SEK::FileError)); + file.write_all(entry.to_str().as_bytes()).map_err_into(SEK::FileError) } else { Ok(()) } @@ -118,6 +175,8 @@ pub struct Store { * Registered hooks */ + store_unload_aspects : Arc>>, + pre_create_aspects : Arc>>, post_create_aspects : Arc>>, pre_retrieve_aspects : Arc>>, @@ -126,6 +185,8 @@ pub struct Store { post_update_aspects : Arc>>, pre_delete_aspects : Arc>>, post_delete_aspects : Arc>>, + pre_move_aspects : Arc>>, + post_move_aspects : Arc>>, /** * Internal Path->File cache map @@ -146,7 +207,7 @@ impl Store { debug!("Validating Store configuration"); if !config_is_valid(&store_config) { - return Err(StoreError::new(StoreErrorKind::ConfigurationError, None)); + return Err(SE::new(SEK::ConfigurationError, None)); } debug!("Building new Store object"); @@ -155,16 +216,19 @@ impl Store { let c = create_dir_all(location.clone()); if c.is_err() { debug!("Failed"); - return Err(StoreError::new(StoreErrorKind::StorePathCreate, - Some(Box::new(c.unwrap_err())))); - } - } else { - if location.is_file() { - debug!("Store path exists as file"); - return Err(StoreError::new(StoreErrorKind::StorePathExists, None)); + return Err(SEK::StorePathCreate.into_error_with_cause(Box::new(c.unwrap_err()))); } + } else if location.is_file() { + debug!("Store path exists as file"); + return Err(SEK::StorePathExists.into_error()); } + let store_unload_aspects = get_store_unload_aspect_names(&store_config) + .into_iter().map(|n| { + let cfg = AspectConfig::get_for(&store_config, n.clone()); + Aspect::new(n, cfg) + }).collect(); + let pre_create_aspects = get_pre_create_aspect_names(&store_config) .into_iter().map(|n| { let cfg = AspectConfig::get_for(&store_config, n.clone()); @@ -213,9 +277,24 @@ impl Store { Aspect::new(n, cfg) }).collect(); + let pre_move_aspects = get_pre_move_aspect_names(&store_config) + .into_iter().map(|n| { + let cfg = AspectConfig::get_for(&store_config, n.clone()); + Aspect::new(n, cfg) + }).collect(); + + let post_move_aspects = get_post_move_aspect_names(&store_config) + .into_iter().map(|n| { + let cfg = AspectConfig::get_for(&store_config, n.clone()); + Aspect::new(n, cfg) + }).collect(); + let store = Store { - location: location, + location: location.clone(), configuration: store_config, + + store_unload_aspects : Arc::new(Mutex::new(store_unload_aspects)), + pre_create_aspects : Arc::new(Mutex::new(pre_create_aspects)), post_create_aspects : Arc::new(Mutex::new(post_create_aspects)), pre_retrieve_aspects : Arc::new(Mutex::new(pre_retrieve_aspects)), @@ -224,6 +303,8 @@ impl Store { post_update_aspects : Arc::new(Mutex::new(post_update_aspects)), pre_delete_aspects : Arc::new(Mutex::new(pre_delete_aspects)), post_delete_aspects : Arc::new(Mutex::new(post_delete_aspects)), + pre_move_aspects : Arc::new(Mutex::new(pre_move_aspects)), + post_move_aspects : Arc::new(Mutex::new(post_move_aspects)), entries: Arc::new(RwLock::new(HashMap::new())), }; @@ -241,23 +322,29 @@ impl Store { let mut new_id = self.location.clone(); new_id.push(id); debug!("Created: '{:?}'", new_id); - new_id + StoreId::from(new_id) } /// Creates the Entry at the given location (inside the entry) - pub fn create<'a>(&'a self, id: StoreId) -> Result> { - let id = self.storify_id(id); + pub fn create<'a, S: IntoStoreId>(&'a self, id: S) -> Result> { + let id = self.storify_id(id.into_storeid()); if let Err(e) = self.execute_hooks_for_id(self.pre_create_aspects.clone(), &id) { - return Err(e); + if e.is_aborting() { + return Err(e) + .map_err_into(SEK::PreHookExecuteError) + .map_err_into(SEK::CreateCallError) + } else { + trace_error(&e); + } } - let hsmap = self.entries.write(); - if hsmap.is_err() { - return Err(StoreError::new(StoreErrorKind::LockPoisoned, None)) - } - let mut hsmap = hsmap.unwrap(); + let mut hsmap = match self.entries.write() { + Err(_) => return Err(SEK::LockPoisoned.into_error()).map_err_into(SEK::CreateCallError), + Ok(s) => s, + }; + if hsmap.contains_key(&id) { - return Err(StoreError::new(StoreErrorKind::EntryAlreadyExists, None)) + return Err(SEK::EntryAlreadyExists.into_error()).map_err_into(SEK::CreateCallError); } hsmap.insert(id.clone(), { let mut se = StoreEntry::new(id.clone()); @@ -265,67 +352,144 @@ impl Store { se }); - let mut fle = FileLockEntry::new(self, Entry::new(id.clone()), id); + let mut fle = FileLockEntry::new(self, Entry::new(id)); self.execute_hooks_for_mut_file(self.post_create_aspects.clone(), &mut fle) - .map_err(|e| StoreError::new(StoreErrorKind::PostHookExecuteError, Some(Box::new(e)))) + .map_err_into(SEK::PostHookExecuteError) .map(|_| fle) + .map_err_into(SEK::CreateCallError) } /// Borrow a given Entry. When the `FileLockEntry` is either `update`d or /// dropped, the new Entry is written to disk - pub fn retrieve<'a>(&'a self, id: StoreId) -> Result> { - let id = self.storify_id(id); + /// + /// Implicitely creates a entry in the store if there is no entry with the id `id`. For a + /// non-implicitely-create look at `Store::get`. + pub fn retrieve<'a, S: IntoStoreId>(&'a self, id: S) -> Result> { + let id = self.storify_id(id.into_storeid()); if let Err(e) = self.execute_hooks_for_id(self.pre_retrieve_aspects.clone(), &id) { - return Err(e); + if e.is_aborting() { + return Err(e) + .map_err_into(SEK::PreHookExecuteError) + .map_err_into(SEK::RetrieveCallError) + } else { + trace_error(&e); + } } self.entries .write() - .map_err(|_| StoreError::new(StoreErrorKind::LockPoisoned, None)) + .map_err(|_| SE::new(SEK::LockPoisoned, None)) .and_then(|mut es| { let mut se = es.entry(id.clone()).or_insert_with(|| StoreEntry::new(id.clone())); let entry = se.get_entry(); se.status = StoreEntryStatus::Borrowed; entry }) - .map(|e| FileLockEntry::new(self, e, id)) + .map(|e| FileLockEntry::new(self, e)) .and_then(|mut fle| { - if let Err(e) = self.execute_hooks_for_mut_file(self.post_retrieve_aspects.clone(), &mut fle) { - Err(StoreError::new(StoreErrorKind::HookExecutionError, Some(Box::new(e)))) - } else { - Ok(fle) - } - + self.execute_hooks_for_mut_file(self.post_retrieve_aspects.clone(), &mut fle) + .map_err_into(SEK::HookExecutionError) + .and(Ok(fle)) }) - } + .map_err_into(SEK::RetrieveCallError) + } + + /// Get an entry from the store if it exists. + /// + /// This executes the {pre,post}_retrieve_aspects hooks. + pub fn get<'a, S: IntoStoreId + Clone>(&'a self, id: S) -> Result>> { + if !self.storify_id(id.clone().into_storeid()).exists() { + debug!("Does not exist: {:?}", id.clone().into_storeid()); + return Ok(None); + } + self.retrieve(id).map(Some).map_err_into(SEK::GetCallError) + } + + /// Same as `Store::get()` but also tries older versions of the entry, returning an iterator + /// over all versions of the entry. + pub fn get_all_versions<'a, S: IntoStoreId>(&'a self, id: S) -> Result + { + // get PathBuf component from storeid, but not version component + fn path_component(id: S) -> Result { + let p : PathBuf = id.into_storeid().into(); + match p.to_str() { + Some(s) => { + let mut split = s.split("~"); + let path_element = match split.next() { + Some(s) => s, + None => return Err(SE::new(SEK::StorePathError, None)) + .map_err_into(SEK::GetAllVersionsCallError), + }; + + Ok(PathBuf::from(path_element)) + }, + + None => Err(SE::new(SEK::StorePathError, None)) + .map_err_into(SEK::GetAllVersionsCallError), + } + } + + fn build_glob_pattern(mut pb: PathBuf) -> Option { + pb.push("~*.*.*"); + pb.to_str().map(String::from) + } + + match path_component(id).map(build_glob_pattern) { + Err(e) => Err(SEK::StorePathError.into_error_with_cause(Box::new(e))) + .map_err_into(SEK::GetAllVersionsCallError), + Ok(None) => Err(SE::new(SEK::StorePathError, None)) + .map_err_into(SEK::GetAllVersionsCallError), + Ok(Some(pattern)) => { + glob(&pattern[..]) + .map(|paths| GlobStoreIdIterator::new(paths).into()) + .map_err_into(SEK::GlobError) + .map_err_into(SEK::GetAllVersionsCallError) + } + } + + } /// Iterate over all StoreIds for one module name pub fn retrieve_for_module(&self, mod_name: &str) -> Result { let mut path = self.path().clone(); path.push(mod_name); - if let Some(path) = path.to_str() { - let path = [ path, "/*" ].join(""); - debug!("glob()ing with '{}'", path); - glob(&path[..]) - .map(StoreIdIterator::new) - .map_err(|e| StoreError::new(StoreErrorKind::GlobError, Some(Box::new(e)))) - } else { - Err(StoreError::new(StoreErrorKind::EncodingError, None)) - } + path.to_str() + .ok_or(SE::new(SEK::EncodingError, None)) + .and_then(|path| { + let path = [ path, "/**/*" ].join(""); + debug!("glob()ing with '{}'", path); + glob(&path[..]).map_err_into(SEK::GlobError) + }) + .map(|paths| GlobStoreIdIterator::new(paths).into()) + .map_err_into(SEK::GlobError) + .map_err_into(SEK::RetrieveForModuleCallError) + } + + // Walk the store tree for the module + pub fn walk<'a>(&'a self, mod_name: &str) -> Walk { + Walk::new(self.path().clone(), mod_name) } /// Return the `FileLockEntry` and write to disk pub fn update<'a>(&'a self, mut entry: FileLockEntry<'a>) -> Result<()> { if let Err(e) = self.execute_hooks_for_mut_file(self.pre_update_aspects.clone(), &mut entry) { - return Err(e); + if e.is_aborting() { + return Err(e) + .map_err_into(SEK::PreHookExecuteError) + .map_err_into(SEK::UpdateCallError); + } else { + trace_error(&e); + } } if let Err(e) = self._update(&entry) { - return Err(e); + return Err(e).map_err_into(SEK::UpdateCallError); } self.execute_hooks_for_mut_file(self.post_update_aspects.clone(), &mut entry) + .map_err_into(SEK::PreHookExecuteError) + .map_err_into(SEK::UpdateCallError) } /// Internal method to write to the filesystem store. @@ -334,13 +498,12 @@ impl Store { /// This method assumes that entry is dropped _right after_ the call, hence /// it is not public. fn _update<'a>(&'a self, entry: &FileLockEntry<'a>) -> Result<()> { - let hsmap = self.entries.write(); - if hsmap.is_err() { - return Err(StoreError::new(StoreErrorKind::LockPoisoned, None)) - } - let mut hsmap = hsmap.unwrap(); - let mut se = try!(hsmap.get_mut(&entry.key) - .ok_or(StoreError::new(StoreErrorKind::IdNotFound, None))); + let mut hsmap = match self.entries.write() { + Err(_) => return Err(SE::new(SEK::LockPoisoned, None)), + Ok(e) => e, + }; + + let mut se = try!(hsmap.get_mut(&entry.location).ok_or(SE::new(SEK::IdNotFound, None))); assert!(se.is_borrowed(), "Tried to update a non borrowed entry."); @@ -356,49 +519,140 @@ impl Store { /// Retrieve a copy of a given entry, this cannot be used to mutate /// the one on disk - pub fn retrieve_copy(&self, id: StoreId) -> Result { - let id = self.storify_id(id); - let entries_lock = self.entries.write(); - if entries_lock.is_err() { - return Err(StoreError::new(StoreErrorKind::LockPoisoned, None)) - } - - let entries = entries_lock.unwrap(); + pub fn retrieve_copy(&self, id: S) -> Result { + let id = self.storify_id(id.into_storeid()); + let entries = match self.entries.write() { + Err(_) => { + return Err(SE::new(SEK::LockPoisoned, None)) + .map_err_into(SEK::RetrieveCopyCallError); + }, + Ok(e) => e, + }; // if the entry is currently modified by the user, we cannot drop it if entries.get(&id).map(|e| e.is_borrowed()).unwrap_or(false) { - return Err(StoreError::new(StoreErrorKind::IdLocked, None)); + return Err(SE::new(SEK::IdLocked, None)).map_err_into(SEK::RetrieveCopyCallError); } StoreEntry::new(id).get_entry() } /// Delete an entry - pub fn delete(&self, id: StoreId) -> Result<()> { - let id = self.storify_id(id); + pub fn delete(&self, id: S) -> Result<()> { + let id = self.storify_id(id.into_storeid()); if let Err(e) = self.execute_hooks_for_id(self.pre_delete_aspects.clone(), &id) { - return Err(e); + if e.is_aborting() { + return Err(e) + .map_err_into(SEK::PreHookExecuteError) + .map_err_into(SEK::DeleteCallError) + } else { + trace_error(&e); + } } - let entries_lock = self.entries.write(); - if entries_lock.is_err() { - return Err(StoreError::new(StoreErrorKind::LockPoisoned, None)) - } - - let mut entries = entries_lock.unwrap(); + let mut entries = match self.entries.write() { + Err(_) => return Err(SE::new(SEK::LockPoisoned, None)) + .map_err_into(SEK::DeleteCallError), + Ok(e) => e, + }; // if the entry is currently modified by the user, we cannot drop it if entries.get(&id).map(|e| e.is_borrowed()).unwrap_or(false) { - return Err(StoreError::new(StoreErrorKind::IdLocked, None)); + return Err(SE::new(SEK::IdLocked, None)) + .map_err_into(SEK::DeleteCallError); } // remove the entry first, then the file entries.remove(&id); if let Err(e) = remove_file(&id) { - return Err(StoreError::new(StoreErrorKind::FileError, Some(Box::new(e)))); + return Err(SEK::FileError.into_error_with_cause(Box::new(e))) + .map_err_into(SEK::DeleteCallError); } self.execute_hooks_for_id(self.post_delete_aspects.clone(), &id) + .map_err_into(SEK::PreHookExecuteError) + .map_err_into(SEK::DeleteCallError) + } + + /// Save a copy of the Entry in another place + /// Executes the post_move_aspects for the new id + pub fn save_to(&self, entry: &FileLockEntry, new_id: StoreId) -> Result<()> { + self.save_to_other_location(entry, new_id, false) + } + + /// Save an Entry in another place + /// Removes the original entry + /// Executes the post_move_aspects for the new id + pub fn save_as(&self, entry: FileLockEntry, new_id: StoreId) -> Result<()> { + self.save_to_other_location(&entry, new_id, true) + } + + fn save_to_other_location(&self, entry: &FileLockEntry, new_id: StoreId, remove_old: bool) + -> Result<()> + { + use std::fs::copy; + use std::fs::remove_file; + + let new_id = self.storify_id(new_id); + let hsmap = self.entries.write(); + if hsmap.is_err() { + return Err(SE::new(SEK::LockPoisoned, None)).map_err_into(SEK::MoveCallError) + } + if hsmap.unwrap().contains_key(&new_id) { + return Err(SE::new(SEK::EntryAlreadyExists, None)).map_err_into(SEK::MoveCallError) + } + + let old_id = entry.get_location().clone(); + + copy(old_id.clone(), new_id.clone()) + .and_then(|_| { + if remove_old { + remove_file(old_id) + } else { + Ok(()) + } + }) + .map_err_into(SEK::FileError) + .and_then(|_| self.execute_hooks_for_id(self.post_move_aspects.clone(), &new_id) + .map_err_into(SEK::PostHookExecuteError)) + .map_err_into(SEK::MoveCallError) + } + + /// Move an entry without loading + pub fn move_by_id(&self, old_id: StoreId, new_id: StoreId) -> Result<()> { + use std::fs::rename; + + let new_id = self.storify_id(new_id); + let old_id = self.storify_id(old_id); + + if let Err(e) = self.execute_hooks_for_id(self.pre_move_aspects.clone(), &old_id) { + if e.is_aborting() { + return Err(e) + .map_err_into(SEK::PreHookExecuteError) + .map_err_into(SEK::MoveByIdCallError) + } else { + trace_error(&e); + } + } + + let hsmap = self.entries.write(); + if hsmap.is_err() { + return Err(SE::new(SEK::LockPoisoned, None)) + } + if hsmap.unwrap().contains_key(&old_id) { + return Err(SE::new(SEK::EntryAlreadyBorrowed, None)); + } else { + match rename(old_id, new_id.clone()) { + Err(e) => return Err(SEK::EntryRenameError.into_error_with_cause(Box::new(e))), + _ => { + debug!("Rename worked"); + }, + } + } + + self.execute_hooks_for_id(self.pre_move_aspects.clone(), &new_id) + .map_err_into(SEK::PostHookExecuteError) + .map_err_into(SEK::MoveByIdCallError) } /// Gets the path where this store is on the disk @@ -408,7 +662,7 @@ impl Store { pub fn register_hook(&mut self, position: HookPosition, - aspect_name: &String, + aspect_name: &str, mut h: Box) -> Result<()> { @@ -417,6 +671,8 @@ impl Store { debug!(" with aspect: {:?}", aspect_name); let guard = match position { + HookPosition::StoreUnload => self.store_unload_aspects.clone(), + HookPosition::PreCreate => self.pre_create_aspects.clone(), HookPosition::PostCreate => self.post_create_aspects.clone(), HookPosition::PreRetrieve => self.pre_retrieve_aspects.clone(), @@ -427,16 +683,11 @@ impl Store { HookPosition::PostDelete => self.post_delete_aspects.clone(), }; - let guard = guard - .deref() - .lock() - .map_err(|_| StoreError::new(StoreErrorKind::LockError, None)); + let mut guard = match guard.deref().lock().map_err(|_| SE::new(SEK::LockError, None)) { + Err(e) => return Err(SEK::HookRegisterError.into_error_with_cause(Box::new(e))), + Ok(g) => g, + }; - if guard.is_err() { - return Err(StoreError::new(StoreErrorKind::HookRegisterError, - Some(Box::new(guard.err().unwrap())))); - } - let mut guard = guard.unwrap(); for mut aspect in guard.deref_mut() { if aspect.name().clone() == aspect_name.clone() { self.get_config_for_hook(h.name()).map(|config| h.set_config(config)); @@ -445,17 +696,17 @@ impl Store { } } - let annfe = StoreError::new(StoreErrorKind::AspectNameNotFoundError, None); - return Err(StoreError::new(StoreErrorKind::HookRegisterError, Some(Box::new(annfe)))); + let annfe = SEK::AspectNameNotFoundError.into_error(); + Err(SEK::HookRegisterError.into_error_with_cause(Box::new(annfe))) } fn get_config_for_hook(&self, name: &str) -> Option<&Value> { - match &self.configuration { - &Some(Value::Table(ref tabl)) => { + match self.configuration { + Some(Value::Table(ref tabl)) => { tabl.get("hooks") .map(|hook_section| { - match hook_section { - &Value::Table(ref tabl) => tabl.get(name), + match *hook_section { + Value::Table(ref tabl) => tabl.get(name), _ => None } }) @@ -468,33 +719,29 @@ impl Store { fn execute_hooks_for_id(&self, aspects: Arc>>, id: &StoreId) - -> Result<()> + -> HookResult<()> { - let guard = aspects.deref().lock(); - if guard.is_err() { return Err(StoreError::new(StoreErrorKind::PreHookExecuteError, None)) } - - guard.unwrap().deref().iter() - .fold(Ok(()), |acc, aspect| { - debug!("[Aspect][exec]: {:?}", aspect); - acc.and_then(|_| (aspect as &StoreIdAccessor).access(id)) - }) - .map_err(|e| StoreError::new(StoreErrorKind::PreHookExecuteError, Some(Box::new(e)))) + match aspects.lock() { + Err(_) => return Err(HookErrorKind::HookExecutionError.into()), + Ok(g) => g + }.iter().fold(Ok(()), |acc, aspect| { + debug!("[Aspect][exec]: {:?}", aspect); + acc.and_then(|_| (aspect as &StoreIdAccessor).access(id)) + }).map_err(Box::new).map_err(|e| HookErrorKind::HookExecutionError.into_error_with_cause(e)) } fn execute_hooks_for_mut_file(&self, aspects: Arc>>, fle: &mut FileLockEntry) - -> Result<()> + -> HookResult<()> { - let guard = aspects.deref().lock(); - if guard.is_err() { return Err(StoreError::new(StoreErrorKind::PreHookExecuteError, None)) } - - guard.unwrap().deref().iter() - .fold(Ok(()), |acc, aspect| { - debug!("[Aspect][exec]: {:?}", aspect); - acc.and_then(|_| aspect.access_mut(fle)) - }) - .map_err(|e| StoreError::new(StoreErrorKind::PreHookExecuteError, Some(Box::new(e)))) + match aspects.lock() { + Err(_) => return Err(HookErrorKind::HookExecutionError.into()), + Ok(g) => g + }.iter().fold(Ok(()), |acc, aspect| { + debug!("[Aspect][exec]: {:?}", aspect); + acc.and_then(|_| aspect.access_mut(fle)) + }).map_err(Box::new).map_err(|e| HookErrorKind::HookExecutionError.into_error_with_cause(e)) } } @@ -531,6 +778,12 @@ impl Drop for Store { * TODO: Unlock them */ fn drop(&mut self) { + let store_id = StoreId::from(self.location.clone()); + if let Err(e) = self.execute_hooks_for_id(self.store_unload_aspects.clone(), &store_id) { + debug!("Store-load hooks execution failed. Cannot create store object."); + warn!("Store Unload Hook error: {:?}", e); + } + debug!("Dropping store"); } @@ -541,15 +794,13 @@ impl Drop for Store { pub struct FileLockEntry<'a> { store: &'a Store, entry: Entry, - key: StoreId, } impl<'a> FileLockEntry<'a, > { - fn new(store: &'a Store, entry: Entry, key: StoreId) -> FileLockEntry<'a> { + fn new(store: &'a Store, entry: Entry) -> FileLockEntry<'a> { FileLockEntry { store: store, entry: entry, - key: key, } } } @@ -575,17 +826,13 @@ impl<'a> Drop for FileLockEntry<'a> { } } -/** - * EntryContent type - */ +/// `EntryContent` type pub type EntryContent = String; -/** - * EntryHeader - * - * This is basically a wrapper around toml::Table which provides convenience to the user of the - * librray. - */ +/// `EntryHeader` +/// +/// This is basically a wrapper around `toml::Table` which provides convenience to the user of the +/// library. #[derive(Debug, Clone)] pub struct EntryHeader { header: Value, @@ -625,15 +872,15 @@ impl EntryHeader { let mut parser = Parser::new(s); parser.parse() - .ok_or(ParserError::new(ParserErrorKind::TOMLParserErrors, None)) + .ok_or(ParserErrorKind::TOMLParserErrors.into()) .and_then(verify_header_consistency) .map(EntryHeader::from_table) } pub fn verify(&self) -> Result<()> { - match &self.header { - &Value::Table(ref t) => verify_header(&t), - _ => Err(StoreError::new(StoreErrorKind::HeaderTypeFailure, None)), + match self.header { + Value::Table(ref t) => verify_header(&t), + _ => Err(SE::new(SEK::HeaderTypeFailure, None)), } } @@ -667,54 +914,53 @@ impl EntryHeader { } pub fn insert_with_sep(&mut self, spec: &str, sep: char, v: Value) -> Result { - let tokens = EntryHeader::tokenize(spec, sep); - if tokens.is_err() { // return parser error if any - return tokens.map(|_| false); - } - let tokens = tokens.unwrap(); + let tokens = match EntryHeader::tokenize(spec, sep) { + Err(e) => return Err(e), + Ok(t) => t + }; - let destination = tokens.iter().last(); - if destination.is_none() { - return Err(StoreError::new(StoreErrorKind::HeaderPathSyntaxError, None)); - } - let destination = destination.unwrap(); + let destination = match tokens.iter().last() { + None => return Err(SE::new(SEK::HeaderPathSyntaxError, None)), + Some(d) => d, + }; let path_to_dest = tokens[..(tokens.len() - 1)].into(); // N - 1 tokens - let value = EntryHeader::walk_header(&mut self.header, path_to_dest); // walk N-1 tokens - if value.is_err() { - return value.map(|_| false); - } - let mut value = value.unwrap(); + + // walk N-1 tokens + let value = match EntryHeader::walk_header(&mut self.header, path_to_dest) { + Err(e) => return Err(e), + Ok(v) => v + }; // There is already an value at this place if EntryHeader::extract(value, destination).is_ok() { return Ok(false); } - match destination { - &Token::Key(ref s) => { // if the destination shall be an map key - match value { + match *destination { + Token::Key(ref s) => { // if the destination shall be an map key + match *value { /* * Put it in there if we have a map */ - &mut Value::Table(ref mut t) => { + Value::Table(ref mut t) => { t.insert(s.clone(), v); } /* * Fail if there is no map here */ - _ => return Err(StoreError::new(StoreErrorKind::HeaderPathTypeFailure, None)), + _ => return Err(SE::new(SEK::HeaderPathTypeFailure, None)), } }, - &Token::Index(i) => { // if the destination shall be an array - match value { + Token::Index(i) => { // if the destination shall be an array + match *value { /* * Put it in there if we have an array */ - &mut Value::Array(ref mut a) => { + Value::Array(ref mut a) => { a.push(v); // push to the end of the array // if the index is inside the array, we swap-remove the element at this @@ -727,7 +973,7 @@ impl EntryHeader { /* * Fail if there is no array here */ - _ => return Err(StoreError::new(StoreErrorKind::HeaderPathTypeFailure, None)), + _ => return Err(SE::new(SEK::HeaderPathTypeFailure, None)), } }, } @@ -763,35 +1009,33 @@ impl EntryHeader { } pub fn set_with_sep(&mut self, spec: &str, sep: char, v: Value) -> Result> { - let tokens = EntryHeader::tokenize(spec, sep); - if tokens.is_err() { // return parser error if any - return Err(tokens.unwrap_err()); - } - let tokens = tokens.unwrap(); + let tokens = match EntryHeader::tokenize(spec, sep) { + Err(e) => return Err(e), + Ok(t) => t, + }; debug!("tokens = {:?}", tokens); - let destination = tokens.iter().last(); - if destination.is_none() { - return Err(StoreError::new(StoreErrorKind::HeaderPathSyntaxError, None)); - } - let destination = destination.unwrap(); + let destination = match tokens.iter().last() { + None => return Err(SE::new(SEK::HeaderPathSyntaxError, None)), + Some(d) => d + }; debug!("destination = {:?}", destination); let path_to_dest = tokens[..(tokens.len() - 1)].into(); // N - 1 tokens - let value = EntryHeader::walk_header(&mut self.header, path_to_dest); // walk N-1 tokens - if value.is_err() { - return Err(value.unwrap_err()); - } - let mut value = value.unwrap(); + // walk N-1 tokens + let value = match EntryHeader::walk_header(&mut self.header, path_to_dest) { + Err(e) => return Err(e), + Ok(v) => v + }; debug!("walked value = {:?}", value); - match destination { - &Token::Key(ref s) => { // if the destination shall be an map key->value - match value { + match *destination { + Token::Key(ref s) => { // if the destination shall be an map key->value + match *value { /* * Put it in there if we have a map */ - &mut Value::Table(ref mut t) => { + Value::Table(ref mut t) => { debug!("Matched Key->Table"); return Ok(t.insert(s.clone(), v)); } @@ -801,18 +1045,18 @@ impl EntryHeader { */ _ => { debug!("Matched Key->NON-Table"); - return Err(StoreError::new(StoreErrorKind::HeaderPathTypeFailure, None)); + return Err(SE::new(SEK::HeaderPathTypeFailure, None)); } } }, - &Token::Index(i) => { // if the destination shall be an array - match value { + Token::Index(i) => { // if the destination shall be an array + match *value { /* * Put it in there if we have an array */ - &mut Value::Array(ref mut a) => { + Value::Array(ref mut a) => { debug!("Matched Index->Array"); a.push(v); // push to the end of the array @@ -832,7 +1076,7 @@ impl EntryHeader { */ _ => { debug!("Matched Index->NON-Array"); - return Err(StoreError::new(StoreErrorKind::HeaderPathTypeFailure, None)); + return Err(SE::new(SEK::HeaderPathTypeFailure, None)); }, } }, @@ -870,64 +1114,60 @@ impl EntryHeader { } pub fn read_with_sep(&self, spec: &str, splitchr: char) -> Result> { - let tokens = EntryHeader::tokenize(spec, splitchr); - if tokens.is_err() { // return parser error if any - return Err(tokens.unwrap_err()); - } - let tokens = tokens.unwrap(); + let tokens = match EntryHeader::tokenize(spec, splitchr) { + Err(e) => return Err(e), + Ok(t) => t, + }; let mut header_clone = self.header.clone(); // we clone as READing is simpler this way - let value = EntryHeader::walk_header(&mut header_clone, tokens); // walk N-1 tokens - if value.is_err() { - let e = value.unwrap_err(); - return match e.err_type() { + // walk N-1 tokens + match EntryHeader::walk_header(&mut header_clone, tokens) { + Err(e) => match e.err_type() { // We cannot find the header key, as there is no path to it - StoreErrorKind::HeaderKeyNotFound => Ok(None), + SEK::HeaderKeyNotFound => Ok(None), _ => Err(e), - }; + }, + Ok(v) => Ok(Some(v.clone())), } - Ok(Some(value.unwrap().clone())) } pub fn delete(&mut self, spec: &str) -> Result> { - let tokens = EntryHeader::tokenize(spec, '.'); - if tokens.is_err() { // return parser error if any - return Err(tokens.unwrap_err()); - } - let tokens = tokens.unwrap(); + let tokens = match EntryHeader::tokenize(spec, '.') { + Err(e) => return Err(e), + Ok(t) => t + }; - let destination = tokens.iter().last(); - if destination.is_none() { - return Err(StoreError::new(StoreErrorKind::HeaderPathSyntaxError, None)); - } - let destination = destination.unwrap(); + let destination = match tokens.iter().last() { + None => return Err(SE::new(SEK::HeaderPathSyntaxError, None)), + Some(d) => d + }; debug!("destination = {:?}", destination); let path_to_dest = tokens[..(tokens.len() - 1)].into(); // N - 1 tokens - let value = EntryHeader::walk_header(&mut self.header, path_to_dest); // walk N-1 tokens - if value.is_err() { - return Err(value.unwrap_err()); - } - let mut value = value.unwrap(); + // walk N-1 tokens + let mut value = match EntryHeader::walk_header(&mut self.header, path_to_dest) { + Err(e) => return Err(e), + Ok(v) => v + }; debug!("walked value = {:?}", value); - match destination { - &Token::Key(ref s) => { // if the destination shall be an map key->value - match value { - &mut Value::Table(ref mut t) => { + match *destination { + Token::Key(ref s) => { // if the destination shall be an map key->value + match *value { + Value::Table(ref mut t) => { debug!("Matched Key->Table, removing {:?}", s); return Ok(t.remove(s)); }, _ => { debug!("Matched Key->NON-Table"); - return Err(StoreError::new(StoreErrorKind::HeaderPathTypeFailure, None)); + return Err(SE::new(SEK::HeaderPathTypeFailure, None)); } } }, - &Token::Index(i) => { // if the destination shall be an array - match value { - &mut Value::Array(ref mut a) => { + Token::Index(i) => { // if the destination shall be an array + match *value { + Value::Array(ref mut a) => { // if the index is inside the array, we swap-remove the element at this // index if a.len() > i { @@ -939,7 +1179,7 @@ impl EntryHeader { }, _ => { debug!("Matched Index->NON-Array"); - return Err(StoreError::new(StoreErrorKind::HeaderPathTypeFailure, None)); + return Err(SE::new(SEK::HeaderPathTypeFailure, None)); }, } }, @@ -977,33 +1217,33 @@ impl EntryHeader { walk_iter(Ok(v), &mut tokens.into_iter()) } - fn extract_from_table<'a>(v: &'a mut Value, s: &String) -> Result<&'a mut Value> { - match v { - &mut Value::Table(ref mut t) => { + fn extract_from_table<'a>(v: &'a mut Value, s: &str) -> Result<&'a mut Value> { + match *v { + Value::Table(ref mut t) => { t.get_mut(&s[..]) - .ok_or(StoreError::new(StoreErrorKind::HeaderKeyNotFound, None)) + .ok_or(SE::new(SEK::HeaderKeyNotFound, None)) }, - _ => Err(StoreError::new(StoreErrorKind::HeaderPathTypeFailure, None)), + _ => Err(SE::new(SEK::HeaderPathTypeFailure, None)), } } fn extract_from_array(v: &mut Value, i: usize) -> Result<&mut Value> { - match v { - &mut Value::Array(ref mut a) => { + match *v { + Value::Array(ref mut a) => { if a.len() < i { - Err(StoreError::new(StoreErrorKind::HeaderKeyNotFound, None)) + Err(SE::new(SEK::HeaderKeyNotFound, None)) } else { Ok(&mut a[i]) } }, - _ => Err(StoreError::new(StoreErrorKind::HeaderPathTypeFailure, None)), + _ => Err(SE::new(SEK::HeaderPathTypeFailure, None)), } } fn extract<'a>(v: &'a mut Value, token: &Token) -> Result<&'a mut Value> { - match token { - &Token::Key(ref s) => EntryHeader::extract_from_table(v, s), - &Token::Index(i) => EntryHeader::extract_from_array(v, i), + match *token { + Token::Key(ref s) => EntryHeader::extract_from_table(v, s), + Token::Index(i) => EntryHeader::extract_from_array(v, i), } } @@ -1044,28 +1284,27 @@ fn build_default_header() -> Value { // BTreeMap } fn verify_header(t: &Table) -> Result<()> { if !has_main_section(t) { - Err(StoreError::from(ParserError::new(ParserErrorKind::MissingMainSection, None))) + Err(SE::from(ParserErrorKind::MissingMainSection.into_error())) } else if !has_imag_version_in_main_section(t) { - Err(StoreError::from(ParserError::new(ParserErrorKind::MissingVersionInfo, None))) + Err(SE::from(ParserErrorKind::MissingVersionInfo.into_error())) } else if !has_only_tables(t) { debug!("Could not verify that it only has tables in its base table"); - Err(StoreError::from(ParserError::new(ParserErrorKind::NonTableInBaseTable, None))) + Err(SE::from(ParserErrorKind::NonTableInBaseTable.into_error())) } else { Ok(()) } } fn verify_header_consistency(t: Table) -> EntryResult { - if let Err(e) = verify_header(&t) { - Err(ParserError::new(ParserErrorKind::HeaderInconsistency, Some(Box::new(e)))) - } else { - Ok(t) - } + verify_header(&t) + .map_err(Box::new) + .map_err(|e| ParserErrorKind::HeaderInconsistency.into_error_with_cause(e)) + .map(|_| t) } fn has_only_tables(t: &Table) -> bool { debug!("Verifying that table has only tables"); - t.iter().all(|(_, x)| if let &Value::Table(_) = x { true } else { false }) + t.iter().all(|(_, x)| if let Value::Table(_) = *x { true } else { false }) } fn has_main_section(t: &Table) -> bool { @@ -1080,20 +1319,18 @@ fn has_main_section(t: &Table) -> bool { fn has_imag_version_in_main_section(t: &Table) -> bool { use semver::Version; - match t.get("imag").unwrap() { - &Value::Table(ref sec) => { + match *t.get("imag").unwrap() { + Value::Table(ref sec) => { sec.get("version") .and_then(|v| { - match v { - &Value::String(ref s) => { - Some(Version::parse(&s[..]).is_ok()) - }, - _ => Some(false), + match *v { + Value::String(ref s) => Some(Version::parse(&s[..]).is_ok()), + _ => Some(false), } }) .unwrap_or(false) } - _ => false, + _ => false, } } @@ -1119,7 +1356,7 @@ impl Entry { } } - pub fn from_file(loc: StoreId, file: &mut File) -> Result { + pub fn from_file(loc: S, file: &mut File) -> Result { let text = { use std::io::Read; let mut s = String::new(); @@ -1129,7 +1366,7 @@ impl Entry { Self::from_str(loc, &text[..]) } - pub fn from_str(loc: StoreId, s: &str) -> Result { + pub fn from_str(loc: S, s: &str) -> Result { debug!("Building entry from string"); lazy_static! { static ref RE: Regex = Regex::new(r"(?smx) @@ -1140,25 +1377,22 @@ impl Entry { ").unwrap(); } - let matches = RE.captures(s); + let matches = match RE.captures(s) { + None => return Err(SE::new(SEK::MalformedEntry, None)), + Some(s) => s, + }; - if matches.is_none() { - return Err(StoreError::new(StoreErrorKind::MalformedEntry, None)); - } + let header = match matches.name("header") { + None => return Err(SE::new(SEK::MalformedEntry, None)), + Some(s) => s + }; - let matches = matches.unwrap(); - - let header = matches.name("header"); let content = matches.name("content").unwrap_or(""); - if header.is_none() { - return Err(StoreError::new(StoreErrorKind::MalformedEntry, None)); - } - debug!("Header and content found. Yay! Building Entry object now"); Ok(Entry { - location: loc, - header: try!(EntryHeader::parse(header.unwrap())), + location: loc.into_storeid(), + header: try!(EntryHeader::parse(header)), content: content.into(), }) } @@ -1195,6 +1429,62 @@ impl Entry { } +mod glob_store_iter { + use std::fmt::{Debug, Formatter}; + use std::fmt::Error as FmtError; + use glob::Paths; + use storeid::StoreId; + use storeid::StoreIdIterator; + + pub struct GlobStoreIdIterator { + paths: Paths, + } + + impl Debug for GlobStoreIdIterator { + + fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> { + write!(fmt, "GlobStoreIdIterator") + } + + } + + impl Into for GlobStoreIdIterator { + + fn into(self) -> StoreIdIterator { + StoreIdIterator::new(Box::new(self)) + } + + } + + impl GlobStoreIdIterator { + + pub fn new(paths: Paths) -> GlobStoreIdIterator { + GlobStoreIdIterator { + paths: paths, + } + } + + } + + impl Iterator for GlobStoreIdIterator { + type Item = StoreId; + + fn next(&mut self) -> Option { + self.paths.next().and_then(|o| { + match o { + Ok(o) => Some(o), + Err(e) => { + debug!("GlobStoreIdIterator error: {:?}", e); + None + }, + } + }).map(|p| StoreId::from(p)) + } + + } + +} + #[cfg(test)] mod test { diff --git a/libimagstore/src/storeid.rs b/libimagstore/src/storeid.rs index 7c5940c1..147b80cb 100644 --- a/libimagstore/src/storeid.rs +++ b/libimagstore/src/storeid.rs @@ -1,16 +1,69 @@ use std::path::PathBuf; -use glob::Paths; +use std::path::Path; +use std::borrow::Borrow; +use std::ops::Deref; + use semver::Version; use std::fmt::{Debug, Formatter}; use std::fmt::Error as FmtError; use std::result::Result as RResult; -use error::{StoreError, StoreErrorKind}; +use error::StoreErrorKind as SEK; use store::Result; use store::Store; /// The Index into the Store -pub type StoreId = PathBuf; +#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)] +pub struct StoreId(PathBuf); + +impl Into for StoreId { + + fn into(self) -> PathBuf { + self.0 + } + +} + +impl Deref for StoreId { + type Target = PathBuf; + + fn deref(&self) -> &PathBuf { + &self.0 + } + +} + +impl From for StoreId { + + fn from(pb: PathBuf) -> StoreId { + StoreId(pb) + } + +} + +impl From for StoreId { + + fn from(string: String) -> StoreId { + StoreId(string.into()) + } + +} + +impl AsRef for StoreId { + + fn as_ref(&self) -> &Path { + self.0.as_ref() + } + +} + +impl Borrow for StoreId { + + fn borrow(&self) -> &Path { + self.0.borrow() + } + +} /// This Trait allows you to convert various representations to a single one /// suitable for usage in the Store @@ -19,6 +72,12 @@ pub trait IntoStoreId { } impl IntoStoreId for PathBuf { + fn into_storeid(self) -> StoreId { + StoreId(self) + } +} + +impl IntoStoreId for StoreId { fn into_storeid(self) -> StoreId { self } @@ -26,18 +85,18 @@ impl IntoStoreId for PathBuf { pub fn build_entry_path(store: &Store, path_elem: &str) -> Result { debug!("Checking path element for version"); - if path_elem.split("~").last().map(|v| Version::parse(v).is_err()).unwrap_or(false) { + if path_elem.split('~').last().map_or(false, |v| Version::parse(v).is_err()) { debug!("Version cannot be parsed from {:?}", path_elem); debug!("Path does not contain version!"); - return Err(StoreError::new(StoreErrorKind::StorePathLacksVersion, None)); + return Err(SEK::StorePathLacksVersion.into()); } debug!("Version checking succeeded"); debug!("Building path from {:?}", path_elem); let mut path = store.path().clone(); - if path_elem.chars().next() == Some('/') { - path.push(&path_elem[1..path_elem.len()]); + if path_elem.starts_with('/') { + path.push(&path_elem[1..]); } else { path.push(path_elem); } @@ -62,6 +121,8 @@ macro_rules! module_entry_path_mod { use std::path::Path; use std::path::PathBuf; + use $crate::storeid::StoreId; + /// A Struct giving you the ability to choose store entries assigned /// to it. /// @@ -86,7 +147,7 @@ macro_rules! module_entry_path_mod { impl $crate::storeid::IntoStoreId for ModuleEntryPath { fn into_storeid(self) -> $crate::storeid::StoreId { - self.0 + StoreId::from(self.0) } } } @@ -94,7 +155,7 @@ macro_rules! module_entry_path_mod { } pub struct StoreIdIterator { - paths: Paths, + iter: Box>, } impl Debug for StoreIdIterator { @@ -107,9 +168,9 @@ impl Debug for StoreIdIterator { impl StoreIdIterator { - pub fn new(paths: Paths) -> StoreIdIterator { + pub fn new(iter: Box>) -> StoreIdIterator { StoreIdIterator { - paths: paths, + iter: iter, } } @@ -119,7 +180,7 @@ impl Iterator for StoreIdIterator { type Item = StoreId; fn next(&mut self) -> Option { - self.paths.next().and_then(|o| o.ok()) + self.iter.next() } } diff --git a/libimagstorestdhook/Cargo.toml b/libimagstorestdhook/Cargo.toml index 43bf50ed..31a1e790 100644 --- a/libimagstorestdhook/Cargo.toml +++ b/libimagstorestdhook/Cargo.toml @@ -5,8 +5,8 @@ authors = ["Matthias Beyer "] [dependencies] toml = "0.1.25" -log = "0.3.5" -fs2 = "0.2.3" +log = "0.3" +fs2 = "0.2" [dependencies.libimagstore] path = "../libimagstore" @@ -14,6 +14,6 @@ path = "../libimagstore" [dependencies.libimagentrylink] path = "../libimagentrylink" -[dependencies.libimagutil] -path = "../libimagutil" +[dependencies.libimagerror] +path = "../libimagerror" diff --git a/libimagstorestdhook/src/debug.rs b/libimagstorestdhook/src/debug.rs index 2a68529e..9f7bd63e 100644 --- a/libimagstorestdhook/src/debug.rs +++ b/libimagstorestdhook/src/debug.rs @@ -43,14 +43,15 @@ impl HookDataAccessorProvider for DebugHook { use libimagstore::hook::accessor::HookDataAccessor as HDA; match self.position { - HP::PreCreate => HDA::StoreIdAccess(&self.accessor), - HP::PostCreate => HDA::MutableAccess(&self.accessor), - HP::PreRetrieve => HDA::StoreIdAccess(&self.accessor), - HP::PostRetrieve => HDA::MutableAccess(&self.accessor), - HP::PreUpdate => HDA::MutableAccess(&self.accessor), - HP::PostUpdate => HDA::MutableAccess(&self.accessor), - HP::PreDelete => HDA::StoreIdAccess(&self.accessor), + HP::StoreUnload | + HP::PreCreate | + HP::PreRetrieve | + HP::PreDelete | HP::PostDelete => HDA::StoreIdAccess(&self.accessor), + HP::PostCreate | + HP::PostRetrieve | + HP::PreUpdate | + HP::PostUpdate => HDA::MutableAccess(&self.accessor), } } diff --git a/libimagstorestdhook/src/flock.rs b/libimagstorestdhook/src/flock.rs index 9ba0fe41..2386180a 100644 --- a/libimagstorestdhook/src/flock.rs +++ b/libimagstorestdhook/src/flock.rs @@ -51,9 +51,9 @@ pub enum Action { } fn action_to_str(a: &Action) -> &'static str { - match a { - &Action::Lock => "lock", - &Action::Unlock => "unlock", + match *a { + Action::Lock => "lock", + Action::Unlock => "unlock", } } diff --git a/libimagstorestdhook/src/lib.rs b/libimagstorestdhook/src/lib.rs index 05e92f75..c53cbc52 100644 --- a/libimagstorestdhook/src/lib.rs +++ b/libimagstorestdhook/src/lib.rs @@ -20,7 +20,7 @@ extern crate fs2; extern crate libimagstore; extern crate libimagentrylink; -extern crate libimagutil; +extern crate libimagerror; pub mod debug; pub mod flock; diff --git a/libimagstorestdhook/src/linkverify.rs b/libimagstorestdhook/src/linkverify.rs index be2501c0..e97981b7 100644 --- a/libimagstorestdhook/src/linkverify.rs +++ b/libimagstorestdhook/src/linkverify.rs @@ -9,7 +9,7 @@ use libimagstore::hook::accessor::NonMutableHookDataAccessor; use libimagstore::hook::result::HookResult; use libimagstore::store::FileLockEntry; use libimagentrylink::internal::InternalLinker; -use libimagutil::trace::trace_error; +use libimagerror::trace::trace_error; #[derive(Debug, Clone)] pub struct LinkedEntriesExistHook { @@ -57,10 +57,8 @@ impl NonMutableHookDataAccessor for LinkedEntriesExistHook { path.push(link); if !path.exists() { warn!("File link does not exist: {:?} -> {:?}", fle.get_location(), path); - } else { - if !path.is_file() { - warn!("File link is not a file: {:?} -> {:?}", fle.get_location(), path); - } + } else if !path.is_file() { + warn!("File link is not a file: {:?} -> {:?}", fle.get_location(), path); } } }) diff --git a/libimagtimeui/Cargo.toml b/libimagtimeui/Cargo.toml new file mode 100644 index 00000000..f4ea5efa --- /dev/null +++ b/libimagtimeui/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "libimagtimeui" +version = "0.1.0" +authors = ["Matthias Beyer "] + +[dependencies] +clap = "2.1.1" +lazy_static = "0.1" +log = "0.3" +chrono = "0.2" +regex = "0.1" + +[dependencies.libimagerror] +path = "../libimagerror" + diff --git a/libimagtimeui/src/cli.rs b/libimagtimeui/src/cli.rs new file mode 100644 index 00000000..86a69f5b --- /dev/null +++ b/libimagtimeui/src/cli.rs @@ -0,0 +1,23 @@ +use clap::{Arg, ArgMatches, App, SubCommand}; + +pub fn build_datetime_cli_component<'a, 'b>() -> Arg<'a, 'b> { + Arg::with_name(datetime_arg_name()) + .short(datetime_arg_short()) + .long(datetime_arg_long()) + .takes_value(true) + .multiple(false) + .help("Specify a DateTime") +} + +pub fn datetime_arg_name() -> &'static str { + "datetime" +} + +pub fn datetime_arg_long() -> &'static str { + "datetime" +} + +pub fn datetime_arg_short() -> &'static str { + "T" +} + diff --git a/libimagtimeui/src/date.rs b/libimagtimeui/src/date.rs new file mode 100644 index 00000000..fb2e57cc --- /dev/null +++ b/libimagtimeui/src/date.rs @@ -0,0 +1,110 @@ +use chrono::naive::date::NaiveDate as ChronoNaiveDate; + +use parse::Parse; + +pub struct Date { + year: i32, + month: u32, + day: u32, +} + +impl Date { + + pub fn new(year: i32, month: u32, day: u32) -> Date { + Date { + year: year, + month: month, + day: day, + } + } + + pub fn year(&self) -> i32 { + self.year + } + + pub fn month(&self) -> u32 { + self.month + } + + pub fn day(&self) -> u32 { + self.day + } + +} + +impl Into for Date { + + fn into(self) -> ChronoNaiveDate { + ChronoNaiveDate::from_ymd(self.year, self.month, self.day) + } + +} + +impl Parse for Date { + + /// Parse the date part of the full string into a Date object + fn parse(s: &str) -> Option { + use std::str::FromStr; + use regex::Regex; + use parse::time_parse_regex; + + lazy_static! { + static ref R: Regex = Regex::new(time_parse_regex()).unwrap(); + } + + R.captures(s) + .and_then(|capts| { + let year = capts.name("Y").and_then(|o| FromStr::from_str(o).ok()); + let month = capts.name("M").and_then(|o| FromStr::from_str(o).ok()); + let day = capts.name("D").and_then(|o| FromStr::from_str(o).ok()); + + if year.is_none() { + debug!("No year"); + return None; + } + if month.is_none() { + debug!("No month"); + return None; + } + if day.is_none() { + debug!("No day"); + return None; + } + + Some(Date::new(year.unwrap(), month.unwrap(), day.unwrap())) + }) + + } + +} + +#[cfg(test)] +mod test { + use super::Date; + use parse::Parse; + + #[test] + fn test_valid() { + let s = "2016-02-01"; + let d = Date::parse(s); + + assert!(d.is_some()); + let d = d.unwrap(); + + assert_eq!(2016, d.year()); + assert_eq!(2, d.month()); + assert_eq!(1, d.day()); + } + + #[test] + fn test_invalid() { + assert!(Date::parse("2016-021-01").is_none()); + assert!(Date::parse("2016-02-012").is_none()); + assert!(Date::parse("2016-02-0").is_none()); + assert!(Date::parse("2016-0-02").is_none()); + assert!(Date::parse("2016-02").is_none()); + assert!(Date::parse("2016-2").is_none()); + } + +} + diff --git a/libimagtimeui/src/datetime.rs b/libimagtimeui/src/datetime.rs new file mode 100644 index 00000000..b78a7436 --- /dev/null +++ b/libimagtimeui/src/datetime.rs @@ -0,0 +1,48 @@ +use chrono::naive::datetime::NaiveDateTime as ChronoNaiveDateTime; + +use parse::Parse; +use date::Date; +use time::Time; + +pub struct DateTime { + date: Date, + time: Time, +} + +impl DateTime { + + pub fn new(date: Date, time: Time) -> DateTime { + DateTime { + date: date, + time: time + } + } + + pub fn date(&self) -> &Date { + &self.date + } + + pub fn time(&self) -> &Time { + &self.time + } + +} + +impl Into for DateTime { + + fn into(self) -> ChronoNaiveDateTime { + ChronoNaiveDateTime::new(self.date.into(), self.time.into()) + } + +} + +impl Parse for DateTime { + + fn parse(s: &str) -> Option { + Date::parse(s) + .and_then(|d| Time::parse(s).map(|t| (d, t))) + .map(|(d, t)| DateTime::new(d, t)) + } + +} + diff --git a/libimagtimeui/src/lib.rs b/libimagtimeui/src/lib.rs new file mode 100644 index 00000000..4486eef9 --- /dev/null +++ b/libimagtimeui/src/lib.rs @@ -0,0 +1,14 @@ +extern crate chrono; +extern crate clap; +extern crate regex; +#[macro_use] extern crate lazy_static; +#[macro_use] extern crate log; + +#[macro_use] extern crate libimagerror; + +pub mod cli; +pub mod date; +pub mod datetime; +pub mod parse; +pub mod time; + diff --git a/libimagtimeui/src/parse.rs b/libimagtimeui/src/parse.rs new file mode 100644 index 00000000..8898e184 --- /dev/null +++ b/libimagtimeui/src/parse.rs @@ -0,0 +1,10 @@ +pub trait Parse : Sized { + + fn parse(s: &str) -> Option; + +} + +pub fn time_parse_regex() -> &'static str { + r#"(?P\d{4})-(?P\d{2})-(?P\d{2})(T(?P\d{2})(:(?P\d{2})(:(?P\d{2}))?)?)?$"# +} + diff --git a/libimagtimeui/src/time.rs b/libimagtimeui/src/time.rs new file mode 100644 index 00000000..7c494bf8 --- /dev/null +++ b/libimagtimeui/src/time.rs @@ -0,0 +1,130 @@ +use chrono::naive::time::NaiveTime as ChronoNaiveTime; + +use parse::Parse; + +pub struct Time { + hour: u32, + minute: u32, + second: u32, +} + +impl Time { + + pub fn new(hour: u32, minute: u32, second: u32) -> Time { + Time { + hour: hour, + minute: minute, + second: second + } + } + + pub fn hour(&self) -> u32 { + self.hour + } + + pub fn minute(&self) -> u32 { + self.minute + } + + pub fn second(&self) -> u32 { + self.second + } + +} + +impl Into for Time { + + fn into(self) -> ChronoNaiveTime { + ChronoNaiveTime::from_hms(self.hour, self.minute, self.second) + } + +} + +impl Parse for Time { + + fn parse(s: &str) -> Option