Merge branch 'master' of https://github.com/matthiasbeyer/imag into imag-task

This commit is contained in:
mario 2016-07-04 13:45:45 +02:00
commit 84deb2fe54
149 changed files with 4492 additions and 2281 deletions

View file

@ -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 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. 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
Finding an issue is simple: We have Finding an issue is simple: We have
@ -95,7 +120,167 @@ ask questions as well!
Feel free to reach out via mail. 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 ## FAQ
_to be written_ _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

18
Makefile Normal file
View file

@ -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/

View file

@ -1,7 +1,7 @@
# imag # imag
Imag is a CLI PIM suite with a nice API-ish commandline interface, so you can 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 ## Goal
@ -89,26 +89,26 @@ provided (as the libraries are work-in-progress).
### Building ### 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-proj-dir>/imag-<module> $> make
$> cargo build ...
``` $> ls out/
It may be tiresome to build all the modules by hand, but one can do something imag-counter imag-link imag-notes imag-store imag-tag imag-view
like this:
```
$> for dir in \
>$(find ./ -maxdepth 1 -path "./imag-*" -name "imag-*" -type d)
>do
>pushd $dir; cargo build; popd
>done
``` ```
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 ### Running
To run imag, simply call `./bin/imag`. This script has a function to search for 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`. modules, which utilizes an environment variable called `IMAG_IS_THE_SHIT`.
To run imag with all components: To run imag with all components:
``` ```
$> IMAG_IS_THE_SHIT=$(pwd) ./bin/imag $> IMAG_IS_THE_SHIT=$(pwd) ./bin/imag
``` ```

10
bin/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "imag"
version = "0.1.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies]
version = "2.0"
walkdir = "0.1.5"
crossbeam = "0.2.9"

View file

@ -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 <<EOS
Usage: imag [--version | --versions | -h | --help] <command> <args...>
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 <command> <args>"
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 $*

187
bin/src/main.rs Normal file
View file

@ -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<String>) {
println!(r#"
_
(_)_ __ ___ __ _ __ _
| | '_ \` _ \/ _\`|/ _\`|
| | | | | | | (_| | (_| |
|_|_| |_| |_|\__,_|\__, |
|___/
-------------------------
Usage: imag [--version | --versions | -h | --help] <command> <args...>
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 <command> <args>'
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<String> {
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<ScopedJoinHandle<Vec<String>>> = 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<String> {
env::args().skip(1).filter(|x| !x.starts_with("-")).next()
}
fn find_args(command: &str) -> Vec<String> {
env::args()
.skip(1)
.position(|e| e == command)
.map(|pos| env::args().skip(pos + 2).collect::<Vec<String>>())
.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);
}
}
}
}
},
}
}

View file

@ -11,6 +11,7 @@ export MAKE_FLAGS=--no-print-directory
export OUT=$(shell pwd)/bin export OUT=$(shell pwd)/bin
export OUT_PDF=$(OUT)/pdf/ export OUT_PDF=$(OUT)/pdf/
export OUT_HTML=$(OUT)/html/ export OUT_HTML=$(OUT)/html/
export OUT_MAN=$(OUT)/man/
DOCUMENT_CLASS=article DOCUMENT_CLASS=article
SETTING_FONTSIZE=11pt SETTING_FONTSIZE=11pt
@ -52,6 +53,10 @@ DOCUMENT_SETTINGS_HTML= \
--table-of-contents \ --table-of-contents \
--webtex --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_HTML=$(PANDOC) $(PANDOC_PARAMS) $(DOCUMENT_SETTINGS_HTML)
export PANDOC_CC_MAN=$(PANDOC) $(PANDOC_PARAMS) $(DOCUMENT_SETTINGS_MAN)
TARGET_PDF=$(OUT_PDF)/paper.pdf TARGET_PDF=$(OUT_PDF)/paper.pdf
TARGET_HTML=$(OUT_HTML)/index.html TARGET_HTML=$(OUT_HTML)/index.html
TARGET_MAN=$(OUT_MAN)/imag.5
# #
# #
# Tasks # Tasks
@ -116,6 +124,11 @@ $(OUT_PDF): $(OUT)
@$(ECHO) "\t[MKDIR ] $@" @$(ECHO) "\t[MKDIR ] $@"
@$(MKDIR) $(OUT_PDF) @$(MKDIR) $(OUT_PDF)
# create man out directory
$(OUT_MAN): $(OUT)
@$(ECHO) "\t[MKDIR ] $@"
@$(MKDIR) $(OUT_MAN)
# cleanup task # cleanup task
clean: clean:
@$(ECHO) "\t[RM ] $@" @$(ECHO) "\t[RM ] $@"
@ -137,5 +150,12 @@ $(TARGET_HTML): $(OUT_HTML)
--template $(TEMPLATES)/default.html5 \ --template $(TEMPLATES)/default.html5 \
$(SRC) -o $@ $(SRC) -o $@
man: $(TARGET_MAN)
$(TARGET_MAN): $(OUT_MAN)
@$(ECHO) "\t[PANDOC] man"
@$(PANDOC_CC_MAN) \
$(SRC) -o $@
.PHONY: $(TARGET_PDF) $(TARGET_HTML) .PHONY: $(TARGET_PDF) $(TARGET_HTML)

View file

@ -5,12 +5,15 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies] [dependencies]
clap = "2.1.1" clap = "2.1.1"
log = "0.3.5" log = "0.3"
version = "2.0.1" version = "2.0.1"
[dependencies.libimagrt] [dependencies.libimagrt]
path = "../libimagrt" path = "../libimagrt"
[dependencies.libimagerror]
path = "../libimagerror"
[dependencies.libimagutil] [dependencies.libimagutil]
path = "../libimagutil" path = "../libimagutil"

View file

@ -2,7 +2,7 @@ use std::str::FromStr;
use std::process::exit; use std::process::exit;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
use libimagcounter::counter::Counter; use libimagcounter::counter::Counter;
pub fn create(rt: &Runtime) { pub fn create(rt: &Runtime) {
@ -17,7 +17,7 @@ pub fn create(rt: &Runtime) {
.and_then(|i| FromStr::from_str(i).ok()) .and_then(|i| FromStr::from_str(i).ok())
.unwrap_or(0); .unwrap_or(0);
match Counter::new(rt.store(), String::from(name.clone()), init) { match Counter::new(rt.store(), String::from(name), init) {
Err(e) => { Err(e) => {
warn!("Could not create Counter '{}' with initial value '{}'", name, init); warn!("Could not create Counter '{}' with initial value '{}'", name, init);
trace_error(&e); trace_error(&e);

View file

@ -1,7 +1,7 @@
use std::process::exit; use std::process::exit;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
use libimagcounter::counter::Counter; use libimagcounter::counter::Counter;
pub fn delete(rt: &Runtime) { pub fn delete(rt: &Runtime) {

View file

@ -10,7 +10,7 @@ use libimagcounter::counter::Counter;
use libimagcounter::error::CounterError; use libimagcounter::error::CounterError;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagutil::key_value_split::IntoKeyValue; use libimagutil::key_value_split::IntoKeyValue;
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
type Result<T> = RResult<T, CounterError>; type Result<T> = RResult<T, CounterError>;
@ -51,7 +51,7 @@ pub fn interactive(rt: &Runtime) {
exit(1); exit(1);
} }
let cont = if input.len() > 0 { let cont = if !input.is_empty() {
let increment = match input.chars().next() { Some('-') => false, _ => true }; let increment = match input.chars().next() { Some('-') => false, _ => true };
input.chars().all(|chr| { input.chars().all(|chr| {
match pairs.get_mut(&chr) { match pairs.get_mut(&chr) {
@ -94,8 +94,8 @@ pub fn interactive(rt: &Runtime) {
fn has_quit_binding(pairs: &BTreeMap<char, Binding>) -> bool { fn has_quit_binding(pairs: &BTreeMap<char, Binding>) -> bool {
pairs.iter() pairs.iter()
.any(|(_, bind)| { .any(|(_, bind)| {
match bind { match *bind {
&Binding::Function(ref name, _) => name == "quit", Binding::Function(ref name, _) => name == "quit",
_ => false, _ => false,
} }
}) })
@ -109,8 +109,8 @@ enum Binding<'a> {
impl<'a> Display for Binding<'a> { impl<'a> Display for Binding<'a> {
fn fmt(&self, fmt: &mut Formatter) -> RResult<(), Error> { fn fmt(&self, fmt: &mut Formatter) -> RResult<(), Error> {
match self { match *self {
&Binding::Counter(ref c) => { Binding::Counter(ref c) => {
match c.name() { match c.name() {
Ok(name) => { Ok(name) => {
try!(write!(fmt, "{}", 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),
} }
} }

View file

@ -1,5 +1,5 @@
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
use libimagcounter::counter::Counter; use libimagcounter::counter::Counter;
pub fn list(rt: &Runtime) { pub fn list(rt: &Runtime) {
@ -16,13 +16,10 @@ pub fn list(rt: &Runtime) {
if name.is_err() { if name.is_err() {
trace_error(&name.unwrap_err()); trace_error(&name.unwrap_err());
} else if value.is_err() {
trace_error(&value.unwrap_err());
} else { } else {
println!("{} - {}", name.unwrap(), value.unwrap());
if value.is_err() {
trace_error(&value.unwrap_err());
} else {
println!("{} - {}", name.unwrap(), value.unwrap());
}
} }
}) })
.map_err(|e| trace_error(&e)) .map_err(|e| trace_error(&e))

View file

@ -19,14 +19,15 @@ extern crate clap;
extern crate libimagcounter; extern crate libimagcounter;
extern crate libimagrt; extern crate libimagrt;
extern crate libimagerror;
extern crate libimagutil; extern crate libimagutil;
use std::process::exit; use std::process::exit;
use std::str::FromStr; use std::str::FromStr;
use libimagrt::runtime::Runtime; use libimagrt::setup::generate_runtime_setup;
use libimagcounter::counter::Counter; use libimagcounter::counter::Counter;
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
use libimagutil::key_value_split::IntoKeyValue; use libimagutil::key_value_split::IntoKeyValue;
mod create; mod create;
@ -49,20 +50,10 @@ enum Action {
} }
fn main() { fn main() {
let name = "imag-counter"; let rt = generate_runtime_setup("imag-counter",
let version = &version!()[..]; &version!()[..],
let about = "Counter tool to count things"; "Counter tool to count things",
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); build_ui);
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);
}
};
rt.cli() rt.cli()
.subcommand_name() .subcommand_name()

View file

@ -7,26 +7,30 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("i") .short("i")
.takes_value(true) .takes_value(true)
.required(false) .required(false)
.help("Increment a counter")) .help("Increment a counter")
.value_name("COUNTER"))
.arg(Arg::with_name("decrement") .arg(Arg::with_name("decrement")
.long("dec") .long("dec")
.short("d") .short("d")
.takes_value(true) .takes_value(true)
.required(false) .required(false)
.help("Decrement a counter")) .help("Decrement a counter")
.value_name("COUNTER"))
.arg(Arg::with_name("reset") .arg(Arg::with_name("reset")
.long("reset") .long("reset")
.takes_value(true) .takes_value(true)
.required(false) .required(false)
.help("Reset a counter")) .help("Reset a counter")
.value_name("COUNTER"))
.arg(Arg::with_name("set") .arg(Arg::with_name("set")
.long("set") .long("set")
.takes_value(true) .takes_value(true)
.required(false) .required(false)
.help("Set a counter")) .help("Set a counter")
.value_name("COUNTER"))
.subcommand(SubCommand::with_name("create") .subcommand(SubCommand::with_name("create")
.about("Create a counter") .about("Create a counter")
@ -36,13 +40,15 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("n") .short("n")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.help("Create counter with this name")) .help("Create counter with this name")
.value_name("NAME"))
.arg(Arg::with_name("initval") .arg(Arg::with_name("initval")
.long("init") .long("init")
.short("i") .short("i")
.takes_value(true) .takes_value(true)
.required(false) .required(false)
.help("Initial value"))) .help("Initial value")
.value_name("VALUE")))
.subcommand(SubCommand::with_name("delete") .subcommand(SubCommand::with_name("delete")
.about("Delete a counter") .about("Delete a counter")
@ -52,7 +58,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("n") .short("n")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.help("Create counter with this name"))) .help("Create counter with this name")
.value_name("NAME")))
.subcommand(SubCommand::with_name("list") .subcommand(SubCommand::with_name("list")
.about("List counters") .about("List counters")
@ -62,28 +69,32 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("n") .short("n")
.takes_value(true) .takes_value(true)
.required(false) .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") .arg(Arg::with_name("greater-than")
.long("greater") .long("greater")
.short("g") .short("g")
.takes_value(true) .takes_value(true)
.required(false) .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") .arg(Arg::with_name("lower-than")
.long("lower") .long("lower")
.short("l") .short("l")
.takes_value(true) .takes_value(true)
.required(false) .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") .arg(Arg::with_name("equals")
.long("equal") .long("equal")
.short("e") .short("e")
.takes_value(true) .takes_value(true)
.required(false) .required(false)
.help("List counters which equal VALUE")) .help("List counters which equal VALUE")
.value_name("VALUE"))
) )
.subcommand(SubCommand::with_name("interactive") .subcommand(SubCommand::with_name("interactive")
@ -97,6 +108,6 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.required(true) .required(true)
.help("Specification for key-bindings. Use <KEY>=<VALUE> where KEY is the .help("Specification for key-bindings. Use <KEY>=<VALUE> where KEY is the
key to bind (single character) and VALUE is the path to the counter to bind key to bind (single character) and VALUE is the path to the counter to bind
to."))) to.")
.value_name("KEY=VALUE")))
} }

38
imag-diary/Cargo.toml Normal file
View file

@ -0,0 +1,38 @@
[package]
name = "imag-diary"
version = "0.1.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[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"

112
imag-diary/src/create.rs Normal file
View file

@ -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<Entry<'a>> {
use std::str::FromStr;
let create = rt.cli().subcommand_matches("create").unwrap();
if !create.is_present("timed") {
debug!("Creating non-timed entry");
diary.new_entry_today()
} else {
let id = match create.value_of("timed") {
Some("h") | Some("hourly") => {
debug!("Creating hourly-timed entry");
let 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!");
}
}

66
imag-diary/src/delete.rs Normal file
View file

@ -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<NaiveDateTime> = 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);
},
}
}

47
imag-diary/src/edit.rs Normal file
View file

@ -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<NaiveDateTime> = 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();
}

52
imag-diary/src/list.rs Normal file
View file

@ -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("<<Path Parsing Error>>"))
}
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();
}

72
imag-diary/src/main.rs Normal file
View file

@ -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!()
}

107
imag-diary/src/ui.rs Normal file
View file

@ -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"))
)
}

9
imag-diary/src/util.rs Normal file
View file

@ -0,0 +1,9 @@
use libimagrt::runtime::Runtime;
pub fn get_diary_name(rt: &Runtime) -> Option<String> {
use libimagdiary::config::get_default_diary_name;
get_default_diary_name(rt)
.or(rt.cli().value_of("diaryname").map(String::from))
}

36
imag-diary/src/view.rs Normal file
View file

@ -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);
},
}
}

View file

@ -6,10 +6,10 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies] [dependencies]
semver = "0.2.1" semver = "0.2.1"
clap = "2.1.1" clap = "2.1.1"
log = "0.3.5" log = "0.3"
version = "2.0.1" version = "2.0.1"
toml = "0.1.25" toml = "0.1.25"
url = "0.5.5" url = "1.1"
[dependencies.libimagstore] [dependencies.libimagstore]
path = "../libimagstore" path = "../libimagstore"
@ -20,6 +20,6 @@ path = "../libimagrt"
[dependencies.libimagentrylink] [dependencies.libimagentrylink]
path = "../libimagentrylink" path = "../libimagentrylink"
[dependencies.libimagutil] [dependencies.libimagerror]
path = "../libimagutil" path = "../libimagerror"

View file

@ -23,17 +23,18 @@ extern crate url;
extern crate libimagentrylink; extern crate libimagentrylink;
extern crate libimagrt; extern crate libimagrt;
extern crate libimagstore; extern crate libimagstore;
extern crate libimagutil; extern crate libimagerror;
use std::process::exit; use std::process::exit;
use std::ops::Deref; use std::ops::Deref;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagstore::error::StoreError; use libimagstore::error::StoreError;
use libimagstore::store::Entry; use libimagstore::store::Entry;
use libimagstore::store::FileLockEntry; use libimagstore::store::FileLockEntry;
use libimagstore::store::Store; use libimagstore::store::Store;
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
use libimagentrylink::external::ExternalLinker; use libimagentrylink::external::ExternalLinker;
use clap::ArgMatches; use clap::ArgMatches;
use url::Url; use url::Url;
@ -43,20 +44,10 @@ mod ui;
use ui::build_ui; use ui::build_ui;
fn main() { fn main() {
let name = "imag-link"; let rt = generate_runtime_setup("imag-link",
let version = &version!()[..]; &version!()[..],
let about = "Link entries"; "Link entries",
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); build_ui);
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);
}
};
rt.cli() rt.cli()
.subcommand_name() .subcommand_name()
@ -74,23 +65,21 @@ fn main() {
fn handle_internal_linking(rt: &Runtime) { fn handle_internal_linking(rt: &Runtime) {
use libimagentrylink::internal::InternalLinker; use libimagentrylink::internal::InternalLinker;
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
debug!("Handle internal linking call"); debug!("Handle internal linking call");
let cmd = rt.cli().subcommand_matches("internal").unwrap(); let cmd = rt.cli().subcommand_matches("internal").unwrap();
if cmd.is_present("list") { if cmd.is_present("list") {
debug!("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); debug!("Listing for '{}'", entry);
match get_entry_by_name(rt, entry) { match get_entry_by_name(rt, entry) {
Ok(e) => { Ok(e) => {
e.get_internal_links() e.get_internal_links()
.map(|links| { .map(|links| {
let mut i = 0; for (i, link) in links.iter().map(|l| l.to_str()).filter_map(|x| x).enumerate() {
for link in links.iter().map(|l| l.to_str()).filter_map(|x| x) {
println!("{: <3}: {}", i, link); println!("{: <3}: {}", i, link);
i += 1;
} }
}) })
.map_err(|e| trace_error(&e)) .map_err(|e| trace_error(&e))
@ -194,7 +183,7 @@ fn get_entry_by_name<'a>(rt: &'a Runtime, name: &str) -> Result<FileLockEntry<'a
} }
fn handle_external_linking(rt: &Runtime) { fn handle_external_linking(rt: &Runtime) {
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
let scmd = rt.cli().subcommand_matches("external").unwrap(); let scmd = rt.cli().subcommand_matches("external").unwrap();
let entry_name = scmd.value_of("id").unwrap(); // enforced by clap let entry_name = scmd.value_of("id").unwrap(); // enforced by clap
@ -275,7 +264,7 @@ fn set_links_for_entry(store: &Store, matches: &ArgMatches, entry: &mut FileLock
.value_of("links") .value_of("links")
.map(String::from) .map(String::from)
.unwrap() .unwrap()
.split(",") .split(',')
.map(|uri| { .map(|uri| {
match Url::parse(uri) { match Url::parse(uri) {
Err(e) => { Err(e) => {
@ -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) { fn list_links_for_entry(store: &Store, entry: &mut FileLockEntry) {
let res = entry.get_external_links(store) let res = entry.get_external_links(store)
.and_then(|links| { .and_then(|links| {
let mut i = 0; for (i, link) in links.iter().enumerate() {
for link in links {
println!("{: <3}: {}", i, link); println!("{: <3}: {}", i, link);
i += 1;
} }
Ok(()) Ok(())
}); });

View file

@ -13,14 +13,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("f") .short("f")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.help("Link from this entry")) .help("Link from this entry")
.value_name("ENTRY"))
.arg(Arg::with_name("to") .arg(Arg::with_name("to")
.long("to") .long("to")
.short("t") .short("t")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.multiple(true) .multiple(true)
.help("Link to this entries")) .help("Link to this entries")
.value_name("ENTRIES"))
) )
.subcommand(SubCommand::with_name("remove") .subcommand(SubCommand::with_name("remove")
@ -31,14 +33,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("f") .short("f")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.help("Remove Link from this entry")) .help("Remove Link from this entry")
.value_name("ENTRY"))
.arg(Arg::with_name("to") .arg(Arg::with_name("to")
.long("to") .long("to")
.short("t") .short("t")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.multiple(true) .multiple(true)
.help("Remove links to these entries")) .help("Remove links to these entries")
.value_name("ENTRIES"))
) )
.arg(Arg::with_name("list") .arg(Arg::with_name("list")
@ -46,7 +50,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("l") .short("l")
.takes_value(true) .takes_value(true)
.required(false) .required(false)
.help("List links to this entry")) .help("List links to this entry")
.value_name("ENTRY"))
) )
.subcommand(SubCommand::with_name("external") .subcommand(SubCommand::with_name("external")
.about("Add and remove external links") .about("Add and remove external links")
@ -57,14 +62,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("i") .short("i")
.takes_value(true) .takes_value(true)
.required(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") .arg(Arg::with_name("add")
.long("add") .long("add")
.short("a") .short("a")
.takes_value(true) .takes_value(true)
.required(false) .required(false)
.help("Add this URI as external link")) .help("Add this URI as external link")
.value_name("URI"))
.arg(Arg::with_name("remove") .arg(Arg::with_name("remove")
.long("remove") .long("remove")
@ -78,7 +85,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("s") .short("s")
.takes_value(true) .takes_value(true)
.required(false) .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") .arg(Arg::with_name("list")
.long("list") .long("list")
@ -93,4 +101,3 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
) )
} }

View file

@ -6,7 +6,7 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies] [dependencies]
semver = "0.2.1" semver = "0.2.1"
clap = "2.1.1" clap = "2.1.1"
log = "0.3.5" log = "0.3"
version = "2.0.1" version = "2.0.1"
[dependencies.libimagrt] [dependencies.libimagrt]
@ -18,6 +18,6 @@ path = "../libimagnotes"
[dependencies.libimagentrytag] [dependencies.libimagentrytag]
path = "../libimagentrytag" path = "../libimagentrytag"
[dependencies.libimagutil] [dependencies.libimagerror]
path = "../libimagutil" path = "../libimagerror"

View file

@ -6,33 +6,24 @@ extern crate semver;
extern crate libimagnotes; extern crate libimagnotes;
extern crate libimagrt; extern crate libimagrt;
extern crate libimagentrytag; extern crate libimagentrytag;
extern crate libimagutil; extern crate libimagerror;
use std::process::exit; use std::process::exit;
use libimagrt::edit::Edit; use libimagrt::edit::Edit;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagnotes::note::Note; use libimagnotes::note::Note;
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
mod ui; mod ui;
use ui::build_ui; use ui::build_ui;
fn main() { fn main() {
let name = "imag-notes"; let rt = generate_runtime_setup("imag-notes",
let version = &version!()[..]; &version!()[..],
let about = "Note taking helper"; "Note taking helper",
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); build_ui);
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);
}
};
rt.cli() rt.cli()
.subcommand_name() .subcommand_name()
@ -60,10 +51,9 @@ fn create(rt: &Runtime) {
.map_err(|e| trace_error(&e)) .map_err(|e| trace_error(&e))
.ok(); .ok();
if rt.cli().subcommand_matches("create").unwrap().is_present("edit") { if rt.cli().subcommand_matches("create").unwrap().is_present("edit") &&
if !edit_entry(rt, name) { !edit_entry(rt, name) {
exit(1); exit(1);
}
} }
} }
@ -79,14 +69,19 @@ fn edit(rt: &Runtime) {
} }
fn edit_entry(rt: &Runtime, name: String) -> bool { fn edit_entry(rt: &Runtime, name: String) -> bool {
let note = Note::retrieve(rt.store(), name); let mut note = match Note::get(rt.store(), name) {
if note.is_err() { Ok(Some(note)) => note,
trace_error(&note.unwrap_err()); Ok(None) => {
warn!("Cannot edit nonexistent Note"); warn!("Cannot edit nonexistent Note");
return false 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) { if let Err(e) = note.edit_content(rt) {
trace_error(&e); trace_error(&e);
warn!("Editing failed"); warn!("Editing failed");

View file

@ -13,7 +13,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("n") .short("n")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.help("Create Note with this name")) .help("Create Note with this name")
.value_name("NAME"))
.arg(Arg::with_name("edit") .arg(Arg::with_name("edit")
.long("edit") .long("edit")
.short("e") .short("e")
@ -30,7 +31,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("n") .short("n")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.help("Delete Note with this name"))) .help("Delete Note with this name")
.value_name("NAME")))
.subcommand(SubCommand::with_name("edit") .subcommand(SubCommand::with_name("edit")
.about("Edit a Note") .about("Edit a Note")
@ -40,7 +42,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("n") .short("n")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.help("Edit Note with this name")) .help("Edit Note with this name")
.value_name("NAME"))
.arg(tag_argument()) .arg(tag_argument())
.group(ArgGroup::with_name("editargs") .group(ArgGroup::with_name("editargs")
@ -53,5 +56,3 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.version("0.1")) .version("0.1"))
} }

View file

@ -5,7 +5,7 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies] [dependencies]
clap = "2.1.1" clap = "2.1.1"
log = "0.3.5" log = "0.3"
version = "2.0.1" version = "2.0.1"
semver = "0.2.1" semver = "0.2.1"
toml = "0.1.25" toml = "0.1.25"
@ -19,3 +19,6 @@ path = "../libimagrt"
[dependencies.libimagutil] [dependencies.libimagutil]
path = "../libimagutil" path = "../libimagutil"
[dependencies.libimagerror]
path = "../libimagerror"

View file

@ -14,7 +14,7 @@ use libimagrt::runtime::Runtime;
use libimagstore::store::Entry; use libimagstore::store::Entry;
use libimagstore::store::EntryHeader; use libimagstore::store::EntryHeader;
use libimagstore::storeid::build_entry_path; use libimagstore::storeid::build_entry_path;
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
use error::StoreError; use error::StoreError;
use error::StoreErrorKind; use error::StoreErrorKind;
@ -60,30 +60,24 @@ pub fn create(rt: &Runtime) {
fn create_from_cli_spec(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Result<()> { fn create_from_cli_spec(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Result<()> {
let content = matches.subcommand_matches("entry") 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"); debug!("Found entry subcommand, parsing content");
entry_subcommand entry_subcommand
.value_of("content") .value_of("content")
.map(String::from) .map_or_else(|| {
.unwrap_or_else(|| { entry_subcommand.value_of("content-from")
entry_subcommand .map_or_else(String::new, string_from_raw_src)
.value_of("content-from") }, String::from)
.map(|src| string_from_raw_src(src))
.unwrap_or(String::new())
})
})
.unwrap_or_else(|| {
debug!("Didn't find entry subcommand, getting raw content");
matches.value_of("from-raw")
.map(|raw_src| string_from_raw_src(raw_src))
.unwrap_or(String::new())
}); });
debug!("Got content with len = {}", content.len()); debug!("Got content with len = {}", content.len());
let header = matches.subcommand_matches("entry") let header = matches.subcommand_matches("entry")
.map(|entry_matches| build_toml_header(entry_matches, EntryHeader::new())) .map_or_else(EntryHeader::new,
.unwrap_or(EntryHeader::new()); |entry_matches| build_toml_header(entry_matches, EntryHeader::new()));
create_with_content_and_header(rt, path, content, header) 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 let content = matches
.value_of("from-raw") .value_of("from-raw")
.ok_or(StoreError::new(StoreErrorKind::NoCommandlineCall, None)) .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() { if content.is_err() {
return content.map(|_| ()); return content.map(|_| ());

View file

@ -1,6 +1,6 @@
use libimagstore::storeid::build_entry_path; use libimagstore::storeid::build_entry_path;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
pub fn delete(rt: &Runtime) { pub fn delete(rt: &Runtime) {
use std::process::exit; use std::process::exit;

View file

@ -1,81 +1,10 @@
use std::error::Error; generate_error_module!(
use std::fmt::Error as FmtError; generate_error_types!(StoreError, StoreErrorKind,
use std::clone::Clone; BackendError => "Backend Error",
use std::fmt::{Display, Formatter}; NoCommandlineCall => "No commandline call"
);
);
/** pub use self::error::StoreError;
* Kind of store error pub use self::error::StoreErrorKind;
*/
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum StoreErrorKind {
BackendError,
NoCommandlineCall,
// maybe more
}
fn store_error_type_as_str(e: &StoreErrorKind) -> &'static str {
match e {
&StoreErrorKind::BackendError => "Backend Error",
&StoreErrorKind::NoCommandlineCall => "No commandline call",
}
}
impl Display for StoreErrorKind {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "{}", store_error_type_as_str(self)));
Ok(())
}
}
#[derive(Debug)]
pub struct StoreError {
err_type: StoreErrorKind,
cause: Option<Box<Error>>,
}
impl StoreError {
/**
* Build a new StoreError from an StoreErrorKind, optionally with cause
*/
pub fn new(errtype: StoreErrorKind, cause: Option<Box<Error>>)
-> StoreError
{
StoreError {
err_type: errtype,
cause: cause,
}
}
/**
* Get the error type of this StoreError
*/
pub fn err_type(&self) -> StoreErrorKind {
self.err_type.clone()
}
}
impl Display for StoreError {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "[{}]", store_error_type_as_str(&self.err_type.clone())));
Ok(())
}
}
impl Error for StoreError {
fn description(&self) -> &str {
store_error_type_as_str(&self.err_type.clone())
}
fn cause(&self) -> Option<&Error> {
self.cause.as_ref().map(|e| &**e)
}
}

31
imag-store/src/get.rs Normal file
View file

@ -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),
}
})
});
}

View file

@ -22,39 +22,31 @@ extern crate toml;
extern crate libimagrt; extern crate libimagrt;
extern crate libimagstore; extern crate libimagstore;
extern crate libimagutil; extern crate libimagutil;
#[macro_use] extern crate libimagerror;
use libimagrt::runtime::Runtime; use libimagrt::setup::generate_runtime_setup;
use std::process::exit;
mod error;
mod ui;
mod create; mod create;
mod retrieve;
mod update;
mod delete; mod delete;
mod error;
mod get;
mod retrieve;
mod ui;
mod update;
mod util; mod util;
use ui::build_ui;
use create::create; use create::create;
use retrieve::retrieve;
use update::update;
use delete::delete; use delete::delete;
use get::get;
use retrieve::retrieve;
use ui::build_ui;
use update::update;
fn main() { fn main() {
let name = "imag-store"; let rt = generate_runtime_setup("imag-store",
let version = &version!()[..]; &version!()[..],
let about = "Direct interface to the store. Use with great care!"; "Direct interface to the store. Use with great care!",
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); build_ui);
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);
}
};
rt.cli() rt.cli()
.subcommand_name() .subcommand_name()
@ -66,10 +58,11 @@ fn main() {
|name| { |name| {
debug!("Call: {}", name); debug!("Call: {}", name);
match name { match name {
"create" => create(&rt), "create" => create(&rt),
"retrieve" => retrieve(&rt), "delete" => delete(&rt),
"update" => update(&rt), "get" => get(&rt),
"delete" => delete(&rt), "retrieve" => retrieve(&rt),
"update" => update(&rt),
_ => { _ => {
debug!("Unknown command"); debug!("Unknown command");
// More error handling // More error handling

View file

@ -6,7 +6,7 @@ use toml::Value;
use libimagstore::store::FileLockEntry; use libimagstore::store::FileLockEntry;
use libimagstore::storeid::build_entry_path; use libimagstore::storeid::build_entry_path;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
pub fn retrieve(rt: &Runtime) { pub fn retrieve(rt: &Runtime) {
rt.cli() 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) { if do_print_raw(scmd) {
debug!("Printing raw content..."); debug!("Printing raw content...");
println!("{}", e.to_str()); println!("{}", e.to_str());

View file

@ -9,17 +9,20 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("p") .short("p")
.takes_value(true) .takes_value(true)
.required(false) .required(false)
.help("Create at this store path")) .help("Create at this store path")
.value_name("PATH"))
.arg(Arg::with_name("id") .arg(Arg::with_name("id")
.long("id") .long("id")
.short("i") .short("i")
.takes_value(true) .takes_value(true)
.required(false) .required(false)
.help("Same as --path, for consistency")) .help("Same as --path, for consistency")
.value_name("PATH"))
.arg(Arg::with_name("from-raw") .arg(Arg::with_name("from-raw")
.long("from-raw") .long("from-raw")
.takes_value(true) .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") .group(ArgGroup::with_name("create-destination-group")
.args(&["path", "id"]) .args(&["path", "id"])
@ -32,12 +35,14 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.long("content") .long("content")
.short("c") .short("c")
.takes_value(true) .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") .arg(Arg::with_name("content-from")
.long("content-from") .long("content-from")
.short("f") .short("f")
.takes_value(true) .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") .group(ArgGroup::with_name("create-content-group")
.args(&["content", "content-from"]) .args(&["content", "content-from"])
@ -48,12 +53,13 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("h") .short("h")
.takes_value(true) .takes_value(true)
.multiple(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") .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") .version("0.1")
.arg(Arg::with_name("id") .arg(Arg::with_name("id")
.long("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") .subcommand(SubCommand::with_name("update")
.about("Get an entry from the store") .about("Get an entry from the store")
.version("0.1") .version("0.1")
@ -103,12 +154,14 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("i") .short("i")
.takes_value(true) .takes_value(true)
.required(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") .arg(Arg::with_name("content")
.long("content") .long("content")
.short("c") .short("c")
.takes_value(true) .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") .arg(Arg::with_name("header")
.long("header") .long("header")
.short("h") .short("h")
@ -125,7 +178,7 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("i") .short("i")
.takes_value(true) .takes_value(true)
.required(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"))
) )
} }

View file

@ -3,7 +3,7 @@ use std::process::exit;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagstore::storeid::build_entry_path; use libimagstore::storeid::build_entry_path;
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
use util::build_toml_header; use util::build_toml_header;

View file

@ -1,4 +1,5 @@
use std::collections::BTreeMap; use std::borrow::Cow;
use std::collections::btree_map::{BTreeMap, Entry};
use std::str::Split; use std::str::Split;
use clap::ArgMatches; use clap::ArgMatches;
@ -21,10 +22,10 @@ pub fn build_toml_header(matches: &ArgMatches, header: EntryHeader) -> EntryHead
for tpl in kvs { for tpl in kvs {
let (key, value) = tpl.into(); let (key, value) = tpl.into();
debug!("Splitting: {:?}", key); debug!("Splitting: {:?}", key);
let mut split = key.split("."); let mut split = key.split('.');
let current = split.next(); let current = split.next();
if current.is_some() { 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, fn insert_key_into<'a>(current: String,
rest_path: &mut Split<&str>, rest_path: &mut Split<char>,
value: String, value: Cow<'a, str>,
map: &mut BTreeMap<String, Value>) { map: &mut BTreeMap<String, Value>) {
let next = rest_path.next(); let next = rest_path.next();
@ -47,27 +48,30 @@ fn insert_key_into(current: String,
map.insert(current, parse_value(value)); map.insert(current, parse_value(value));
} else { } else {
debug!("Inserting into {:?} ... = {:?}", current, value); debug!("Inserting into {:?} ... = {:?}", current, value);
if map.contains_key(&current) { match map.entry(current) {
match map.get_mut(&current).unwrap() { Entry::Occupied(ref mut e) => {
&mut Value::Table(ref mut t) => { match *e.get_mut() {
insert_key_into(String::from(next.unwrap()), rest_path, value, t); Value::Table(ref mut t) => {
}, insert_key_into(String::from(next.unwrap()), rest_path, value, t);
_ => unreachable!(), },
_ => 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<str>) -> Value {
use std::str::FromStr; use std::str::FromStr;
fn is_ary(v: &String) -> bool { fn is_ary(v: &str) -> bool {
v.chars().next() == Some('[') && v.chars().last() == Some(']') && v.len() >= 3 v.starts_with('[') && v.ends_with(']') && v.len() >= 3
} }
if value == "true" { if value == "true" {
@ -79,7 +83,7 @@ fn parse_value(value: String) -> Value {
} else if is_ary(&value) { } else if is_ary(&value) {
debug!("Building Array out of: {:?}...", value); debug!("Building Array out of: {:?}...", value);
let sub = &value[1..(value.len()-1)]; 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 { } else {
FromStr::from_str(&value[..]) FromStr::from_str(&value[..])
.map(|i: i64| { .map(|i: i64| {
@ -94,7 +98,7 @@ fn parse_value(value: String) -> Value {
}) })
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
debug!("Building String out of: {:?}...", value); debug!("Building String out of: {:?}...", value);
Value::String(value) Value::String(value.into_owned())
}) })
}) })
} }

View file

@ -1,4 +1,4 @@
source $(dirname ${BASH_SOURCE[0]})/../tests/utils.sh source $(dirname ${BASH_SOURCE[0]})/../../tests/utils.sh
imag-store() { imag-store() {
imag-call-binary "$(dirname ${BASH_SOURCE[0]})/../target/debug/" imag-store $* imag-call-binary "$(dirname ${BASH_SOURCE[0]})/../target/debug/" imag-store $*

View file

@ -5,7 +5,7 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies] [dependencies]
clap = "2.1.1" clap = "2.1.1"
log = "0.3.5" log = "0.3"
version = "2.0.1" version = "2.0.1"
semver = "0.2.1" semver = "0.2.1"
toml = "0.1.25" toml = "0.1.25"
@ -16,8 +16,8 @@ path = "../libimagstore"
[dependencies.libimagrt] [dependencies.libimagrt]
path = "../libimagrt" path = "../libimagrt"
[dependencies.libimagutil] [dependencies.libimagerror]
path = "../libimagutil" path = "../libimagerror"
[dependencies.libimagentrytag] [dependencies.libimagentrytag]
path = "../libimagentrytag" path = "../libimagentrytag"

View file

@ -7,46 +7,36 @@ extern crate toml;
extern crate libimagstore; extern crate libimagstore;
extern crate libimagrt; extern crate libimagrt;
extern crate libimagentrytag; extern crate libimagentrytag;
extern crate libimagutil; extern crate libimagerror;
use std::process::exit; use std::process::exit;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagentrytag::tagable::Tagable; use libimagentrytag::tagable::Tagable;
use libimagentrytag::tag::Tag;
use libimagstore::storeid::build_entry_path; use libimagstore::storeid::build_entry_path;
use libimagerror::trace::trace_error;
use libimagentrytag::ui::{get_add_tags, get_remove_tags};
mod ui; mod ui;
use ui::build_ui; use ui::build_ui;
use libimagutil::trace::trace_error;
fn main() { fn main() {
let name = "imag-store"; let rt = generate_runtime_setup("imag-store",
let version = &version!()[..]; &version!()[..],
let about = "Direct interface to the store. Use with great care!"; "Direct interface to the store. Use with great care!",
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); build_ui);
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 id = rt.cli().value_of("id").unwrap(); // enforced by clap let id = rt.cli().value_of("id").unwrap(); // enforced by clap
rt.cli() rt.cli()
.subcommand_name() .subcommand_name()
.map_or_else( .map_or_else(
|| { || {
let add = rt.cli().value_of("add"); let add = get_add_tags(rt.cli());
let rem = rt.cli().value_of("remove"); let rem = get_remove_tags(rt.cli());
let set = rt.cli().value_of("set"); alter(&rt, id, add, rem);
alter(&rt, id, add, rem, set);
}, },
|name| { |name| {
debug!("Call: {}", 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<Vec<Tag>>, rem: Option<Vec<Tag>>) {
let path = { let path = {
match build_entry_path(rt.store(), id) { match build_entry_path(rt.store(), id) {
Err(e) => { Err(e) => {
@ -72,43 +62,36 @@ fn alter(rt: &Runtime, id: &str, add: Option<&str>, rem: Option<&str>, set: Opti
}; };
debug!("path = {:?}", path); debug!("path = {:?}", path);
rt.store() match rt.store().get(path) {
// "id" must be present, enforced via clap spec Ok(Some(mut e)) => {
.retrieve(path)
.map(|mut e| {
add.map(|tags| { add.map(|tags| {
let tags = tags.split(",");
for tag in tags { for tag in tags {
info!("Adding tag '{}'", tag); debug!("Adding tag '{:?}'", tag);
if let Err(e) = e.add_tag(String::from(tag)) { if let Err(e) = e.add_tag(tag) {
trace_error(&e); trace_error(&e);
} }
} }
}); }); // it is okay to ignore a None here
rem.map(|tags| { rem.map(|tags| {
let tags = tags.split(",");
for tag in tags { for tag in tags {
info!("Removing tag '{}'", tag); debug!("Removing tag '{:?}'", tag);
if let Err(e) = e.remove_tag(String::from(tag)) { if let Err(e) = e.remove_tag(tag) {
trace_error(&e); trace_error(&e);
} }
} }
}); }); // it is okay to ignore a None here
},
set.map(|tags| { Ok(None) => {
info!("Setting tags '{}'", tags); info!("No entry found.");
let tags = tags.split(",").map(String::from).collect(); },
if let Err(e) = e.set_tags(tags) {
trace_error(&e); Err(e) => {
}
});
})
.map_err(|e| {
info!("No entry."); info!("No entry.");
trace_error(&e); trace_error(&e);
}) },
.ok(); }
} }
fn list(id: &str, rt: &Runtime) { fn list(id: &str, rt: &Runtime) {
@ -123,14 +106,20 @@ fn list(id: &str, rt: &Runtime) {
}; };
debug!("path = {:?}", path); debug!("path = {:?}", path);
let entry = rt.store().retrieve(path.clone()); let entry = match rt.store().get(path.clone()) {
if entry.is_err() { Ok(Some(e)) => e,
debug!("Could not retrieve '{:?}' => {:?}", id, path); Ok(None) => {
warn!("Could not retrieve entry '{}'", id); info!("No entry found.");
trace_error(&entry.unwrap_err()); exit(1);
exit(1); },
}
let entry = entry.unwrap(); 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() let scmd = rt.cli().subcommand_matches("list").unwrap(); // safe, we checked in main()

View file

@ -1,36 +1,18 @@
use clap::{Arg, App, ArgGroup, SubCommand}; 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> { pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
app.arg(Arg::with_name("id") app.arg(Arg::with_name("id")
.long("id") .long("id")
.short("i") .short("i")
.takes_value(true) .takes_value(true)
.required(true) .required(true)
.help("Use this entry")) .help("Use this entry")
.value_name("ID"))
.arg(Arg::with_name("add") .arg(tag_add_arg())
.long("add") .arg(tag_remove_arg())
.short("a")
.takes_value(true)
.required(false)
.multiple(true)
.help("Add this tag"))
.arg(Arg::with_name("remove")
.long("remove")
.short("r")
.takes_value(true)
.required(false)
.multiple(true)
.help("Remove this tag"))
.arg(Arg::with_name("set")
.long("set")
.short("s")
.takes_value(true)
.required(false)
.multiple(true)
.help("Set these tags"))
.subcommand(SubCommand::with_name("list") .subcommand(SubCommand::with_name("list")
.about("List tags (default)") .about("List tags (default)")
@ -58,7 +40,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("s") .short("s")
.takes_value(true) .takes_value(true)
.required(false) .required(false)
.help("Seperated by string")) .help("Separated by string")
.value_name("SEP"))
.group(ArgGroup::with_name("list-group") .group(ArgGroup::with_name("list-group")
.args(&[ .args(&[
@ -71,5 +54,3 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
) )
} }

View file

@ -6,7 +6,7 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies] [dependencies]
clap = "2.1.1" clap = "2.1.1"
glob = "0.2.11" glob = "0.2.11"
log = "0.3.5" log = "0.3"
rustbox = "0.8.1" rustbox = "0.8.1"
semver = "0.2.1" semver = "0.2.1"
toml = "0.1.25" toml = "0.1.25"
@ -18,6 +18,6 @@ path = "../libimagstore"
[dependencies.libimagrt] [dependencies.libimagrt]
path = "../libimagrt" path = "../libimagrt"
[dependencies.libimagutil] [dependencies.libimagerror]
path = "../libimagutil" path = "../libimagerror"

View file

@ -1,87 +1,12 @@
use std::error::Error; generate_error_module!(
use std::fmt::Error as FmtError; generate_error_types!(ViewError, ViewErrorKind,
use std::clone::Clone; StoreError => "Store error",
use std::fmt::{Display, Formatter}; NoVersion => "No version specified",
PatternError => "Error in Pattern",
GlobBuildError => "Could not build glob() Argument"
);
);
/** pub use self::error::ViewError;
* Kind of store error pub use self::error::ViewErrorKind;
*/
#[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<Box<Error>>,
}
impl ViewError {
/**
* Build a new ViewError from an ViewErrorKind, optionally with cause
*/
pub fn new(errtype: ViewErrorKind, cause: Option<Box<Error>>)
-> 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)
}
}

View file

@ -22,14 +22,15 @@ extern crate toml;
extern crate libimagrt; extern crate libimagrt;
extern crate libimagstore; extern crate libimagstore;
extern crate libimagutil; #[macro_use] extern crate libimagerror;
use std::result::Result as RResult; use std::result::Result as RResult;
use std::process::exit; use std::process::exit;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagstore::store::FileLockEntry; use libimagstore::store::FileLockEntry;
use libimagutil::trace::trace_error; use libimagerror::trace::trace_error;
mod error; mod error;
mod ui; mod ui;
@ -44,20 +45,10 @@ use viewer::stdout::StdoutViewer;
type Result<T> = RResult<T, ViewError>; type Result<T> = RResult<T, ViewError>;
fn main() { fn main() {
let name = "imag-view"; let rt = generate_runtime_setup( "imag-view",
let version = &version!()[..]; &version!()[..],
let about = "View entries (readonly)"; "View entries (readonly)",
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about)); build_ui);
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 entry_id = rt.cli().value_of("id").unwrap(); // enforced by clap let entry_id = rt.cli().value_of("id").unwrap(); // enforced by clap
@ -129,7 +120,7 @@ fn load_entry<'a>(id: &str,
let version = { let version = {
if version.is_none() { if version.is_none() {
let r = id.split("~").last(); let r = id.split('~').last();
if r.is_none() { if r.is_none() {
warn!("No version"); warn!("No version");
return Err(ViewError::new(ViewErrorKind::NoVersion, None)); return Err(ViewError::new(ViewErrorKind::NoVersion, None));
@ -144,7 +135,7 @@ fn load_entry<'a>(id: &str,
debug!("Building path from {:?} and {:?}", id, version); debug!("Building path from {:?} and {:?}", id, version);
let mut path = rt.store().path().clone(); let mut path = rt.store().path().clone();
if id.chars().next() == Some('/') { if id.starts_with('/') {
path.push(format!("{}~{}", &id[1..id.len()], version)); path.push(format!("{}~{}", &id[1..id.len()], version));
} else { } else {
path.push(format!("{}~{}", id, version)); 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(); let mut path = rt.store().path().clone();
if id.chars().next() == Some('/') { if id.starts_with('/') {
path.push(format!("{}~*", &id[1..id.len()])); path.push(format!("{}~*", &id[1..id.len()]));
} else { } else {
path.push(format!("{}~*", id)); path.push(format!("{}~*", id));

View file

@ -7,14 +7,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("i") .short("i")
.takes_value(true) .takes_value(true)
.required(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") .arg(Arg::with_name("version")
.long("version") .long("version")
.short("V") .short("V")
.takes_value(true) .takes_value(true)
.required(false) .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") .arg(Arg::with_name("versions")
.long("versions") .long("versions")
@ -71,21 +73,24 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("b") .short("b")
.takes_value(true) // optional, which browser .takes_value(true) // optional, which browser
.required(false) .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") .arg(Arg::with_name("view-in-texteditor")
.long("editor") .long("editor")
.short("e") .short("e")
.takes_value(true) // optional, which editor .takes_value(true) // optional, which editor
.required(false) .required(false)
.help("View content in $EDITOR")) .help("View content in $EDITOR")
.value_name("EDITOR"))
.arg(Arg::with_name("view-in-custom") .arg(Arg::with_name("view-in-custom")
.long("custom") .long("custom")
.short("c") .short("c")
.takes_value(true) // non-optional, call-string .takes_value(true) // non-optional, call-string
.required(false) .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") .group(ArgGroup::with_name("viewer")
.args(&["view-in-stdout", .args(&["view-in-stdout",
@ -105,15 +110,15 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("f") .short("f")
.takes_value(true) // "markdown" or "textile" or "restructuredtex" .takes_value(true) // "markdown" or "textile" or "restructuredtex"
.required(true) .required(true)
.help("Compile from")) .help("Compile from")
.value_name("FORMAT"))
.arg(Arg::with_name("to") .arg(Arg::with_name("to")
.long("to") .long("to")
.short("t") .short("t")
.takes_value(true) // "html" or "HTML" or ... json maybe? .takes_value(true) // "html" or "HTML" or ... json maybe?
.required(true) .required(true)
.help("Compile to")) .help("Compile to")
.value_name("FORMAT"))
) )
} }

31
imagrc.toml Normal file
View file

@ -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"

View file

@ -4,10 +4,13 @@ version = "0.1.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"] authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies] [dependencies]
log = "0.3.5" log = "0.3"
toml = "0.1.25" toml = "0.1.25"
semver = "0.2" semver = "0.2"
[dependencies.libimagstore] [dependencies.libimagstore]
path = "../libimagstore" path = "../libimagstore"
[dependencies.libimagerror]
path = "../libimagerror"

View file

@ -144,12 +144,12 @@ impl<'a> Counter<'a> {
} }
trait FromStoreId { trait FromStoreId {
fn from_storeid<'a>(&'a Store, StoreId) -> Result<Counter<'a>>; fn from_storeid(&Store, StoreId) -> Result<Counter>;
} }
impl<'a> FromStoreId for Counter<'a> { impl<'a> FromStoreId for Counter<'a> {
fn from_storeid<'b>(store: &'b Store, id: StoreId) -> Result<Counter<'b>> { fn from_storeid(store: &Store, id: StoreId) -> Result<Counter> {
debug!("Loading counter from storeid: '{:?}'", id); debug!("Loading counter from storeid: '{:?}'", id);
match store.retrieve(id) { match store.retrieve(id) {
Err(e) => Err(CE::new(CEK::StoreReadError, Some(Box::new(e)))), Err(e) => Err(CE::new(CEK::StoreReadError, Some(Box::new(e)))),

View file

@ -1,87 +1,12 @@
use std::error::Error; generate_error_module!(
use std::fmt::Error as FmtError; generate_error_types!(CounterError, CounterErrorKind,
use std::clone::Clone; StoreReadError => "Store read error",
use std::fmt::{Display, Formatter}; StoreWriteError => "Store write error",
HeaderTypeError => "Header type error",
HeaderFieldMissingError => "Header field missing error"
);
);
/** pub use self::error::CounterError;
* Kind of error pub use self::error::CounterErrorKind;
*/
#[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<Box<Error>>,
}
impl CounterError {
/**
* Build a new CounterError from an CounterErrorKind, optionally with cause
*/
pub fn new(errtype: CounterErrorKind, cause: Option<Box<Error>>)
-> 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)
}
}

View file

@ -17,6 +17,7 @@ extern crate toml;
#[macro_use] extern crate semver; #[macro_use] extern crate semver;
#[macro_use] extern crate libimagstore; #[macro_use] extern crate libimagstore;
#[macro_use] extern crate libimagerror;
module_entry_path_mod!("counter", "0.1.0"); module_entry_path_mod!("counter", "0.1.0");

26
libimagdiary/Cargo.toml Normal file
View file

@ -0,0 +1,26 @@
[package]
name = "libimagdiary"
version = "0.1.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[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"

View file

@ -0,0 +1,19 @@
use toml::Value;
use libimagrt::runtime::Runtime;
pub fn get_default_diary_name(rt: &Runtime) -> Option<String> {
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"))
}

109
libimagdiary/src/diary.rs Normal file
View file

@ -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<Entry> {
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<Entry> {
self.retrieve(id.with_diary_name(String::from(self.name)))
}
pub fn retrieve(&self, id: DiaryId) -> Result<Entry> {
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<DiaryEntryIterator<'a>> {
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<Result<Entry>> {
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
}
}

217
libimagdiary/src/diaryid.rs Normal file
View file

@ -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<DT: Datelike + Timelike>(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<String> 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<NaiveDateTime> 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<Self>;
}
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<DiaryId> {
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<u32> = cmps.next().and_then(component_to_str).and_then(|s| FromStr::from_str(s).ok());
let month :Option<u32> = cmps.next().and_then(component_to_str).and_then(|s| FromStr::from_str(s).ok());
let year :Option<i32> = 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))
}
}

71
libimagdiary/src/entry.rs Normal file
View file

@ -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<FileLockEntry<'a>> for Entry<'a> {
fn into(self) -> FileLockEntry<'a> {
self.0
}
}
impl<'a> From<FileLockEntry<'a>> for Entry<'a> {
fn from(fle: FileLockEntry<'a>) -> Entry<'a> {
Entry::new(fle)
}
}
impl<'a> Edit for Entry<'a> {
fn edit_content(&mut self, rt: &Runtime) -> EditResult<()> {
self.0.edit_content(rt)
}
}

16
libimagdiary/src/error.rs Normal file
View file

@ -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;

View file

@ -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)
}
}

111
libimagdiary/src/iter.rs Normal file
View file

@ -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<i32>,
month: Option<u32>,
day: Option<u32>,
}
impl<'a> Debug for DiaryEntryIterator<'a> {
fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
write!(fmt, "DiaryEntryIterator<name = {}, year = {:?}, month = {:?}, day = {:?}>",
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<DiaryEntry<'a>>;
fn next(&mut self) -> Option<Result<DiaryEntry<'a>>> {
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);
}
}
}
}

39
libimagdiary/src/lib.rs Normal file
View file

@ -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;

View file

@ -0,0 +1,5 @@
use std::result::Result as RResult;
use error::DiaryError;
pub type Result<T> = RResult<T, DiaryError>;

View file

@ -6,7 +6,7 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies] [dependencies]
clap = "2.1.1" clap = "2.1.1"
itertools = "0.4" itertools = "0.4"
log = "0.3.4" log = "0.3"
regex = "0.1" regex = "0.1"
toml = "0.1.27" toml = "0.1.27"
semver = "0.2.1" semver = "0.2.1"

View file

@ -14,15 +14,15 @@ struct EqGt {
impl Predicate for EqGt { impl Predicate for EqGt {
fn evaluate(&self, v: Value) -> bool { fn evaluate(&self, v: Value) -> bool {
match &self.comp { match self.comp {
&Value::Integer(i) => { Value::Integer(i) => {
match v { match v {
Value::Integer(j) => i > j, Value::Integer(j) => i > j,
Value::Float(f) => (i as f64) > f, Value::Float(f) => (i as f64) > f,
_ => false, _ => false,
} }
}, },
&Value::Float(f) => { Value::Float(f) => {
match v { match v {
Value::Integer(i) => f > (i as f64), Value::Integer(i) => f > (i as f64),
Value::Float(d) => f > d, Value::Float(d) => f > d,

View file

@ -27,11 +27,11 @@ impl Filter for FieldIsEmpty {
.map(|v| { .map(|v| {
match v { match v {
Some(Value::Array(a)) => a.is_empty(), 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::String(s)) => s.is_empty(),
Some(Value::Table(t)) => t.is_empty(), Some(Value::Table(t)) => t.is_empty(),
Some(Value::Boolean(_)) |
Some(Value::Float(_)) |
Some(Value::Integer(_)) => false,
_ => true, _ => true,
} }
}) })

View file

@ -21,11 +21,11 @@ impl Type {
fn matches(&self, v: &Value) -> bool { fn matches(&self, v: &Value) -> bool {
match (self, v) { match (self, v) {
(&Type::String, &Value::String(_)) => true, (&Type::String, &Value::String(_)) |
(&Type::Integer, &Value::Integer(_)) => true, (&Type::Integer, &Value::Integer(_)) |
(&Type::Float, &Value::Float(_)) => true, (&Type::Float, &Value::Float(_)) |
(&Type::Boolean, &Value::Boolean(_)) => true, (&Type::Boolean, &Value::Boolean(_)) |
(&Type::Array, &Value::Array(_)) => true, (&Type::Array, &Value::Array(_)) |
(&Type::Table, &Value::Table(_)) => true, (&Type::Table, &Value::Table(_)) => true,
_ => false, _ => false,
} }

View file

@ -14,15 +14,15 @@ struct EqLt {
impl Predicate for EqLt { impl Predicate for EqLt {
fn evaluate(&self, v: Value) -> bool { fn evaluate(&self, v: Value) -> bool {
match &self.comp { match self.comp {
&Value::Integer(i) => { Value::Integer(i) => {
match v { match v {
Value::Integer(j) => i < j, Value::Integer(j) => i < j,
Value::Float(f) => (i as f64) < f, Value::Float(f) => (i as f64) < f,
_ => false, _ => false,
} }
}, },
&Value::Float(f) => { Value::Float(f) => {
match v { match v {
Value::Integer(i) => f < (i as f64), Value::Integer(i) => f < (i as f64),
Value::Float(d) => f < d, Value::Float(d) => f < d,

View file

@ -23,7 +23,7 @@ impl Filter for VersionEq {
e.get_header() e.get_header()
.read("imag.version") .read("imag.version")
.map(|val| { .map(|val| {
val.map(|v| { val.map_or(false, |v| {
match v { match v {
Value::String(s) => { Value::String(s) => {
match Version::parse(&s[..]) { match Version::parse(&s[..]) {
@ -34,7 +34,6 @@ impl Filter for VersionEq {
_ => false, _ => false,
} }
}) })
.unwrap_or(false)
}) })
.unwrap_or(false) .unwrap_or(false)
} }

View file

@ -23,7 +23,7 @@ impl Filter for VersionGt {
e.get_header() e.get_header()
.read("imag.version") .read("imag.version")
.map(|val| { .map(|val| {
val.map(|v| { val.map_or(false, |v| {
match v { match v {
Value::String(s) => { Value::String(s) => {
match Version::parse(&s[..]) { match Version::parse(&s[..]) {
@ -34,7 +34,6 @@ impl Filter for VersionGt {
_ => false, _ => false,
} }
}) })
.unwrap_or(false)
}) })
.unwrap_or(false) .unwrap_or(false)
} }

View file

@ -23,7 +23,7 @@ impl Filter for VersionLt {
e.get_header() e.get_header()
.read("imag.version") .read("imag.version")
.map(|val| { .map(|val| {
val.map(|v| { val.map_or(false, |v| {
match v { match v {
Value::String(s) => { Value::String(s) => {
match Version::parse(&s[..]) { match Version::parse(&s[..]) {
@ -34,7 +34,6 @@ impl Filter for VersionLt {
_ => false, _ => false,
} }
}) })
.unwrap_or(false)
}) })
.unwrap_or(false) .unwrap_or(false)
} }

View file

@ -5,12 +5,18 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies] [dependencies]
itertools = "0.4" itertools = "0.4"
log = "0.3.4" log = "0.3"
toml = "0.1.27" toml = "0.1.27"
semver = "0.2" semver = "0.2"
url = "0.5.5" url = "1.1"
rust-crypto = "0.2.35" rust-crypto = "0.2.35"
[dependencies.libimagstore] [dependencies.libimagstore]
path = "../libimagstore" path = "../libimagstore"
[dependencies.libimagerror]
path = "../libimagerror"
[dependencies.libimagutil]
path = "../libimagutil"

View file

@ -1,90 +1,16 @@
use std::error::Error; generate_error_module!(
use std::fmt::Error as FmtError; generate_error_types!(LinkError, LinkErrorKind,
use std::fmt::{Display, Formatter}; 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 use self::error::LinkError;
pub enum LinkErrorKind { pub use self::error::LinkErrorKind;
EntryHeaderReadError,
EntryHeaderWriteError,
ExistingLinkTypeWrong,
LinkTargetDoesNotExist,
InternalConversionError,
InvalidUri,
StoreReadError,
StoreWriteError,
}
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<Box<Error>>,
}
impl LinkError {
pub fn new(errtype: LinkErrorKind, cause: Option<Box<Error>>) -> 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)
}
}

View file

@ -31,8 +31,8 @@ use url::Url;
use crypto::sha1::Sha1; use crypto::sha1::Sha1;
use crypto::digest::Digest; use crypto::digest::Digest;
/// "Link" Type, just an abstraction over FileLockEntry to have some convenience internally. /// "Link" Type, just an abstraction over `FileLockEntry` to have some convenience internally.
struct Link<'a> { pub struct Link<'a> {
link: FileLockEntry<'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 /// 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<Option<Link<'a>>> { fn retrieve(store: &'a Store, id: StoreId) -> Result<Option<Link<'a>>> {
store.retrieve(id) store.retrieve(id)
.map(|fle| { .map(|fle| Link::get_link_uri_from_filelockentry(&fle).map(|_| Link { link: fle }))
if let Some(_) = Link::get_link_uri_from_filelockentry(&fle) {
Some(Link {
link: fle
})
} else {
None
}
})
.map_err(|e| LE::new(LEK::StoreReadError, Some(Box::new(e)))) .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<Url> { fn get_link_uri_from_filelockentry(file: &FileLockEntry<'a>) -> Option<Url> {
file.get_header() file.get_header()
.read("imag.content.uri") .read("imag.content.uri")
.ok() .ok()
.and_then(|opt| { .and_then(|opt| match opt {
match opt { Some(Value::String(s)) => Url::parse(&s[..]).ok(),
Some(Value::String(s)) => Url::parse(&s[..]).ok(), _ => None
_ => None
}
}) })
} }
@ -78,7 +68,7 @@ impl<'a> Link<'a> {
match opt { match opt {
Ok(Some(Value::String(s))) => { Ok(Some(Value::String(s))) => {
Url::parse(&s[..]) Url::parse(&s[..])
.map(|s| Some(s)) .map(Some)
.map_err(|e| LE::new(LEK::EntryHeaderReadError, Some(Box::new(e)))) .map_err(|e| LE::new(LEK::EntryHeaderReadError, Some(Box::new(e))))
}, },
Ok(None) => Ok(None), Ok(None) => Ok(None),
@ -105,7 +95,7 @@ pub trait ExternalLinker : InternalLinker {
} }
/// Check whether the StoreId starts with `/link/external/` /// 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); debug!("Checking whether this is a /link/external/*: '{:?}'", id);
id.starts_with("/link/external/") id.starts_with("/link/external/")
} }
@ -115,7 +105,7 @@ fn get_external_link_from_file(entry: &FileLockEntry) -> Result<Url> {
.ok_or(LE::new(LEK::StoreReadError, None)) .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 /// 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. /// entry in the store can only have one external link.
impl ExternalLinker for Entry { impl ExternalLinker for Entry {
@ -129,7 +119,7 @@ impl ExternalLinker for Entry {
.map(|vect| { .map(|vect| {
debug!("Getting external links"); debug!("Getting external links");
vect.into_iter() vect.into_iter()
.filter(is_link_store_id) .filter(is_external_link_storeid)
.map(|id| { .map(|id| {
debug!("Retrieving entry for id: '{:?}'", id); debug!("Retrieving entry for id: '{:?}'", id);
match store.retrieve(id.clone()) { match store.retrieve(id.clone()) {
@ -156,7 +146,7 @@ impl ExternalLinker for Entry {
for link in links { // for all links for link in links { // for all links
let hash = { let hash = {
let mut s = Sha1::new(); let mut s = Sha1::new();
s.input_str(&link.serialize()[..]); s.input_str(&link.as_str()[..]);
s.result_str() s.result_str()
}; };
let file_id = ModuleEntryPath::new(format!("external/{}", hash)).into_storeid(); 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)))), 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); debug!("setting URL = '{:?}", v);
table.insert(String::from("url"), v); table.insert(String::from("url"), v);
@ -231,7 +221,7 @@ impl ExternalLinker for Entry {
.and_then(|links| { .and_then(|links| {
debug!("Removing link = '{:?}' from links = {:?}", link, links); debug!("Removing link = '{:?}' from links = {:?}", link, links);
let links = links.into_iter() let links = links.into_iter()
.filter(|l| l.serialize() != link.serialize()) .filter(|l| l.as_str() != link.as_str())
.collect(); .collect();
self.set_external_links(store, links) self.set_external_links(store, links)
}) })

View file

@ -4,8 +4,9 @@ use libimagstore::storeid::StoreId;
use libimagstore::store::Entry; use libimagstore::store::Entry;
use libimagstore::store::EntryHeader; use libimagstore::store::EntryHeader;
use libimagstore::store::Result as StoreResult; use libimagstore::store::Result as StoreResult;
use libimagerror::into::IntoError;
use error::{LinkError, LinkErrorKind}; use error::LinkErrorKind as LEK;
use result::Result; use result::Result;
use toml::Value; use toml::Value;
@ -50,7 +51,7 @@ impl InternalLinker for Entry {
let new_links = links_into_values(new_links); let new_links = links_into_values(new_links);
if new_links.iter().any(|o| o.is_none()) { 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(); 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))) 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<StoreId>) -> Vec<Option<Value>> { fn links_into_values(links: Vec<StoreId>) -> Vec<Option<Value>> {
links links
.into_iter() .into_iter()
.map(|s| s.to_str().map(|s| String::from(s))) .map(|s| s.to_str().map(String::from))
.unique() .unique()
.map(|elem| elem.map(|s| Value::String(s))) .map(|elem| elem.map(Value::String))
.sorted_by(|a, b| { .sorted_by(|a, b| {
match (a, b) { match (a, b) {
(&Some(Value::String(ref a)), &Some(Value::String(ref b))) => Ord::cmp(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<StoreId>) -> Result<()> {
if links.iter().any(|o| o.is_none()) { if links.iter().any(|o| o.is_none()) {
// if any type convert failed we fail as well // if any type convert failed we fail as well
Err(LinkError::new(LinkErrorKind::InternalConversionError, None)) Err(LEK::InternalConversionError.into())
} else { } else {
// I know it is ugly // I know it is ugly
let links = links.into_iter().map(|opt| opt.unwrap()).collect(); 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); links.push(from);
let links = links_into_values(links); let links = links_into_values(links);
if links.iter().any(|o| o.is_none()) { if links.iter().any(|o| o.is_none()) {
Err(LinkError::new(LinkErrorKind::InternalConversionError, None)) Err(LEK::InternalConversionError.into())
} else { } else {
let links = links.into_iter().map(|opt| opt.unwrap()).collect(); let links = links.into_iter().map(|opt| opt.unwrap()).collect();
process_rw_result(target.get_header_mut().set("imag.links", Value::Array(links))) 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<Option<Value>>) -> Result<Vec<Link>> { fn process_rw_result(links: StoreResult<Option<Value>>) -> Result<Vec<Link>> {
if links.is_err() { let links = match links {
debug!("RW action on store failed. Generating LinkError"); Err(e) => {
let lerr = LinkError::new(LinkErrorKind::EntryHeaderReadError, debug!("RW action on store failed. Generating LinkError");
Some(Box::new(links.unwrap_err()))); return Err(LEK::EntryHeaderReadError.into_error_with_cause(Box::new(e)))
return Err(lerr); },
} Ok(None) => {
let links = links.unwrap(); debug!("We got no value from the header!");
return Ok(vec![])
if links.is_none() { },
debug!("We got no value from the header!"); Ok(Some(Value::Array(l))) => l,
return Ok(vec![]) Ok(Some(_)) => {
} debug!("We expected an Array for the links, but there was a non-Array!");
let links = links.unwrap(); return Err(LEK::ExistingLinkTypeWrong.into());
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));
},
} }
}; };
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!("At least one of the Values which were expected in the Array of links is a non-String!");
debug!("Generating LinkError"); debug!("Generating LinkError");
return Err(LinkError::new(LinkErrorKind::ExistingLinkTypeWrong, None)); return Err(LEK::ExistingLinkTypeWrong.into());
} }
let links : Vec<Link> = links.into_iter() let links : Vec<Link> = links.into_iter()

View file

@ -20,6 +20,8 @@ extern crate url;
extern crate crypto; extern crate crypto;
#[macro_use] extern crate libimagstore; #[macro_use] extern crate libimagstore;
#[macro_use] extern crate libimagerror;
#[macro_use] extern crate libimagutil;
module_entry_path_mod!("links", "0.1.0"); module_entry_path_mod!("links", "0.1.0");

View file

@ -5,9 +5,12 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies] [dependencies]
clap = "2.1.1" clap = "2.1.1"
log = "0.3.5" log = "0.3"
toml = "0.1.25" toml = "0.1.25"
[dependencies.libimagstore] [dependencies.libimagstore]
path = "../libimagstore" path = "../libimagstore"
[dependencies.libimagerror]
path = "../libimagerror"

View file

@ -1,85 +1,12 @@
use std::error::Error; generate_error_module!(
use std::fmt::Error as FmtError; generate_error_types!(ListError, ListErrorKind,
use std::clone::Clone; FormatError => "FormatError",
use std::fmt::{Display, Formatter}; EntryError => "EntryError",
IterationError => "IterationError",
CLIError => "No CLI subcommand for listing entries"
);
);
/** pub use self::error::ListError;
* Kind of error pub use self::error::ListErrorKind;
*/
#[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<Box<Error>>,
}
impl ListError {
/**
* Build a new ListError from an ListErrorKind, optionally with cause
*/
pub fn new(errtype: ListErrorKind, cause: Option<Box<Error>>) -> 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)
}
}

View file

@ -19,6 +19,7 @@ extern crate clap;
extern crate toml; extern crate toml;
extern crate libimagstore; extern crate libimagstore;
#[macro_use] extern crate libimagerror;
pub mod cli; pub mod cli;
pub mod error; pub mod error;

View file

@ -27,12 +27,19 @@ impl<'a> Lister for CoreLister<'a> {
use error::ListError as LE; use error::ListError as LE;
use error::ListErrorKind as LEK; use error::ListErrorKind as LEK;
entries.fold(Ok(()), |accu, entry| { debug!("Called list()");
accu.and_then(|_| { let (r, n) = entries
write!(stdout(), "{:?}\n", (self.lister)(&entry)) .fold((Ok(()), 0), |(accu, i), entry| {
.map_err(|e| LE::new(LEK::FormatError, Some(Box::new(e)))) 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
} }
} }

View file

@ -32,7 +32,7 @@ impl Lister for PathLister {
if self.absolute { if self.absolute {
pb.canonicalize().map_err(|e| LE::new(LEK::FormatError, Some(Box::new(e)))) pb.canonicalize().map_err(|e| LE::new(LEK::FormatError, Some(Box::new(e))))
} else { } else {
Ok(pb) Ok(pb.into())
} }
}) })
.and_then(|pb| { .and_then(|pb| {

View file

@ -0,0 +1,17 @@
[package]
name = "libimagentrymarkdown"
version = "0.1.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies]
log = "0.3"
hoedown = "5.0.0"
crossbeam = "0.2"
url = "1.1"
[dependencies.libimagstore]
path = "../libimagstore"
[dependencies.libimagerror]
path = "../libimagerror"

View file

@ -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.

View file

@ -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;

View file

@ -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<HTML> {
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: Iterator<Item = Entry>> {
i: I
}
impl<I: Iterator<Item = Entry>> ToHtmlIterator<I> {
fn new(i: I) -> ToHtmlIterator<I> {
ToHtmlIterator { i: i }
}
}
impl<I: Iterator<Item = Entry>> Iterator for ToHtmlIterator<I> {
type Item = Result<HTML>;
fn next(&mut self) -> Option<Self::Item> {
self.i.next().map(|entry| to_html(&entry.get_content()[..]))
}
}
impl<I: Iterator<Item = Entry>> From<I> for ToHtmlIterator<I> {
fn from(obj: I) -> ToHtmlIterator<I> {
ToHtmlIterator::new(obj)
}
}
/// Iterate over `(Entry, Result<HTML>)` tuples
pub struct WithHtmlIterator<I: Iterator<Item = Entry>> {
i: I
}
impl<I: Iterator<Item = Entry>> WithHtmlIterator<I> {
fn new(i: I) -> WithHtmlIterator<I> {
WithHtmlIterator { i: i }
}
}
impl<I: Iterator<Item = Entry>> Iterator for WithHtmlIterator<I> {
type Item = (Entry, Result<HTML>);
fn next(&mut self) -> Option<Self::Item> {
self.i.next().map(|entry| {
let html = to_html(&entry.get_content()[..]);
(entry, html)
})
}
}
}

View file

@ -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;

View file

@ -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<UrlLink> {
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<Link>,
}
impl LinkExtractor {
pub fn new() -> LinkExtractor {
LinkExtractor { links: vec![] }
}
pub fn links(self) -> Vec<Link> {
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<Link> {
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());
}
}

View file

@ -0,0 +1,6 @@
use std::result::Result as RResult;
use error::MarkdownError;
pub type Result<T> = RResult<T, MarkdownError>;

View file

@ -0,0 +1,16 @@
[package]
name = "libimagentryselect"
version = "0.1.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies]
clap = "2"
log = "0.3"
interactor = "0.1"
[dependencies.libimagstore]
path = "../libimagstore"
[dependencies.libimagerror]
path = "../libimagerror"

View file

@ -0,0 +1,9 @@
extern crate clap;
extern crate log;
extern crate interactor;
extern crate libimagstore;
extern crate libimagerror;
pub mod ui;

View file

@ -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<Vec<StoreId>> {
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<Vec<StoreId>> {
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)]),
}
})
}

View file

@ -5,7 +5,7 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies] [dependencies]
clap = "2.1.1" clap = "2.1.1"
log = "0.3.5" log = "0.3"
regex = "0.1" regex = "0.1"
toml = "0.1.25" toml = "0.1.25"
itertools = "0.4" itertools = "0.4"
@ -13,3 +13,9 @@ itertools = "0.4"
[dependencies.libimagstore] [dependencies.libimagstore]
path = "../libimagstore" path = "../libimagstore"
[dependencies.libimagerror]
path = "../libimagerror"
[dependencies.libimagutil]
path = "../libimagutil"

View file

@ -1,69 +1,12 @@
use std::error::Error; generate_error_module!(
use std::fmt::Error as FmtError; generate_error_types!(TagError, TagErrorKind,
use std::clone::Clone; TagTypeError => "Entry Header Tag Type wrong",
use std::fmt::{Display, Formatter}; HeaderReadError => "Error while reading entry header",
HeaderWriteError => "Error while writing entry header",
NotATag => "String is not a tag"
);
);
#[derive(Clone, Copy, Debug, PartialEq)] pub use self::error::TagError;
pub enum TagErrorKind { pub use self::error::TagErrorKind;
TagTypeError,
HeaderReadError,
HeaderWriteError,
NotATag,
}
fn tag_error_type_as_str(e: &TagErrorKind) -> &'static str {
match e {
&TagErrorKind::TagTypeError => "Entry Header Tag Type wrong",
&TagErrorKind::HeaderReadError => "Error while reading entry header",
&TagErrorKind::HeaderWriteError => "Error while writing entry header",
&TagErrorKind::NotATag => "String is not a tag",
}
}
impl Display for TagErrorKind {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "{}", tag_error_type_as_str(self)));
Ok(())
}
}
#[derive(Debug)]
pub struct TagError {
kind: TagErrorKind,
cause: Option<Box<Error>>,
}
impl TagError {
pub fn new(errtype: TagErrorKind, cause: Option<Box<Error>>) -> TagError {
TagError {
kind: errtype,
cause: cause,
}
}
}
impl Display for TagError {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "[{}]", tag_error_type_as_str(&self.kind.clone())));
Ok(())
}
}
impl Error for TagError {
fn description(&self) -> &str {
tag_error_type_as_str(&self.kind.clone())
}
fn cause(&self) -> Option<&Error> {
self.cause.as_ref().map(|e| &**e)
}
}

View file

@ -7,22 +7,20 @@ use tagable::*;
use ui::{get_add_tags, get_remove_tags}; use ui::{get_add_tags, get_remove_tags};
pub fn exec_cli_for_entry(matches: &ArgMatches, entry: &mut FileLockEntry) -> Result<()> { pub fn exec_cli_for_entry(matches: &ArgMatches, entry: &mut FileLockEntry) -> Result<()> {
match get_add_tags(matches) { if let Some(ts) = get_add_tags(matches) {
Some(ts) => for t in ts { for t in ts {
if let Err(e) = entry.add_tag(t) { if let Err(e) = entry.add_tag(t) {
return Err(e); return Err(e);
} }
}, }
None => { },
} }
match get_remove_tags(matches) { if let Some(ts) = get_remove_tags(matches) {
Some(ts) => for t in ts { for t in ts {
if let Err(e) = entry.remove_tag(t) { if let Err(e) = entry.remove_tag(t) {
return Err(e); return Err(e);
} }
}, }
None => { },
} }
Ok(()) Ok(())

View file

@ -19,6 +19,8 @@ extern crate regex;
extern crate toml; extern crate toml;
extern crate libimagstore; extern crate libimagstore;
#[macro_use] extern crate libimagerror;
#[macro_use] extern crate libimagutil;
pub mod error; pub mod error;
pub mod exec; pub mod exec;

View file

@ -1 +1,2 @@
pub type Tag = String; pub type Tag = String;
pub type TagSlice<'a> = &'a str;

View file

@ -4,10 +4,11 @@ use std::ops::DerefMut;
use itertools::Itertools; use itertools::Itertools;
use libimagstore::store::{Entry, EntryHeader, FileLockEntry}; use libimagstore::store::{Entry, EntryHeader, FileLockEntry};
use libimagerror::into::IntoError;
use error::{TagError, TagErrorKind}; use error::TagErrorKind;
use result::Result; use result::Result;
use tag::Tag; use tag::{Tag, TagSlice};
use util::is_tag; use util::is_tag;
use toml::Value; use toml::Value;
@ -15,13 +16,13 @@ use toml::Value;
pub trait Tagable { pub trait Tagable {
fn get_tags(&self) -> Result<Vec<Tag>>; fn get_tags(&self) -> Result<Vec<Tag>>;
fn set_tags(&mut self, ts: Vec<Tag>) -> Result<()>; fn set_tags(&mut self, ts: &[Tag]) -> Result<()>;
fn add_tag(&mut self, t: Tag) -> Result<()>; fn add_tag(&mut self, t: Tag) -> Result<()>;
fn remove_tag(&mut self, t: Tag) -> Result<()>; fn remove_tag(&mut self, t: Tag) -> Result<()>;
fn has_tag(&self, t: &Tag) -> Result<bool>; fn has_tag(&self, t: TagSlice) -> Result<bool>;
fn has_tags(&self, ts: &Vec<Tag>) -> Result<bool>; fn has_tags(&self, ts: &[Tag]) -> Result<bool>;
} }
@ -31,20 +32,20 @@ impl Tagable for EntryHeader {
let tags = self.read("imag.tags"); let tags = self.read("imag.tags");
if tags.is_err() { if tags.is_err() {
let kind = TagErrorKind::HeaderReadError; 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(); let tags = tags.unwrap();
match tags { match tags {
Some(Value::Array(tags)) => { Some(Value::Array(tags)) => {
if !tags.iter().all(|t| match t { &Value::String(_) => true, _ => false }) { if !tags.iter().all(|t| is_match!(*t, Value::String(_))) {
return Err(TagError::new(TagErrorKind::TagTypeError, None)); return Err(TagErrorKind::TagTypeError.into());
} }
if tags.iter().any(|t| match t { if tags.iter().any(|t| match *t {
&Value::String(ref s) => !is_tag(&s), Value::String(ref s) => !is_tag(s),
_ => unreachable!()}) _ => unreachable!()})
{ {
return Err(TagError::new(TagErrorKind::NotATag, None)); return Err(TagErrorKind::NotATag.into());
} }
Ok(tags.iter() Ok(tags.iter()
@ -58,32 +59,33 @@ impl Tagable for EntryHeader {
.collect()) .collect())
}, },
None => Ok(vec![]), None => Ok(vec![]),
_ => Err(TagError::new(TagErrorKind::TagTypeError, None)), _ => Err(TagErrorKind::TagTypeError.into()),
} }
} }
fn set_tags(&mut self, ts: Vec<Tag>) -> Result<()> { fn set_tags(&mut self, ts: &[Tag]) -> Result<()> {
if ts.iter().any(|tag| !is_tag(tag)) { if ts.iter().any(|tag| !is_tag(tag)) {
debug!("Not a tag: '{}'", ts.iter().filter(|t| !is_tag(t)).next().unwrap()); 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(); let a = ts.iter().unique().map(|t| Value::String(t.clone())).collect();
self.set("imag.tags", Value::Array(a)) self.set("imag.tags", Value::Array(a))
.map(|_| ()) .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<()> { fn add_tag(&mut self, t: Tag) -> Result<()> {
if !is_tag(&t) { if !is_tag(&t) {
debug!("Not a tag: '{}'", t); debug!("Not a tag: '{}'", t);
return Err(TagError::new(TagErrorKind::NotATag, None)); return Err(TagErrorKind::NotATag.into());
} }
self.get_tags() self.get_tags()
.map(|mut tags| { .map(|mut tags| {
tags.push(t); tags.push(t);
self.set_tags(tags.into_iter().unique().collect()) self.set_tags(&tags.into_iter().unique().collect::<Vec<_>>()[..])
}) })
.map(|_| ()) .map(|_| ())
} }
@ -91,40 +93,40 @@ impl Tagable for EntryHeader {
fn remove_tag(&mut self, t: Tag) -> Result<()> { fn remove_tag(&mut self, t: Tag) -> Result<()> {
if !is_tag(&t) { if !is_tag(&t) {
debug!("Not a tag: '{}'", t); debug!("Not a tag: '{}'", t);
return Err(TagError::new(TagErrorKind::NotATag, None)); return Err(TagErrorKind::NotATag.into());
} }
self.get_tags() self.get_tags()
.map(|mut tags| { .map(|mut tags| {
tags.retain(|tag| tag.clone() != t); tags.retain(|tag| tag.clone() != t);
self.set_tags(tags) self.set_tags(&tags[..])
}) })
.map(|_| ()) .map(|_| ())
} }
fn has_tag(&self, t: &Tag) -> Result<bool> { fn has_tag(&self, t: TagSlice) -> Result<bool> {
let tags = self.read("imag.tags"); let tags = self.read("imag.tags");
if tags.is_err() { if tags.is_err() {
let kind = TagErrorKind::HeaderReadError; 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(); let tags = tags.unwrap();
if !tags.iter().all(|t| match t { &Value::String(_) => true, _ => false }) { if !tags.iter().all(|t| is_match!(*t, Value::String(_))) {
return Err(TagError::new(TagErrorKind::TagTypeError, None)); return Err(TagErrorKind::TagTypeError.into());
} }
Ok(tags Ok(tags
.iter() .iter()
.any(|tag| { .any(|tag| {
match tag { match *tag {
&Value::String(ref s) => { s == t }, Value::String(ref s) => { s == t },
_ => unreachable!() _ => unreachable!()
} }
})) }))
} }
fn has_tags(&self, tags: &Vec<Tag>) -> Result<bool> { fn has_tags(&self, tags: &[Tag]) -> Result<bool> {
let mut result = true; let mut result = true;
for tag in tags { for tag in tags {
let check = self.has_tag(tag); let check = self.has_tag(tag);
@ -147,7 +149,7 @@ impl Tagable for Entry {
self.get_header().get_tags() self.get_header().get_tags()
} }
fn set_tags(&mut self, ts: Vec<Tag>) -> Result<()> { fn set_tags(&mut self, ts: &[Tag]) -> Result<()> {
self.get_header_mut().set_tags(ts) self.get_header_mut().set_tags(ts)
} }
@ -159,11 +161,11 @@ impl Tagable for Entry {
self.get_header_mut().remove_tag(t) self.get_header_mut().remove_tag(t)
} }
fn has_tag(&self, t: &Tag) -> Result<bool> { fn has_tag(&self, t: TagSlice) -> Result<bool> {
self.get_header().has_tag(t) self.get_header().has_tag(t)
} }
fn has_tags(&self, ts: &Vec<Tag>) -> Result<bool> { fn has_tags(&self, ts: &[Tag]) -> Result<bool> {
self.get_header().has_tags(ts) self.get_header().has_tags(ts)
} }
@ -175,7 +177,7 @@ impl<'a> Tagable for FileLockEntry<'a> {
self.deref().get_tags() self.deref().get_tags()
} }
fn set_tags(&mut self, ts: Vec<Tag>) -> Result<()> { fn set_tags(&mut self, ts: &[Tag]) -> Result<()> {
self.deref_mut().set_tags(ts) self.deref_mut().set_tags(ts)
} }
@ -187,11 +189,11 @@ impl<'a> Tagable for FileLockEntry<'a> {
self.deref_mut().remove_tag(t) self.deref_mut().remove_tag(t)
} }
fn has_tag(&self, t: &Tag) -> Result<bool> { fn has_tag(&self, t: TagSlice) -> Result<bool> {
self.deref().has_tag(t) self.deref().has_tag(t)
} }
fn has_tags(&self, ts: &Vec<Tag>) -> Result<bool> { fn has_tags(&self, ts: &[Tag]) -> Result<bool> {
self.deref().has_tags(ts) self.deref().has_tags(ts)
} }

View file

@ -2,27 +2,35 @@ use clap::{Arg, ArgMatches, App, SubCommand};
use tag::Tag; 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. /// "tags --add foo --remove bar" subcommand to do tagging action.
pub fn tag_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn tag_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name(tag_subcommand_name()) SubCommand::with_name(tag_subcommand_name())
.author("Matthias Beyer <mail@beyermatthias.de>") .author("Matthias Beyer <mail@beyermatthias.de>")
.version("0.1") .version("0.1")
.about("Add or remove tags") .about("Add or remove tags")
.arg(tag_add_arg())
.arg(tag_remove_arg())
}
.arg(Arg::with_name(tag_subcommand_add_arg_name()) pub fn tag_add_arg<'a, 'b>() -> Arg<'a, 'b> {
.short("a") Arg::with_name(tag_subcommand_add_arg_name())
.long("add") .short("a")
.takes_value(true) .long("add")
.multiple(true) .takes_value(true)
.help("Add tags, seperated by comma or by specifying multiple times")) .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()) pub fn tag_remove_arg<'a, 'b>() -> Arg<'a, 'b> {
.short("r") Arg::with_name(tag_subcommand_remove_arg_name())
.long("remove") .short("r")
.takes_value(true) .long("remove")
.multiple(true) .takes_value(true)
.help("Remove tags, seperated by comma or by specifying multiple times")) .value_name("tags")
.multiple(true)
.help("Remove tags, seperated by comma or by specifying multiple times")
} }
pub fn tag_subcommand_name() -> &'static str { 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()] 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) /// "-t" or "--tags" argument which takes values for tagging actions (add, remove)
pub fn tag_argument<'a, 'b>() -> Arg<'a, 'b> { pub fn tag_argument<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(tag_argument_name()) 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 /// Returns none if the argument was not specified
pub fn get_add_tags(matches: &ArgMatches) -> Option<Vec<Tag>> { pub fn get_add_tags(matches: &ArgMatches) -> Option<Vec<Tag>> {
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 /// Get the tags which should be removed from the commandline
/// ///
/// Returns none if the argument was not specified /// Returns none if the argument was not specified
pub fn get_remove_tags(matches: &ArgMatches) -> Option<Vec<Tag>> { pub fn get_remove_tags(matches: &ArgMatches) -> Option<Vec<Tag>> {
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<Vec<Tag>> { fn extract_tags(matches: &ArgMatches, specifier: &str, specchar: char) -> Option<Vec<Tag>> {
@ -79,7 +91,7 @@ fn extract_tags(matches: &ArgMatches, specifier: &str, specchar: char) -> Option
.map(|argmatches| { .map(|argmatches| {
argmatches argmatches
.map(String::from) .map(String::from)
.filter(|s| s.chars().next() == Some(specchar)) .filter(|s| s.starts_with(specchar))
.map(|s| { .map(|s| {
String::from(s.split_at(1).1) String::from(s.split_at(1).1)
}) })

View file

@ -1,5 +1,5 @@
use regex::Regex; use regex::Regex;
pub fn is_tag(s: &String) -> bool { pub fn is_tag(s: &str) -> bool {
Regex::new("^[a-zA-Z]([a-zA-Z0-9_-]*)$").unwrap().captures(&s[..]).is_some() Regex::new("^[a-zA-Z]([a-zA-Z0-9_-]*)$").unwrap().captures(s).is_some()
} }

View file

@ -8,3 +8,6 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies.libimagstore] [dependencies.libimagstore]
path = "../libimagstore" path = "../libimagstore"
[dependencies.libimagerror]
path = "../libimagerror"

View file

@ -1,80 +1,9 @@
use std::error::Error; generate_error_module!(
use std::fmt::Error as FmtError; generate_error_types!(ViewError, ViewErrorKind,
use std::clone::Clone; Unknown => "Unknown view error"
use std::fmt::{Display, Formatter}; );
);
/** pub use self::error::ViewError;
* Kind of error pub use self::error::ViewErrorKind;
*/
#[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<Box<Error>>,
}
impl ViewError {
/**
* Build a new ViewError from an ViewErrorKind, optionally with cause
*/
pub fn new(errtype: ViewErrorKind, cause: Option<Box<Error>>)
-> 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)
}
}

Some files were not shown because too many files have changed in this diff Show more