Merge branch 'master' of https://github.com/matthiasbeyer/imag into imag-task
This commit is contained in:
commit
84deb2fe54
149 changed files with 4492 additions and 2281 deletions
185
CONTRIBUTING.md
185
CONTRIBUTING.md
|
@ -5,6 +5,31 @@ So you want to contribute to imag! Thank you, that's awesome of you!
|
|||
If you already have something in mind, go ahead with [the prerequisites
|
||||
section](#prerequisites). If you don't know what you could do, start here.
|
||||
|
||||
All contribors agree to the
|
||||
[developer certificate of origin](#developer-certificate-of-origin)
|
||||
by contributing to imag.
|
||||
|
||||
## Without Github
|
||||
|
||||
If you do not want to use github for your contribution, this is completely okay
|
||||
with us. Feel free to contact [me](https://github.com/matthiasbeyer) via mail,
|
||||
feel also free to submit patches via mail (use `git format-patch` and
|
||||
`git send-email`, always add a cover letter to describe your submission).
|
||||
|
||||
Also ensure that each commit has
|
||||
[a "Signed-off-by: " line](https://stackoverflow.com/questions/1962094/what-is-the-sign-off-feature-in-git-for).
|
||||
By adding that line, you agree to our
|
||||
[developer certificate of origin](#developer-certificate-of-origin).
|
||||
|
||||
We do not have a mailinglist, so we do not have a process where all contributors
|
||||
can review your PR. This means that once _I am_ okay with your patchset, I will
|
||||
submit it as PR in the github repository, so more people can review it and CI
|
||||
can test it. I might come back to you if something broke in CI or someone has a
|
||||
suggestion how to improve your PR.
|
||||
I will keep you as author of the commits.
|
||||
|
||||
The following sections describe the way how to contribute with github.
|
||||
|
||||
## Finding an issue
|
||||
|
||||
Finding an issue is simple: We have
|
||||
|
@ -95,7 +120,167 @@ ask questions as well!
|
|||
|
||||
Feel free to reach out via mail.
|
||||
|
||||
## More information about the structure of this project
|
||||
|
||||
Here goes some notes on how this project is structured.
|
||||
|
||||
### Issue- and PR-Labels
|
||||
|
||||
Our labels are color coded as well as "namespaced". The color groups labels
|
||||
exactly as the prefix does. The prefix is the first component which is seperated
|
||||
from the others by `"/`". See below:
|
||||
|
||||
| Label | Description | search |
|
||||
| --- | --- | --- |
|
||||
| complexity/easy | Easy to do | [search][search-complexity/easy] |
|
||||
| complexity/high | Not so easy to do | [search][search-complexity/high] |
|
||||
| complexity/medium | Relatively easy | [search][search-complexity/medium] |
|
||||
| kind/bug | Bug | [search][search-kind/bug] |
|
||||
| kind/doc | Documentation related | [search][search-kind/doc] |
|
||||
| kind/enhancement | Enhancement | [search][search-kind/enhancement] |
|
||||
| kind/feature | Feature | [search][search-kind/feature] |
|
||||
| kind/hotfix | Hotfix | [search][search-kind/hotfix] |
|
||||
| kind/infrastructure | Infrastructure code | [search][search-kind/infrastructure] |
|
||||
| kind/invalid | Not valid Issue/PR | [search][search-kind/invalid] |
|
||||
| kind/nicetohave | Would be a nice thing | [search][search-kind/nicetohave] |
|
||||
| kind/refactor | Refactor codebase | [search][search-kind/refactor] |
|
||||
| meta/assigned | Is assigned | [search][search-meta/assigned] |
|
||||
| meta/blocked | Blocked by other Issue/PR | [search][search-meta/blocked] |
|
||||
| meta/blocker | Blocks other Issue/PR | [search][search-meta/blocker] |
|
||||
| meta/decision-pending | Not clear what to do | [search][search-meta/decision-pending] |
|
||||
| meta/dependencies | Dependency-related | [search][search-meta/dependencies] |
|
||||
| meta/doc | Documentation related | [search][search-meta/doc] |
|
||||
| meta/importance/high | Very Important | [search][search-meta/importance/high] |
|
||||
| meta/importance/low | Not so important | [search][search-meta/importance/low] |
|
||||
| meta/importance/medium | Rather important | [search][search-meta/importance/medium] |
|
||||
| meta/on-hold | Do not work on this! | [search][search-meta/on-hold] |
|
||||
| meta/ready | Ready for review/merge | [search][search-meta/ready] |
|
||||
| meta/reopen-later | Reopen closed issue/pr later | [search][search-meta/reopen-later] |
|
||||
| meta/WIP | Work in Progress | [search][search-meta/WIP] |
|
||||
| nochange/duplicate | Duplicated | [search][search-nochange/duplicate] |
|
||||
| nochange/question | Question | [search][search-nochange/question] |
|
||||
| nochange/rfc | Request for comments | [search][search-nochange/rfc] |
|
||||
| nochange/wontfix | Won't fix this issue | [search][search-nochange/wontfix] |
|
||||
| part/bin/imag-counter | Targets binary: imag-counter | [search][search-part/bin/imag-counter] |
|
||||
| part/bin/imag-link | Targets binary: imag-link | [search][search-part/bin/imag-link] |
|
||||
| part/bin/imag-store | Targets binary: imag-store | [search][search-part/bin/imag-store] |
|
||||
| part/bin/imag-tag | Targets binary: imag-tag | [search][search-part/bin/imag-tag] |
|
||||
| part/bin/imag-view | Targets binary: imag-view | [search][search-part/bin/imag-view] |
|
||||
| part/interface | Changes the interface | [search][search-part/interface] |
|
||||
| part/lib/imagcounter | Targets library: imagcounter | [search][search-part/lib/imagcounter] |
|
||||
| part/lib/imagentryfilter | Targets library: imagentryfilter | [search][search-part/lib/imagentryfilter] |
|
||||
| part/lib/imagentrylink | Targets library: imagentrylink | [search][search-part/lib/imagentrylink] |
|
||||
| part/lib/imagentrylist | Targets library: imagentrylist | [search][search-part/lib/imagentrylist] |
|
||||
| part/lib/imagentrymarkup | Targets library: imagentrymarkup | [search][search-part/lib/imagentrymarkup] |
|
||||
| part/lib/imagentryprinter | Targets library: imagentryprinter | [search][search-part/lib/imagentryprinter] |
|
||||
| part/lib/imagentrytag | Targets library: imagentrytag | [search][search-part/lib/imagentrytag] |
|
||||
| part/lib/imagentryview | Targets library: imagentryview | [search][search-part/lib/imagentryview] |
|
||||
| part/lib/imagnotes | Targets library: imagnotes | [search][search-part/lib/imagnotes] |
|
||||
| part/lib/imagrt | Targets library: imagrt | [search][search-part/lib/imagrt] |
|
||||
| part/lib/imagstore | Targets library: imagstore | [search][search-part/lib/imagstore] |
|
||||
| part/lib/imagstorestdhook | Targets library: imagstorestdhook | [search][search-part/lib/imagstorestdhook] |
|
||||
| part/lib/imagutil | Targets library: | [search][search-part/lib/imagutil] |
|
||||
| part/_new_binary | Introduces new binary | [search][search-part/_new_binary] |
|
||||
| part/_new_library | Introduces new library | [search][search-part/_new_library] |
|
||||
| test/change | Changes a test | [search][search-test/change] |
|
||||
| test/missing | Test missing | [search][search-test/missing] |
|
||||
| test/new | New test | [search][search-test/new] |
|
||||
|
||||
## Developer Certificate of Origin
|
||||
|
||||
```
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
660 York Street, Suite 102,
|
||||
San Francisco, CA 94110 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
_to be written_
|
||||
|
||||
[search-complexity/easy]: https://github.com/matthiasbeyer/imag/labels/
|
||||
[search-complexity/high]: https://github.com/matthiasbeyer/imag/labels/complexity%2Fhigh
|
||||
[search-complexity/medium]: https://github.com/matthiasbeyer/imag/labels/complexity%2Fmedium
|
||||
[search-kind/bug]: https://github.com/matthiasbeyer/imag/labels/kind%2Fbug
|
||||
[search-kind/doc]: https://github.com/matthiasbeyer/imag/labels/kind%2Fdoc
|
||||
[search-kind/enhancement]: https://github.com/matthiasbeyer/imag/labels/kind%2Fenhancement
|
||||
[search-kind/feature]: https://github.com/matthiasbeyer/imag/labels/kind%2Ffeature
|
||||
[search-kind/hotfix]: https://github.com/matthiasbeyer/imag/labels/kind%2Fhotfix
|
||||
[search-kind/infrastructure]: https://github.com/matthiasbeyer/imag/labels/kind%2Finfrastructure
|
||||
[search-kind/invalid]: https://github.com/matthiasbeyer/imag/labels/kind%2Finvalid
|
||||
[search-kind/nicetohave]: https://github.com/matthiasbeyer/imag/labels/kind%2Fnicetohave
|
||||
[search-kind/refactor]: https://github.com/matthiasbeyer/imag/labels/kind%2Frefactor
|
||||
[search-meta/assigned]: https://github.com/matthiasbeyer/imag/labels/meta%2Fassigned
|
||||
[search-meta/blocked]: https://github.com/matthiasbeyer/imag/labels/meta%2Fblocked
|
||||
[search-meta/blocker]: https://github.com/matthiasbeyer/imag/labels/meta%2Fblocker
|
||||
[search-meta/decision-pending]: https://github.com/matthiasbeyer/imag/labels/meta%2Fdecision-pending
|
||||
[search-meta/dependencies]: https://github.com/matthiasbeyer/imag/labels/meta%2Fdependencies
|
||||
[search-meta/doc]: https://github.com/matthiasbeyer/imag/labels/meta%2Fdoc
|
||||
[search-meta/importance/high]: https://github.com/matthiasbeyer/imag/labels/meta%2Fimportance%2Fhigh
|
||||
[search-meta/importance/low]: https://github.com/matthiasbeyer/imag/labels/meta%2Fimportance%2Flow
|
||||
[search-meta/importance/medium]: https://github.com/matthiasbeyer/imag/labels/meta%2Fimportance%2Fmedium
|
||||
[search-meta/on-hold]: https://github.com/matthiasbeyer/imag/labels/meta%2Fon-hold
|
||||
[search-meta/ready]: https://github.com/matthiasbeyer/imag/labels/meta%2Fready
|
||||
[search-meta/reopen-later]: https://github.com/matthiasbeyer/imag/labels/meta%2Freopen-later
|
||||
[search-meta/WIP]: https://github.com/matthiasbeyer/imag/labels/meta%2FWIP
|
||||
[search-nochange/duplicate]: https://github.com/matthiasbeyer/imag/labels/nochange%2Fduplicate
|
||||
[search-nochange/question]: https://github.com/matthiasbeyer/imag/labels/nochange%2Fquestion
|
||||
[search-nochange/rfc]: https://github.com/matthiasbeyer/imag/labels/nochange%2Frfc
|
||||
[search-nochange/wontfix]: https://github.com/matthiasbeyer/imag/labels/nochange%2Fwontfix
|
||||
[search-part/bin/imag-counter]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-counter
|
||||
[search-part/bin/imag-link]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-link
|
||||
[search-part/bin/imag-store]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-store
|
||||
[search-part/bin/imag-tag]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-tag
|
||||
[search-part/bin/imag-view]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-view
|
||||
[search-part/interface]: https://github.com/matthiasbeyer/imag/labels/part%2F_interface
|
||||
[search-part/lib/imagcounter]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagcounter
|
||||
[search-part/lib/imagentryfilter]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentryfilter
|
||||
[search-part/lib/imagentrylink]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentrylink
|
||||
[search-part/lib/imagentrylist]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentrylist
|
||||
[search-part/lib/imagentrymarkup]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentrymarkup
|
||||
[search-part/lib/imagentryprinter]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentryprinter
|
||||
[search-part/lib/imagentrytag]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentrytag
|
||||
[search-part/lib/imagentryview]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentryview
|
||||
[search-part/lib/imagnotes]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagnotes
|
||||
[search-part/lib/imagrt]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagrt
|
||||
[search-part/lib/imagstore]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagstore
|
||||
[search-part/lib/imagstorestdhook]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagstorestdhook
|
||||
[search-part/lib/imagutil]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagutil
|
||||
[search-part/_new_binary]: https://github.com/matthiasbeyer/imag/labels/part%2F_new_binary
|
||||
[search-part/_new_library]: https://github.com/matthiasbeyer/imag/labels/part%2F_new_library
|
||||
[search-test/change]: https://github.com/matthiasbeyer/imag/labels/test%2Fchange
|
||||
[search-test/missing]: https://github.com/matthiasbeyer/imag/labels/test%2Fmissing
|
||||
[search-test/new]: https://github.com/matthiasbeyer/imag/labels/test%2Fnew
|
||||
|
|
18
Makefile
Normal file
18
Makefile
Normal 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/
|
26
README.md
26
README.md
|
@ -1,7 +1,7 @@
|
|||
# imag
|
||||
|
||||
Imag is a CLI PIM suite with a nice API-ish commandline interface, so you can
|
||||
integrate it in your tools of coice (Editor, MUA, RSS reader, etc etc).
|
||||
integrate it in your tools of choice (Editor, MUA, RSS reader, etc etc).
|
||||
|
||||
## Goal
|
||||
|
||||
|
@ -89,26 +89,26 @@ provided (as the libraries are work-in-progress).
|
|||
|
||||
### Building
|
||||
|
||||
To build a single module:
|
||||
One can build all the modules simply by running `make` which defaults to building all the modules
|
||||
and placing them in the `out/` directory of the project root.
|
||||
|
||||
```
|
||||
$> cd <imag-proj-dir>/imag-<module>
|
||||
$> cargo build
|
||||
```
|
||||
It may be tiresome to build all the modules by hand, but one can do something
|
||||
like this:
|
||||
```
|
||||
$> for dir in \
|
||||
>$(find ./ -maxdepth 1 -path "./imag-*" -name "imag-*" -type d)
|
||||
>do
|
||||
>pushd $dir; cargo build; popd
|
||||
>done
|
||||
$> make
|
||||
...
|
||||
$> ls out/
|
||||
imag-counter imag-link imag-notes imag-store imag-tag imag-view
|
||||
```
|
||||
|
||||
Building all the modules may take some time, so alternatively one can build only a specific module
|
||||
by runing `$> make $module` where `$module` is one of the `imag-*` names, such as `imag-counter`,
|
||||
`imag-link`, etc.
|
||||
|
||||
### Running
|
||||
|
||||
To run imag, simply call `./bin/imag`. This script has a function to search for
|
||||
modules, which utilizes an environment variable called `IMAG_IS_THE_SHIT`.
|
||||
To run imag with all components:
|
||||
|
||||
```
|
||||
$> IMAG_IS_THE_SHIT=$(pwd) ./bin/imag
|
||||
```
|
||||
|
|
10
bin/Cargo.toml
Normal file
10
bin/Cargo.toml
Normal 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"
|
||||
|
79
bin/imag
79
bin/imag
|
@ -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
187
bin/src/main.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
}
|
20
doc/Makefile
20
doc/Makefile
|
@ -11,6 +11,7 @@ export MAKE_FLAGS=--no-print-directory
|
|||
export OUT=$(shell pwd)/bin
|
||||
export OUT_PDF=$(OUT)/pdf/
|
||||
export OUT_HTML=$(OUT)/html/
|
||||
export OUT_MAN=$(OUT)/man/
|
||||
|
||||
DOCUMENT_CLASS=article
|
||||
SETTING_FONTSIZE=11pt
|
||||
|
@ -52,6 +53,10 @@ DOCUMENT_SETTINGS_HTML= \
|
|||
--table-of-contents \
|
||||
--webtex
|
||||
|
||||
DOCUMENT_SETTINGS_MAN= \
|
||||
-s \
|
||||
--variable section=5 \
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
@ -89,8 +94,11 @@ export PANDOC_CC_PDF=$(PANDOC) \
|
|||
|
||||
export PANDOC_CC_HTML=$(PANDOC) $(PANDOC_PARAMS) $(DOCUMENT_SETTINGS_HTML)
|
||||
|
||||
export PANDOC_CC_MAN=$(PANDOC) $(PANDOC_PARAMS) $(DOCUMENT_SETTINGS_MAN)
|
||||
|
||||
TARGET_PDF=$(OUT_PDF)/paper.pdf
|
||||
TARGET_HTML=$(OUT_HTML)/index.html
|
||||
TARGET_MAN=$(OUT_MAN)/imag.5
|
||||
#
|
||||
#
|
||||
# Tasks
|
||||
|
@ -116,6 +124,11 @@ $(OUT_PDF): $(OUT)
|
|||
@$(ECHO) "\t[MKDIR ] $@"
|
||||
@$(MKDIR) $(OUT_PDF)
|
||||
|
||||
# create man out directory
|
||||
$(OUT_MAN): $(OUT)
|
||||
@$(ECHO) "\t[MKDIR ] $@"
|
||||
@$(MKDIR) $(OUT_MAN)
|
||||
|
||||
# cleanup task
|
||||
clean:
|
||||
@$(ECHO) "\t[RM ] $@"
|
||||
|
@ -137,5 +150,12 @@ $(TARGET_HTML): $(OUT_HTML)
|
|||
--template $(TEMPLATES)/default.html5 \
|
||||
$(SRC) -o $@
|
||||
|
||||
man: $(TARGET_MAN)
|
||||
|
||||
$(TARGET_MAN): $(OUT_MAN)
|
||||
@$(ECHO) "\t[PANDOC] man"
|
||||
@$(PANDOC_CC_MAN) \
|
||||
$(SRC) -o $@
|
||||
|
||||
.PHONY: $(TARGET_PDF) $(TARGET_HTML)
|
||||
|
||||
|
|
|
@ -5,12 +5,15 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
|||
|
||||
[dependencies]
|
||||
clap = "2.1.1"
|
||||
log = "0.3.5"
|
||||
log = "0.3"
|
||||
version = "2.0.1"
|
||||
|
||||
[dependencies.libimagrt]
|
||||
path = "../libimagrt"
|
||||
|
||||
[dependencies.libimagerror]
|
||||
path = "../libimagerror"
|
||||
|
||||
[dependencies.libimagutil]
|
||||
path = "../libimagutil"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::str::FromStr;
|
|||
use std::process::exit;
|
||||
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagutil::trace::trace_error;
|
||||
use libimagerror::trace::trace_error;
|
||||
use libimagcounter::counter::Counter;
|
||||
|
||||
pub fn create(rt: &Runtime) {
|
||||
|
@ -17,7 +17,7 @@ pub fn create(rt: &Runtime) {
|
|||
.and_then(|i| FromStr::from_str(i).ok())
|
||||
.unwrap_or(0);
|
||||
|
||||
match Counter::new(rt.store(), String::from(name.clone()), init) {
|
||||
match Counter::new(rt.store(), String::from(name), init) {
|
||||
Err(e) => {
|
||||
warn!("Could not create Counter '{}' with initial value '{}'", name, init);
|
||||
trace_error(&e);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::process::exit;
|
||||
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagutil::trace::trace_error;
|
||||
use libimagerror::trace::trace_error;
|
||||
use libimagcounter::counter::Counter;
|
||||
|
||||
pub fn delete(rt: &Runtime) {
|
||||
|
|
|
@ -10,7 +10,7 @@ use libimagcounter::counter::Counter;
|
|||
use libimagcounter::error::CounterError;
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagutil::key_value_split::IntoKeyValue;
|
||||
use libimagutil::trace::trace_error;
|
||||
use libimagerror::trace::trace_error;
|
||||
|
||||
type Result<T> = RResult<T, CounterError>;
|
||||
|
||||
|
@ -51,7 +51,7 @@ pub fn interactive(rt: &Runtime) {
|
|||
exit(1);
|
||||
}
|
||||
|
||||
let cont = if input.len() > 0 {
|
||||
let cont = if !input.is_empty() {
|
||||
let increment = match input.chars().next() { Some('-') => false, _ => true };
|
||||
input.chars().all(|chr| {
|
||||
match pairs.get_mut(&chr) {
|
||||
|
@ -94,8 +94,8 @@ pub fn interactive(rt: &Runtime) {
|
|||
fn has_quit_binding(pairs: &BTreeMap<char, Binding>) -> bool {
|
||||
pairs.iter()
|
||||
.any(|(_, bind)| {
|
||||
match bind {
|
||||
&Binding::Function(ref name, _) => name == "quit",
|
||||
match *bind {
|
||||
Binding::Function(ref name, _) => name == "quit",
|
||||
_ => false,
|
||||
}
|
||||
})
|
||||
|
@ -109,8 +109,8 @@ enum Binding<'a> {
|
|||
impl<'a> Display for Binding<'a> {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> RResult<(), Error> {
|
||||
match self {
|
||||
&Binding::Counter(ref c) => {
|
||||
match *self {
|
||||
Binding::Counter(ref c) => {
|
||||
match c.name() {
|
||||
Ok(name) => {
|
||||
try!(write!(fmt, "{}", name));
|
||||
|
@ -122,7 +122,7 @@ impl<'a> Display for Binding<'a> {
|
|||
},
|
||||
}
|
||||
},
|
||||
&Binding::Function(ref name, _) => write!(fmt, "{}()", name),
|
||||
Binding::Function(ref name, _) => write!(fmt, "{}()", name),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use libimagrt::runtime::Runtime;
|
||||
use libimagutil::trace::trace_error;
|
||||
use libimagerror::trace::trace_error;
|
||||
use libimagcounter::counter::Counter;
|
||||
|
||||
pub fn list(rt: &Runtime) {
|
||||
|
@ -16,13 +16,10 @@ pub fn list(rt: &Runtime) {
|
|||
|
||||
if name.is_err() {
|
||||
trace_error(&name.unwrap_err());
|
||||
} else if value.is_err() {
|
||||
trace_error(&value.unwrap_err());
|
||||
} else {
|
||||
|
||||
if value.is_err() {
|
||||
trace_error(&value.unwrap_err());
|
||||
} else {
|
||||
println!("{} - {}", name.unwrap(), value.unwrap());
|
||||
}
|
||||
println!("{} - {}", name.unwrap(), value.unwrap());
|
||||
}
|
||||
})
|
||||
.map_err(|e| trace_error(&e))
|
||||
|
|
|
@ -19,14 +19,15 @@ extern crate clap;
|
|||
|
||||
extern crate libimagcounter;
|
||||
extern crate libimagrt;
|
||||
extern crate libimagerror;
|
||||
extern crate libimagutil;
|
||||
|
||||
use std::process::exit;
|
||||
use std::str::FromStr;
|
||||
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagrt::setup::generate_runtime_setup;
|
||||
use libimagcounter::counter::Counter;
|
||||
use libimagutil::trace::trace_error;
|
||||
use libimagerror::trace::trace_error;
|
||||
use libimagutil::key_value_split::IntoKeyValue;
|
||||
|
||||
mod create;
|
||||
|
@ -49,20 +50,10 @@ enum Action {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let name = "imag-counter";
|
||||
let version = &version!()[..];
|
||||
let about = "Counter tool to count things";
|
||||
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about));
|
||||
let rt = {
|
||||
let rt = Runtime::new(ui);
|
||||
if rt.is_ok() {
|
||||
rt.unwrap()
|
||||
} else {
|
||||
println!("Could not set up Runtime");
|
||||
println!("{:?}", rt.unwrap_err());
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
let rt = generate_runtime_setup("imag-counter",
|
||||
&version!()[..],
|
||||
"Counter tool to count things",
|
||||
build_ui);
|
||||
|
||||
rt.cli()
|
||||
.subcommand_name()
|
||||
|
|
|
@ -7,26 +7,30 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("i")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Increment a counter"))
|
||||
.help("Increment a counter")
|
||||
.value_name("COUNTER"))
|
||||
|
||||
.arg(Arg::with_name("decrement")
|
||||
.long("dec")
|
||||
.short("d")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Decrement a counter"))
|
||||
.help("Decrement a counter")
|
||||
.value_name("COUNTER"))
|
||||
|
||||
.arg(Arg::with_name("reset")
|
||||
.long("reset")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Reset a counter"))
|
||||
.help("Reset a counter")
|
||||
.value_name("COUNTER"))
|
||||
|
||||
.arg(Arg::with_name("set")
|
||||
.long("set")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Set a counter"))
|
||||
.help("Set a counter")
|
||||
.value_name("COUNTER"))
|
||||
|
||||
.subcommand(SubCommand::with_name("create")
|
||||
.about("Create a counter")
|
||||
|
@ -36,13 +40,15 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("n")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Create counter with this name"))
|
||||
.help("Create counter with this name")
|
||||
.value_name("NAME"))
|
||||
.arg(Arg::with_name("initval")
|
||||
.long("init")
|
||||
.short("i")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Initial value")))
|
||||
.help("Initial value")
|
||||
.value_name("VALUE")))
|
||||
|
||||
.subcommand(SubCommand::with_name("delete")
|
||||
.about("Delete a counter")
|
||||
|
@ -52,7 +58,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("n")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Create counter with this name")))
|
||||
.help("Create counter with this name")
|
||||
.value_name("NAME")))
|
||||
|
||||
.subcommand(SubCommand::with_name("list")
|
||||
.about("List counters")
|
||||
|
@ -62,28 +69,32 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("n")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("List counters with this name (foo/bar and baz/bar would match 'bar')"))
|
||||
.help("List counters with this name (foo/bar and baz/bar would match 'bar')")
|
||||
.value_name("NAME"))
|
||||
|
||||
.arg(Arg::with_name("greater-than")
|
||||
.long("greater")
|
||||
.short("g")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("List counters which are greater than VALUE"))
|
||||
.help("List counters which are greater than VALUE")
|
||||
.value_name("VALUE"))
|
||||
|
||||
.arg(Arg::with_name("lower-than")
|
||||
.long("lower")
|
||||
.short("l")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("List counters which are lower than VALUE"))
|
||||
.help("List counters which are lower than VALUE")
|
||||
.value_name("VALUE"))
|
||||
|
||||
.arg(Arg::with_name("equals")
|
||||
.long("equal")
|
||||
.short("e")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("List counters which equal VALUE"))
|
||||
.help("List counters which equal VALUE")
|
||||
.value_name("VALUE"))
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("interactive")
|
||||
|
@ -97,6 +108,6 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.required(true)
|
||||
.help("Specification for key-bindings. Use <KEY>=<VALUE> where KEY is the
|
||||
key to bind (single character) and VALUE is the path to the counter to bind
|
||||
to.")))
|
||||
to.")
|
||||
.value_name("KEY=VALUE")))
|
||||
}
|
||||
|
||||
|
|
38
imag-diary/Cargo.toml
Normal file
38
imag-diary/Cargo.toml
Normal 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
112
imag-diary/src/create.rs
Normal 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
66
imag-diary/src/delete.rs
Normal 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
47
imag-diary/src/edit.rs
Normal 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
52
imag-diary/src/list.rs
Normal 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
72
imag-diary/src/main.rs
Normal 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
107
imag-diary/src/ui.rs
Normal 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
9
imag-diary/src/util.rs
Normal 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
36
imag-diary/src/view.rs
Normal 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);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,10 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
|||
[dependencies]
|
||||
semver = "0.2.1"
|
||||
clap = "2.1.1"
|
||||
log = "0.3.5"
|
||||
log = "0.3"
|
||||
version = "2.0.1"
|
||||
toml = "0.1.25"
|
||||
url = "0.5.5"
|
||||
url = "1.1"
|
||||
|
||||
[dependencies.libimagstore]
|
||||
path = "../libimagstore"
|
||||
|
@ -20,6 +20,6 @@ path = "../libimagrt"
|
|||
[dependencies.libimagentrylink]
|
||||
path = "../libimagentrylink"
|
||||
|
||||
[dependencies.libimagutil]
|
||||
path = "../libimagutil"
|
||||
[dependencies.libimagerror]
|
||||
path = "../libimagerror"
|
||||
|
||||
|
|
|
@ -23,17 +23,18 @@ extern crate url;
|
|||
extern crate libimagentrylink;
|
||||
extern crate libimagrt;
|
||||
extern crate libimagstore;
|
||||
extern crate libimagutil;
|
||||
extern crate libimagerror;
|
||||
|
||||
use std::process::exit;
|
||||
use std::ops::Deref;
|
||||
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagrt::setup::generate_runtime_setup;
|
||||
use libimagstore::error::StoreError;
|
||||
use libimagstore::store::Entry;
|
||||
use libimagstore::store::FileLockEntry;
|
||||
use libimagstore::store::Store;
|
||||
use libimagutil::trace::trace_error;
|
||||
use libimagerror::trace::trace_error;
|
||||
use libimagentrylink::external::ExternalLinker;
|
||||
use clap::ArgMatches;
|
||||
use url::Url;
|
||||
|
@ -43,20 +44,10 @@ mod ui;
|
|||
use ui::build_ui;
|
||||
|
||||
fn main() {
|
||||
let name = "imag-link";
|
||||
let version = &version!()[..];
|
||||
let about = "Link entries";
|
||||
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about));
|
||||
let rt = {
|
||||
let rt = Runtime::new(ui);
|
||||
if rt.is_ok() {
|
||||
rt.unwrap()
|
||||
} else {
|
||||
println!("Could not set up Runtime");
|
||||
println!("{:?}", rt.unwrap_err());
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
let rt = generate_runtime_setup("imag-link",
|
||||
&version!()[..],
|
||||
"Link entries",
|
||||
build_ui);
|
||||
|
||||
rt.cli()
|
||||
.subcommand_name()
|
||||
|
@ -74,23 +65,21 @@ fn main() {
|
|||
|
||||
fn handle_internal_linking(rt: &Runtime) {
|
||||
use libimagentrylink::internal::InternalLinker;
|
||||
use libimagutil::trace::trace_error;
|
||||
use libimagerror::trace::trace_error;
|
||||
|
||||
debug!("Handle internal linking call");
|
||||
let cmd = rt.cli().subcommand_matches("internal").unwrap();
|
||||
|
||||
if cmd.is_present("list") {
|
||||
debug!("List...");
|
||||
for entry in cmd.value_of("list").unwrap().split(",") {
|
||||
for entry in cmd.value_of("list").unwrap().split(',') {
|
||||
debug!("Listing for '{}'", entry);
|
||||
match get_entry_by_name(rt, entry) {
|
||||
Ok(e) => {
|
||||
e.get_internal_links()
|
||||
.map(|links| {
|
||||
let mut i = 0;
|
||||
for link in links.iter().map(|l| l.to_str()).filter_map(|x| x) {
|
||||
for (i, link) in links.iter().map(|l| l.to_str()).filter_map(|x| x).enumerate() {
|
||||
println!("{: <3}: {}", i, link);
|
||||
i += 1;
|
||||
}
|
||||
})
|
||||
.map_err(|e| trace_error(&e))
|
||||
|
@ -194,7 +183,7 @@ fn get_entry_by_name<'a>(rt: &'a Runtime, name: &str) -> Result<FileLockEntry<'a
|
|||
}
|
||||
|
||||
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 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")
|
||||
.map(String::from)
|
||||
.unwrap()
|
||||
.split(",")
|
||||
.split(',')
|
||||
.map(|uri| {
|
||||
match Url::parse(uri) {
|
||||
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) {
|
||||
let res = entry.get_external_links(store)
|
||||
.and_then(|links| {
|
||||
let mut i = 0;
|
||||
for link in links {
|
||||
for (i, link) in links.iter().enumerate() {
|
||||
println!("{: <3}: {}", i, link);
|
||||
i += 1;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
|
|
@ -13,14 +13,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("f")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Link from this entry"))
|
||||
.help("Link from this entry")
|
||||
.value_name("ENTRY"))
|
||||
.arg(Arg::with_name("to")
|
||||
.long("to")
|
||||
.short("t")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.multiple(true)
|
||||
.help("Link to this entries"))
|
||||
.help("Link to this entries")
|
||||
.value_name("ENTRIES"))
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("remove")
|
||||
|
@ -31,14 +33,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("f")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Remove Link from this entry"))
|
||||
.help("Remove Link from this entry")
|
||||
.value_name("ENTRY"))
|
||||
.arg(Arg::with_name("to")
|
||||
.long("to")
|
||||
.short("t")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.multiple(true)
|
||||
.help("Remove links to these entries"))
|
||||
.help("Remove links to these entries")
|
||||
.value_name("ENTRIES"))
|
||||
)
|
||||
|
||||
.arg(Arg::with_name("list")
|
||||
|
@ -46,7 +50,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("l")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("List links to this entry"))
|
||||
.help("List links to this entry")
|
||||
.value_name("ENTRY"))
|
||||
)
|
||||
.subcommand(SubCommand::with_name("external")
|
||||
.about("Add and remove external links")
|
||||
|
@ -57,14 +62,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("i")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Modify external link of this entry"))
|
||||
.help("Modify external link of this entry")
|
||||
.value_name("ENTRY"))
|
||||
|
||||
.arg(Arg::with_name("add")
|
||||
.long("add")
|
||||
.short("a")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Add this URI as external link"))
|
||||
.help("Add this URI as external link")
|
||||
.value_name("URI"))
|
||||
|
||||
.arg(Arg::with_name("remove")
|
||||
.long("remove")
|
||||
|
@ -78,7 +85,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("s")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Set these URIs as external link (seperate by comma)"))
|
||||
.help("Set these URIs as external link (seperate by comma)")
|
||||
.value_name("URIs"))
|
||||
|
||||
.arg(Arg::with_name("list")
|
||||
.long("list")
|
||||
|
@ -93,4 +101,3 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
|||
[dependencies]
|
||||
semver = "0.2.1"
|
||||
clap = "2.1.1"
|
||||
log = "0.3.5"
|
||||
log = "0.3"
|
||||
version = "2.0.1"
|
||||
|
||||
[dependencies.libimagrt]
|
||||
|
@ -18,6 +18,6 @@ path = "../libimagnotes"
|
|||
[dependencies.libimagentrytag]
|
||||
path = "../libimagentrytag"
|
||||
|
||||
[dependencies.libimagutil]
|
||||
path = "../libimagutil"
|
||||
[dependencies.libimagerror]
|
||||
path = "../libimagerror"
|
||||
|
||||
|
|
|
@ -6,33 +6,24 @@ extern crate semver;
|
|||
extern crate libimagnotes;
|
||||
extern crate libimagrt;
|
||||
extern crate libimagentrytag;
|
||||
extern crate libimagutil;
|
||||
extern crate libimagerror;
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
use libimagrt::edit::Edit;
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagrt::setup::generate_runtime_setup;
|
||||
use libimagnotes::note::Note;
|
||||
use libimagutil::trace::trace_error;
|
||||
use libimagerror::trace::trace_error;
|
||||
|
||||
mod ui;
|
||||
use ui::build_ui;
|
||||
|
||||
fn main() {
|
||||
let name = "imag-notes";
|
||||
let version = &version!()[..];
|
||||
let about = "Note taking helper";
|
||||
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about));
|
||||
let rt = {
|
||||
let rt = Runtime::new(ui);
|
||||
if rt.is_ok() {
|
||||
rt.unwrap()
|
||||
} else {
|
||||
println!("Could not set up Runtime");
|
||||
println!("{:?}", rt.unwrap_err());
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
let rt = generate_runtime_setup("imag-notes",
|
||||
&version!()[..],
|
||||
"Note taking helper",
|
||||
build_ui);
|
||||
|
||||
rt.cli()
|
||||
.subcommand_name()
|
||||
|
@ -60,10 +51,9 @@ fn create(rt: &Runtime) {
|
|||
.map_err(|e| trace_error(&e))
|
||||
.ok();
|
||||
|
||||
if rt.cli().subcommand_matches("create").unwrap().is_present("edit") {
|
||||
if !edit_entry(rt, name) {
|
||||
exit(1);
|
||||
}
|
||||
if rt.cli().subcommand_matches("create").unwrap().is_present("edit") &&
|
||||
!edit_entry(rt, name) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,14 +69,19 @@ fn edit(rt: &Runtime) {
|
|||
}
|
||||
|
||||
fn edit_entry(rt: &Runtime, name: String) -> bool {
|
||||
let note = Note::retrieve(rt.store(), name);
|
||||
if note.is_err() {
|
||||
trace_error(¬e.unwrap_err());
|
||||
warn!("Cannot edit nonexistent Note");
|
||||
return false
|
||||
}
|
||||
let mut note = match Note::get(rt.store(), name) {
|
||||
Ok(Some(note)) => note,
|
||||
Ok(None) => {
|
||||
warn!("Cannot edit nonexistent Note");
|
||||
return false
|
||||
},
|
||||
Err(e) => {
|
||||
trace_error(&e);
|
||||
warn!("Cannot edit nonexistent Note");
|
||||
return false
|
||||
},
|
||||
};
|
||||
|
||||
let mut note = note.unwrap();
|
||||
if let Err(e) = note.edit_content(rt) {
|
||||
trace_error(&e);
|
||||
warn!("Editing failed");
|
||||
|
|
|
@ -13,7 +13,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("n")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Create Note with this name"))
|
||||
.help("Create Note with this name")
|
||||
.value_name("NAME"))
|
||||
.arg(Arg::with_name("edit")
|
||||
.long("edit")
|
||||
.short("e")
|
||||
|
@ -30,7 +31,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("n")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Delete Note with this name")))
|
||||
.help("Delete Note with this name")
|
||||
.value_name("NAME")))
|
||||
|
||||
.subcommand(SubCommand::with_name("edit")
|
||||
.about("Edit a Note")
|
||||
|
@ -40,7 +42,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("n")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Edit Note with this name"))
|
||||
.help("Edit Note with this name")
|
||||
.value_name("NAME"))
|
||||
|
||||
.arg(tag_argument())
|
||||
.group(ArgGroup::with_name("editargs")
|
||||
|
@ -53,5 +56,3 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.version("0.1"))
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
|||
|
||||
[dependencies]
|
||||
clap = "2.1.1"
|
||||
log = "0.3.5"
|
||||
log = "0.3"
|
||||
version = "2.0.1"
|
||||
semver = "0.2.1"
|
||||
toml = "0.1.25"
|
||||
|
@ -19,3 +19,6 @@ path = "../libimagrt"
|
|||
[dependencies.libimagutil]
|
||||
path = "../libimagutil"
|
||||
|
||||
[dependencies.libimagerror]
|
||||
path = "../libimagerror"
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use libimagrt::runtime::Runtime;
|
|||
use libimagstore::store::Entry;
|
||||
use libimagstore::store::EntryHeader;
|
||||
use libimagstore::storeid::build_entry_path;
|
||||
use libimagutil::trace::trace_error;
|
||||
use libimagerror::trace::trace_error;
|
||||
|
||||
use error::StoreError;
|
||||
use error::StoreErrorKind;
|
||||
|
@ -60,30 +60,24 @@ pub fn create(rt: &Runtime) {
|
|||
|
||||
fn create_from_cli_spec(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Result<()> {
|
||||
let content = matches.subcommand_matches("entry")
|
||||
.map(|entry_subcommand| {
|
||||
.map_or_else(|| {
|
||||
debug!("Didn't find entry subcommand, getting raw content");
|
||||
matches.value_of("from-raw")
|
||||
.map_or_else(String::new, string_from_raw_src)
|
||||
}, |entry_subcommand| {
|
||||
debug!("Found entry subcommand, parsing content");
|
||||
entry_subcommand
|
||||
.value_of("content")
|
||||
.map(String::from)
|
||||
.unwrap_or_else(|| {
|
||||
entry_subcommand
|
||||
.value_of("content-from")
|
||||
.map(|src| string_from_raw_src(src))
|
||||
.unwrap_or(String::new())
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
debug!("Didn't find entry subcommand, getting raw content");
|
||||
matches.value_of("from-raw")
|
||||
.map(|raw_src| string_from_raw_src(raw_src))
|
||||
.unwrap_or(String::new())
|
||||
.map_or_else(|| {
|
||||
entry_subcommand.value_of("content-from")
|
||||
.map_or_else(String::new, string_from_raw_src)
|
||||
}, String::from)
|
||||
});
|
||||
|
||||
debug!("Got content with len = {}", content.len());
|
||||
|
||||
let header = matches.subcommand_matches("entry")
|
||||
.map(|entry_matches| build_toml_header(entry_matches, EntryHeader::new()))
|
||||
.unwrap_or(EntryHeader::new());
|
||||
.map_or_else(EntryHeader::new,
|
||||
|entry_matches| build_toml_header(entry_matches, EntryHeader::new()));
|
||||
|
||||
create_with_content_and_header(rt, path, content, header)
|
||||
}
|
||||
|
@ -92,7 +86,7 @@ fn create_from_source(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Res
|
|||
let content = matches
|
||||
.value_of("from-raw")
|
||||
.ok_or(StoreError::new(StoreErrorKind::NoCommandlineCall, None))
|
||||
.map(|raw_src| string_from_raw_src(raw_src));
|
||||
.map(string_from_raw_src);
|
||||
|
||||
if content.is_err() {
|
||||
return content.map(|_| ());
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use libimagstore::storeid::build_entry_path;
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagutil::trace::trace_error;
|
||||
use libimagerror::trace::trace_error;
|
||||
|
||||
pub fn delete(rt: &Runtime) {
|
||||
use std::process::exit;
|
||||
|
|
|
@ -1,81 +1,10 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::Error as FmtError;
|
||||
use std::clone::Clone;
|
||||
use std::fmt::{Display, Formatter};
|
||||
generate_error_module!(
|
||||
generate_error_types!(StoreError, StoreErrorKind,
|
||||
BackendError => "Backend Error",
|
||||
NoCommandlineCall => "No commandline call"
|
||||
);
|
||||
);
|
||||
|
||||
/**
|
||||
* Kind of store error
|
||||
*/
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum StoreErrorKind {
|
||||
BackendError,
|
||||
NoCommandlineCall,
|
||||
// maybe more
|
||||
}
|
||||
|
||||
fn store_error_type_as_str(e: &StoreErrorKind) -> &'static str {
|
||||
match e {
|
||||
&StoreErrorKind::BackendError => "Backend Error",
|
||||
&StoreErrorKind::NoCommandlineCall => "No commandline call",
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StoreErrorKind {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
|
||||
try!(write!(fmt, "{}", store_error_type_as_str(self)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StoreError {
|
||||
err_type: StoreErrorKind,
|
||||
cause: Option<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)
|
||||
}
|
||||
|
||||
}
|
||||
pub use self::error::StoreError;
|
||||
pub use self::error::StoreErrorKind;
|
||||
|
||||
|
|
31
imag-store/src/get.rs
Normal file
31
imag-store/src/get.rs
Normal 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),
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
|
@ -22,39 +22,31 @@ extern crate toml;
|
|||
extern crate libimagrt;
|
||||
extern crate libimagstore;
|
||||
extern crate libimagutil;
|
||||
#[macro_use] extern crate libimagerror;
|
||||
|
||||
use libimagrt::runtime::Runtime;
|
||||
use std::process::exit;
|
||||
use libimagrt::setup::generate_runtime_setup;
|
||||
|
||||
mod error;
|
||||
mod ui;
|
||||
mod create;
|
||||
mod retrieve;
|
||||
mod update;
|
||||
mod delete;
|
||||
mod error;
|
||||
mod get;
|
||||
mod retrieve;
|
||||
mod ui;
|
||||
mod update;
|
||||
mod util;
|
||||
|
||||
use ui::build_ui;
|
||||
use create::create;
|
||||
use retrieve::retrieve;
|
||||
use update::update;
|
||||
use delete::delete;
|
||||
use get::get;
|
||||
use retrieve::retrieve;
|
||||
use ui::build_ui;
|
||||
use update::update;
|
||||
|
||||
fn main() {
|
||||
let name = "imag-store";
|
||||
let version = &version!()[..];
|
||||
let about = "Direct interface to the store. Use with great care!";
|
||||
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about));
|
||||
let rt = {
|
||||
let rt = Runtime::new(ui);
|
||||
if rt.is_ok() {
|
||||
rt.unwrap()
|
||||
} else {
|
||||
println!("Could not set up Runtime");
|
||||
println!("{:?}", rt.unwrap_err());
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
let rt = generate_runtime_setup("imag-store",
|
||||
&version!()[..],
|
||||
"Direct interface to the store. Use with great care!",
|
||||
build_ui);
|
||||
|
||||
rt.cli()
|
||||
.subcommand_name()
|
||||
|
@ -66,10 +58,11 @@ fn main() {
|
|||
|name| {
|
||||
debug!("Call: {}", name);
|
||||
match name {
|
||||
"create" => create(&rt),
|
||||
"retrieve" => retrieve(&rt),
|
||||
"update" => update(&rt),
|
||||
"delete" => delete(&rt),
|
||||
"create" => create(&rt),
|
||||
"delete" => delete(&rt),
|
||||
"get" => get(&rt),
|
||||
"retrieve" => retrieve(&rt),
|
||||
"update" => update(&rt),
|
||||
_ => {
|
||||
debug!("Unknown command");
|
||||
// More error handling
|
||||
|
|
|
@ -6,7 +6,7 @@ use toml::Value;
|
|||
use libimagstore::store::FileLockEntry;
|
||||
use libimagstore::storeid::build_entry_path;
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagutil::trace::trace_error;
|
||||
use libimagerror::trace::trace_error;
|
||||
|
||||
pub fn retrieve(rt: &Runtime) {
|
||||
rt.cli()
|
||||
|
@ -35,7 +35,7 @@ pub fn retrieve(rt: &Runtime) {
|
|||
});
|
||||
}
|
||||
|
||||
fn print_entry(rt: &Runtime, scmd: &ArgMatches, e: FileLockEntry) {
|
||||
pub fn print_entry(rt: &Runtime, scmd: &ArgMatches, e: FileLockEntry) {
|
||||
if do_print_raw(scmd) {
|
||||
debug!("Printing raw content...");
|
||||
println!("{}", e.to_str());
|
||||
|
|
|
@ -9,17 +9,20 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("p")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Create at this store path"))
|
||||
.help("Create at this store path")
|
||||
.value_name("PATH"))
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
.short("i")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Same as --path, for consistency"))
|
||||
.help("Same as --path, for consistency")
|
||||
.value_name("PATH"))
|
||||
.arg(Arg::with_name("from-raw")
|
||||
.long("from-raw")
|
||||
.takes_value(true)
|
||||
.help("Create a new entry by reading this file ('-' for stdin)"))
|
||||
.help("Create a new entry by reading this file ('-' for stdin)")
|
||||
.value_name("FILE"))
|
||||
|
||||
.group(ArgGroup::with_name("create-destination-group")
|
||||
.args(&["path", "id"])
|
||||
|
@ -32,12 +35,14 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.long("content")
|
||||
.short("c")
|
||||
.takes_value(true)
|
||||
.help("Content for the Entry from commandline"))
|
||||
.help("Content for the Entry from commandline")
|
||||
.value_name("CONTENT"))
|
||||
.arg(Arg::with_name("content-from")
|
||||
.long("content-from")
|
||||
.short("f")
|
||||
.takes_value(true)
|
||||
.help("Content for the Entry from this file ('-' for stdin)"))
|
||||
.help("Content for the Entry from this file ('-' for stdin)")
|
||||
.value_name("CONTENT"))
|
||||
|
||||
.group(ArgGroup::with_name("create-content-group")
|
||||
.args(&["content", "content-from"])
|
||||
|
@ -48,12 +53,13 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("h")
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.help("Set a header field. Specify as 'header.field.value=value', multiple allowed"))
|
||||
.help("Set a header field. Specify as 'header.field.value=value', multiple allowed")
|
||||
.value_name("header.field.value=value"))
|
||||
)
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("retrieve")
|
||||
.about("Get an entry from the store")
|
||||
.about("Retrieve an entry from the store (implicitely creates the entry)")
|
||||
.version("0.1")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
|
@ -95,6 +101,51 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
)
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("get")
|
||||
.about("Get an entry from the store (fails if non-existent)")
|
||||
.version("0.1")
|
||||
.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
.short("i")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Retrieve by Store Path, where root (/) is the store itself")
|
||||
.value_name("PATH"))
|
||||
.arg(Arg::with_name("content")
|
||||
.long("content")
|
||||
.short("c")
|
||||
.help("Print content"))
|
||||
.arg(Arg::with_name("header")
|
||||
.long("header")
|
||||
.short("h")
|
||||
.help("Print header"))
|
||||
.arg(Arg::with_name("header-json")
|
||||
.long("header-json")
|
||||
.short("j")
|
||||
.help("Print header as json"))
|
||||
.arg(Arg::with_name("raw")
|
||||
.long("raw")
|
||||
.short("r")
|
||||
.help("Print Entries as they are in the store"))
|
||||
|
||||
.subcommand(SubCommand::with_name("filter-header")
|
||||
.about("Retrieve Entries by filtering")
|
||||
.version("0.1")
|
||||
.arg(Arg::with_name("header-field-where")
|
||||
.long("where")
|
||||
.short("w")
|
||||
.takes_value(true)
|
||||
.help("Filter with 'header.field=foo' where the header field 'header.field' equals 'foo'")
|
||||
.value_name("header.field=foo")
|
||||
)
|
||||
.arg(Arg::with_name("header-field-grep")
|
||||
.long("grep")
|
||||
.short("g")
|
||||
.takes_value(true)
|
||||
.help("Filter with 'header.field=[a-zA-Z0-9]*' where the header field 'header.field' matches '[a-zA-Z0-9]*'"))
|
||||
)
|
||||
)
|
||||
|
||||
.subcommand(SubCommand::with_name("update")
|
||||
.about("Get an entry from the store")
|
||||
.version("0.1")
|
||||
|
@ -103,12 +154,14 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("i")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Update Store Entry with this path. Root (/) is the store itself"))
|
||||
.help("Update Store Entry with this path. Root (/) is the store itself")
|
||||
.value_name("PATH"))
|
||||
.arg(Arg::with_name("content")
|
||||
.long("content")
|
||||
.short("c")
|
||||
.takes_value(true)
|
||||
.help("Take the content for the new Entry from this file ('-' for stdin)"))
|
||||
.help("Take the content for the new Entry from this file ('-' for stdin)")
|
||||
.value_name("CONTENT"))
|
||||
.arg(Arg::with_name("header")
|
||||
.long("header")
|
||||
.short("h")
|
||||
|
@ -125,7 +178,7 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("i")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Remove Store Entry with this path. Root (/) is the store itself"))
|
||||
.help("Remove Store Entry with this path. Root (/) is the store itself")
|
||||
.value_name("PATH"))
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::process::exit;
|
|||
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagstore::storeid::build_entry_path;
|
||||
use libimagutil::trace::trace_error;
|
||||
use libimagerror::trace::trace_error;
|
||||
|
||||
use util::build_toml_header;
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::btree_map::{BTreeMap, Entry};
|
||||
use std::str::Split;
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
@ -21,10 +22,10 @@ pub fn build_toml_header(matches: &ArgMatches, header: EntryHeader) -> EntryHead
|
|||
for tpl in kvs {
|
||||
let (key, value) = tpl.into();
|
||||
debug!("Splitting: {:?}", key);
|
||||
let mut split = key.split(".");
|
||||
let mut split = key.split('.');
|
||||
let current = split.next();
|
||||
if current.is_some() {
|
||||
insert_key_into(String::from(current.unwrap()), &mut split, value, &mut main);
|
||||
insert_key_into(String::from(current.unwrap()), &mut split, Cow::Owned(value), &mut main);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,9 +37,9 @@ pub fn build_toml_header(matches: &ArgMatches, header: EntryHeader) -> EntryHead
|
|||
}
|
||||
}
|
||||
|
||||
fn insert_key_into(current: String,
|
||||
rest_path: &mut Split<&str>,
|
||||
value: String,
|
||||
fn insert_key_into<'a>(current: String,
|
||||
rest_path: &mut Split<char>,
|
||||
value: Cow<'a, str>,
|
||||
map: &mut BTreeMap<String, Value>) {
|
||||
let next = rest_path.next();
|
||||
|
||||
|
@ -47,27 +48,30 @@ fn insert_key_into(current: String,
|
|||
map.insert(current, parse_value(value));
|
||||
} else {
|
||||
debug!("Inserting into {:?} ... = {:?}", current, value);
|
||||
if map.contains_key(¤t) {
|
||||
match map.get_mut(¤t).unwrap() {
|
||||
&mut Value::Table(ref mut t) => {
|
||||
insert_key_into(String::from(next.unwrap()), rest_path, value, t);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
match map.entry(current) {
|
||||
Entry::Occupied(ref mut e) => {
|
||||
match *e.get_mut() {
|
||||
Value::Table(ref mut t) => {
|
||||
insert_key_into(String::from(next.unwrap()), rest_path, value, t);
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
},
|
||||
Entry::Vacant(v) => { v.insert(Value::Table( {
|
||||
let mut submap = BTreeMap::new();
|
||||
insert_key_into(String::from(next.unwrap()), rest_path, value, &mut submap);
|
||||
debug!("Inserting submap = {:?}", submap);
|
||||
submap }));
|
||||
}
|
||||
} else {
|
||||
let mut submap = BTreeMap::new();
|
||||
insert_key_into(String::from(next.unwrap()), rest_path, value, &mut submap);
|
||||
debug!("Inserting submap = {:?}", submap);
|
||||
map.insert(current, Value::Table(submap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_value(value: String) -> Value {
|
||||
fn parse_value(value: Cow<str>) -> Value {
|
||||
use std::str::FromStr;
|
||||
|
||||
fn is_ary(v: &String) -> bool {
|
||||
v.chars().next() == Some('[') && v.chars().last() == Some(']') && v.len() >= 3
|
||||
fn is_ary(v: &str) -> bool {
|
||||
v.starts_with('[') && v.ends_with(']') && v.len() >= 3
|
||||
}
|
||||
|
||||
if value == "true" {
|
||||
|
@ -79,7 +83,7 @@ fn parse_value(value: String) -> Value {
|
|||
} else if is_ary(&value) {
|
||||
debug!("Building Array out of: {:?}...", value);
|
||||
let sub = &value[1..(value.len()-1)];
|
||||
Value::Array(sub.split(",").map(|v| parse_value(String::from(v))).collect())
|
||||
Value::Array(sub.split(',').map(|x| parse_value(Cow::from(x))).collect())
|
||||
} else {
|
||||
FromStr::from_str(&value[..])
|
||||
.map(|i: i64| {
|
||||
|
@ -94,7 +98,7 @@ fn parse_value(value: String) -> Value {
|
|||
})
|
||||
.unwrap_or_else(|_| {
|
||||
debug!("Building String out of: {:?}...", value);
|
||||
Value::String(value)
|
||||
Value::String(value.into_owned())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
source $(dirname ${BASH_SOURCE[0]})/../tests/utils.sh
|
||||
source $(dirname ${BASH_SOURCE[0]})/../../tests/utils.sh
|
||||
|
||||
imag-store() {
|
||||
imag-call-binary "$(dirname ${BASH_SOURCE[0]})/../target/debug/" imag-store $*
|
||||
|
|
|
@ -5,7 +5,7 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
|||
|
||||
[dependencies]
|
||||
clap = "2.1.1"
|
||||
log = "0.3.5"
|
||||
log = "0.3"
|
||||
version = "2.0.1"
|
||||
semver = "0.2.1"
|
||||
toml = "0.1.25"
|
||||
|
@ -16,8 +16,8 @@ path = "../libimagstore"
|
|||
[dependencies.libimagrt]
|
||||
path = "../libimagrt"
|
||||
|
||||
[dependencies.libimagutil]
|
||||
path = "../libimagutil"
|
||||
[dependencies.libimagerror]
|
||||
path = "../libimagerror"
|
||||
|
||||
[dependencies.libimagentrytag]
|
||||
path = "../libimagentrytag"
|
||||
|
|
|
@ -7,46 +7,36 @@ extern crate toml;
|
|||
extern crate libimagstore;
|
||||
extern crate libimagrt;
|
||||
extern crate libimagentrytag;
|
||||
extern crate libimagutil;
|
||||
extern crate libimagerror;
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagrt::setup::generate_runtime_setup;
|
||||
use libimagentrytag::tagable::Tagable;
|
||||
use libimagentrytag::tag::Tag;
|
||||
use libimagstore::storeid::build_entry_path;
|
||||
use libimagerror::trace::trace_error;
|
||||
use libimagentrytag::ui::{get_add_tags, get_remove_tags};
|
||||
|
||||
mod ui;
|
||||
|
||||
use ui::build_ui;
|
||||
|
||||
use libimagutil::trace::trace_error;
|
||||
|
||||
fn main() {
|
||||
let name = "imag-store";
|
||||
let version = &version!()[..];
|
||||
let about = "Direct interface to the store. Use with great care!";
|
||||
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about));
|
||||
let rt = {
|
||||
let rt = Runtime::new(ui);
|
||||
if rt.is_ok() {
|
||||
rt.unwrap()
|
||||
} else {
|
||||
println!("Could not set up Runtime");
|
||||
println!("{:?}", rt.unwrap_err());
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
let rt = generate_runtime_setup("imag-store",
|
||||
&version!()[..],
|
||||
"Direct interface to the store. Use with great care!",
|
||||
build_ui);
|
||||
|
||||
let id = rt.cli().value_of("id").unwrap(); // enforced by clap
|
||||
rt.cli()
|
||||
.subcommand_name()
|
||||
.map_or_else(
|
||||
|| {
|
||||
let add = rt.cli().value_of("add");
|
||||
let rem = rt.cli().value_of("remove");
|
||||
let set = rt.cli().value_of("set");
|
||||
|
||||
alter(&rt, id, add, rem, set);
|
||||
let add = get_add_tags(rt.cli());
|
||||
let rem = get_remove_tags(rt.cli());
|
||||
alter(&rt, id, add, rem);
|
||||
},
|
||||
|name| {
|
||||
debug!("Call: {}", name);
|
||||
|
@ -60,7 +50,7 @@ fn main() {
|
|||
});
|
||||
}
|
||||
|
||||
fn alter(rt: &Runtime, id: &str, add: Option<&str>, rem: Option<&str>, set: Option<&str>) {
|
||||
fn alter(rt: &Runtime, id: &str, add: Option<Vec<Tag>>, rem: Option<Vec<Tag>>) {
|
||||
let path = {
|
||||
match build_entry_path(rt.store(), id) {
|
||||
Err(e) => {
|
||||
|
@ -72,43 +62,36 @@ fn alter(rt: &Runtime, id: &str, add: Option<&str>, rem: Option<&str>, set: Opti
|
|||
};
|
||||
debug!("path = {:?}", path);
|
||||
|
||||
rt.store()
|
||||
// "id" must be present, enforced via clap spec
|
||||
.retrieve(path)
|
||||
.map(|mut e| {
|
||||
match rt.store().get(path) {
|
||||
Ok(Some(mut e)) => {
|
||||
add.map(|tags| {
|
||||
let tags = tags.split(",");
|
||||
for tag in tags {
|
||||
info!("Adding tag '{}'", tag);
|
||||
if let Err(e) = e.add_tag(String::from(tag)) {
|
||||
debug!("Adding tag '{:?}'", tag);
|
||||
if let Err(e) = e.add_tag(tag) {
|
||||
trace_error(&e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}); // it is okay to ignore a None here
|
||||
|
||||
rem.map(|tags| {
|
||||
let tags = tags.split(",");
|
||||
for tag in tags {
|
||||
info!("Removing tag '{}'", tag);
|
||||
if let Err(e) = e.remove_tag(String::from(tag)) {
|
||||
debug!("Removing tag '{:?}'", tag);
|
||||
if let Err(e) = e.remove_tag(tag) {
|
||||
trace_error(&e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}); // it is okay to ignore a None here
|
||||
},
|
||||
|
||||
set.map(|tags| {
|
||||
info!("Setting tags '{}'", tags);
|
||||
let tags = tags.split(",").map(String::from).collect();
|
||||
if let Err(e) = e.set_tags(tags) {
|
||||
trace_error(&e);
|
||||
}
|
||||
});
|
||||
})
|
||||
.map_err(|e| {
|
||||
Ok(None) => {
|
||||
info!("No entry found.");
|
||||
},
|
||||
|
||||
Err(e) => {
|
||||
info!("No entry.");
|
||||
trace_error(&e);
|
||||
})
|
||||
.ok();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn list(id: &str, rt: &Runtime) {
|
||||
|
@ -123,14 +106,20 @@ fn list(id: &str, rt: &Runtime) {
|
|||
};
|
||||
debug!("path = {:?}", path);
|
||||
|
||||
let entry = rt.store().retrieve(path.clone());
|
||||
if entry.is_err() {
|
||||
debug!("Could not retrieve '{:?}' => {:?}", id, path);
|
||||
warn!("Could not retrieve entry '{}'", id);
|
||||
trace_error(&entry.unwrap_err());
|
||||
exit(1);
|
||||
}
|
||||
let entry = entry.unwrap();
|
||||
let entry = match rt.store().get(path.clone()) {
|
||||
Ok(Some(e)) => e,
|
||||
Ok(None) => {
|
||||
info!("No entry found.");
|
||||
exit(1);
|
||||
},
|
||||
|
||||
Err(e) => {
|
||||
debug!("Could not get '{:?}' => {:?}", id, path);
|
||||
warn!("Could not get entry '{}'", id);
|
||||
trace_error(&e);
|
||||
exit(1);
|
||||
},
|
||||
};
|
||||
|
||||
let scmd = rt.cli().subcommand_matches("list").unwrap(); // safe, we checked in main()
|
||||
|
||||
|
|
|
@ -1,36 +1,18 @@
|
|||
use clap::{Arg, App, ArgGroup, SubCommand};
|
||||
|
||||
use libimagentrytag::ui::{tag_add_arg, tag_remove_arg};
|
||||
|
||||
pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
||||
app.arg(Arg::with_name("id")
|
||||
.long("id")
|
||||
.short("i")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Use this entry"))
|
||||
.help("Use this entry")
|
||||
.value_name("ID"))
|
||||
|
||||
.arg(Arg::with_name("add")
|
||||
.long("add")
|
||||
.short("a")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.multiple(true)
|
||||
.help("Add this tag"))
|
||||
|
||||
.arg(Arg::with_name("remove")
|
||||
.long("remove")
|
||||
.short("r")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.multiple(true)
|
||||
.help("Remove this tag"))
|
||||
|
||||
.arg(Arg::with_name("set")
|
||||
.long("set")
|
||||
.short("s")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.multiple(true)
|
||||
.help("Set these tags"))
|
||||
.arg(tag_add_arg())
|
||||
.arg(tag_remove_arg())
|
||||
|
||||
.subcommand(SubCommand::with_name("list")
|
||||
.about("List tags (default)")
|
||||
|
@ -58,7 +40,8 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("s")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("Seperated by string"))
|
||||
.help("Separated by string")
|
||||
.value_name("SEP"))
|
||||
|
||||
.group(ArgGroup::with_name("list-group")
|
||||
.args(&[
|
||||
|
@ -71,5 +54,3 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
|||
[dependencies]
|
||||
clap = "2.1.1"
|
||||
glob = "0.2.11"
|
||||
log = "0.3.5"
|
||||
log = "0.3"
|
||||
rustbox = "0.8.1"
|
||||
semver = "0.2.1"
|
||||
toml = "0.1.25"
|
||||
|
@ -18,6 +18,6 @@ path = "../libimagstore"
|
|||
[dependencies.libimagrt]
|
||||
path = "../libimagrt"
|
||||
|
||||
[dependencies.libimagutil]
|
||||
path = "../libimagutil"
|
||||
[dependencies.libimagerror]
|
||||
path = "../libimagerror"
|
||||
|
||||
|
|
|
@ -1,87 +1,12 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::Error as FmtError;
|
||||
use std::clone::Clone;
|
||||
use std::fmt::{Display, Formatter};
|
||||
generate_error_module!(
|
||||
generate_error_types!(ViewError, ViewErrorKind,
|
||||
StoreError => "Store error",
|
||||
NoVersion => "No version specified",
|
||||
PatternError => "Error in Pattern",
|
||||
GlobBuildError => "Could not build glob() Argument"
|
||||
);
|
||||
);
|
||||
|
||||
/**
|
||||
* Kind of store error
|
||||
*/
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ViewErrorKind {
|
||||
StoreError,
|
||||
NoVersion,
|
||||
PatternError,
|
||||
GlobBuildError,
|
||||
}
|
||||
|
||||
fn view_error_type_as_str(e: &ViewErrorKind) -> &'static str {
|
||||
match e {
|
||||
&ViewErrorKind::StoreError => "Store error",
|
||||
&ViewErrorKind::NoVersion => "No version specified",
|
||||
&ViewErrorKind::PatternError => "Error in Pattern",
|
||||
&ViewErrorKind::GlobBuildError => "Could not build glob() Argument",
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ViewErrorKind {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
|
||||
try!(write!(fmt, "{}", view_error_type_as_str(self)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* View error type
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
pub struct ViewError {
|
||||
err_type: ViewErrorKind,
|
||||
cause: Option<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)
|
||||
}
|
||||
|
||||
}
|
||||
pub use self::error::ViewError;
|
||||
pub use self::error::ViewErrorKind;
|
||||
|
||||
|
|
|
@ -22,14 +22,15 @@ extern crate toml;
|
|||
|
||||
extern crate libimagrt;
|
||||
extern crate libimagstore;
|
||||
extern crate libimagutil;
|
||||
#[macro_use] extern crate libimagerror;
|
||||
|
||||
use std::result::Result as RResult;
|
||||
use std::process::exit;
|
||||
|
||||
use libimagrt::runtime::Runtime;
|
||||
use libimagrt::setup::generate_runtime_setup;
|
||||
use libimagstore::store::FileLockEntry;
|
||||
use libimagutil::trace::trace_error;
|
||||
use libimagerror::trace::trace_error;
|
||||
|
||||
mod error;
|
||||
mod ui;
|
||||
|
@ -44,20 +45,10 @@ use viewer::stdout::StdoutViewer;
|
|||
type Result<T> = RResult<T, ViewError>;
|
||||
|
||||
fn main() {
|
||||
let name = "imag-view";
|
||||
let version = &version!()[..];
|
||||
let about = "View entries (readonly)";
|
||||
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about));
|
||||
let rt = {
|
||||
let rt = Runtime::new(ui);
|
||||
if rt.is_ok() {
|
||||
rt.unwrap()
|
||||
} else {
|
||||
println!("Could not set up Runtime");
|
||||
println!("{:?}", rt.unwrap_err());
|
||||
exit(1); // we can afford not-executing destructors here
|
||||
}
|
||||
};
|
||||
let rt = generate_runtime_setup( "imag-view",
|
||||
&version!()[..],
|
||||
"View entries (readonly)",
|
||||
build_ui);
|
||||
|
||||
let entry_id = rt.cli().value_of("id").unwrap(); // enforced by clap
|
||||
|
||||
|
@ -129,7 +120,7 @@ fn load_entry<'a>(id: &str,
|
|||
|
||||
let version = {
|
||||
if version.is_none() {
|
||||
let r = id.split("~").last();
|
||||
let r = id.split('~').last();
|
||||
if r.is_none() {
|
||||
warn!("No version");
|
||||
return Err(ViewError::new(ViewErrorKind::NoVersion, None));
|
||||
|
@ -144,7 +135,7 @@ fn load_entry<'a>(id: &str,
|
|||
debug!("Building path from {:?} and {:?}", id, version);
|
||||
let mut path = rt.store().path().clone();
|
||||
|
||||
if id.chars().next() == Some('/') {
|
||||
if id.starts_with('/') {
|
||||
path.push(format!("{}~{}", &id[1..id.len()], version));
|
||||
} else {
|
||||
path.push(format!("{}~{}", id, version));
|
||||
|
@ -161,7 +152,7 @@ fn view_versions_of(id: &str, rt: &Runtime) -> Result<()> {
|
|||
|
||||
let mut path = rt.store().path().clone();
|
||||
|
||||
if id.chars().next() == Some('/') {
|
||||
if id.starts_with('/') {
|
||||
path.push(format!("{}~*", &id[1..id.len()]));
|
||||
} else {
|
||||
path.push(format!("{}~*", id));
|
||||
|
|
|
@ -7,14 +7,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("i")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("View this entry at this store path"))
|
||||
.help("View this entry at this store path")
|
||||
.value_name("ID"))
|
||||
|
||||
.arg(Arg::with_name("version")
|
||||
.long("version")
|
||||
.short("V")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.help("View this version (youngest if not specified)"))
|
||||
.help("View this version (youngest if not specified)")
|
||||
.value_name("VERSION"))
|
||||
|
||||
.arg(Arg::with_name("versions")
|
||||
.long("versions")
|
||||
|
@ -71,21 +73,24 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("b")
|
||||
.takes_value(true) // optional, which browser
|
||||
.required(false)
|
||||
.help("View content in $BROWSER (fails if no env variable $BROWSER)"))
|
||||
.help("View content in $BROWSER (fails if no env variable $BROWSER)")
|
||||
.value_name("BROWSER"))
|
||||
|
||||
.arg(Arg::with_name("view-in-texteditor")
|
||||
.long("editor")
|
||||
.short("e")
|
||||
.takes_value(true) // optional, which editor
|
||||
.required(false)
|
||||
.help("View content in $EDITOR"))
|
||||
.help("View content in $EDITOR")
|
||||
.value_name("EDITOR"))
|
||||
|
||||
.arg(Arg::with_name("view-in-custom")
|
||||
.long("custom")
|
||||
.short("c")
|
||||
.takes_value(true) // non-optional, call-string
|
||||
.required(false)
|
||||
.help("View content in custom program, for example 'libreoffice %e', replace '%e' with entry path"))
|
||||
.help("View content in custom program, for example 'libreoffice %e', replace '%e' with entry path")
|
||||
.value_name("PROGRAM"))
|
||||
|
||||
.group(ArgGroup::with_name("viewer")
|
||||
.args(&["view-in-stdout",
|
||||
|
@ -105,15 +110,15 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
|||
.short("f")
|
||||
.takes_value(true) // "markdown" or "textile" or "restructuredtex"
|
||||
.required(true)
|
||||
.help("Compile from"))
|
||||
.help("Compile from")
|
||||
.value_name("FORMAT"))
|
||||
|
||||
.arg(Arg::with_name("to")
|
||||
.long("to")
|
||||
.short("t")
|
||||
.takes_value(true) // "html" or "HTML" or ... json maybe?
|
||||
.required(true)
|
||||
.help("Compile to"))
|
||||
.help("Compile to")
|
||||
.value_name("FORMAT"))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
|
31
imagrc.toml
Normal file
31
imagrc.toml
Normal 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"
|
|
@ -4,10 +4,13 @@ version = "0.1.0"
|
|||
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.3.5"
|
||||
log = "0.3"
|
||||
toml = "0.1.25"
|
||||
semver = "0.2"
|
||||
|
||||
[dependencies.libimagstore]
|
||||
path = "../libimagstore"
|
||||
|
||||
[dependencies.libimagerror]
|
||||
path = "../libimagerror"
|
||||
|
||||
|
|
|
@ -144,12 +144,12 @@ impl<'a> Counter<'a> {
|
|||
}
|
||||
|
||||
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> {
|
||||
|
||||
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);
|
||||
match store.retrieve(id) {
|
||||
Err(e) => Err(CE::new(CEK::StoreReadError, Some(Box::new(e)))),
|
||||
|
|
|
@ -1,87 +1,12 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::Error as FmtError;
|
||||
use std::clone::Clone;
|
||||
use std::fmt::{Display, Formatter};
|
||||
generate_error_module!(
|
||||
generate_error_types!(CounterError, CounterErrorKind,
|
||||
StoreReadError => "Store read error",
|
||||
StoreWriteError => "Store write error",
|
||||
HeaderTypeError => "Header type error",
|
||||
HeaderFieldMissingError => "Header field missing error"
|
||||
);
|
||||
);
|
||||
|
||||
/**
|
||||
* Kind of error
|
||||
*/
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum CounterErrorKind {
|
||||
StoreReadError,
|
||||
StoreWriteError,
|
||||
HeaderTypeError,
|
||||
HeaderFieldMissingError,
|
||||
}
|
||||
|
||||
fn counter_error_type_as_str(e: &CounterErrorKind) -> &'static str {
|
||||
match e {
|
||||
&CounterErrorKind::StoreReadError => "Store read error",
|
||||
&CounterErrorKind::StoreWriteError => "Store write error",
|
||||
&CounterErrorKind::HeaderTypeError => "Header type error",
|
||||
&CounterErrorKind::HeaderFieldMissingError => "Header field missing error",
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CounterErrorKind {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
|
||||
try!(write!(fmt, "{}", counter_error_type_as_str(self)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Store error type
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
pub struct CounterError {
|
||||
err_type: CounterErrorKind,
|
||||
cause: Option<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)
|
||||
}
|
||||
|
||||
}
|
||||
pub use self::error::CounterError;
|
||||
pub use self::error::CounterErrorKind;
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ extern crate toml;
|
|||
#[macro_use] extern crate semver;
|
||||
|
||||
#[macro_use] extern crate libimagstore;
|
||||
#[macro_use] extern crate libimagerror;
|
||||
|
||||
module_entry_path_mod!("counter", "0.1.0");
|
||||
|
||||
|
|
26
libimagdiary/Cargo.toml
Normal file
26
libimagdiary/Cargo.toml
Normal 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"
|
||||
|
19
libimagdiary/src/config.rs
Normal file
19
libimagdiary/src/config.rs
Normal 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
109
libimagdiary/src/diary.rs
Normal 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
217
libimagdiary/src/diaryid.rs
Normal 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
71
libimagdiary/src/entry.rs
Normal 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
16
libimagdiary/src/error.rs
Normal 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;
|
||||
|
26
libimagdiary/src/is_in_diary.rs
Normal file
26
libimagdiary/src/is_in_diary.rs
Normal 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
111
libimagdiary/src/iter.rs
Normal 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
39
libimagdiary/src/lib.rs
Normal 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;
|
5
libimagdiary/src/result.rs
Normal file
5
libimagdiary/src/result.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use std::result::Result as RResult;
|
||||
|
||||
use error::DiaryError;
|
||||
|
||||
pub type Result<T> = RResult<T, DiaryError>;
|
|
@ -6,7 +6,7 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
|||
[dependencies]
|
||||
clap = "2.1.1"
|
||||
itertools = "0.4"
|
||||
log = "0.3.4"
|
||||
log = "0.3"
|
||||
regex = "0.1"
|
||||
toml = "0.1.27"
|
||||
semver = "0.2.1"
|
||||
|
|
|
@ -14,15 +14,15 @@ struct EqGt {
|
|||
impl Predicate for EqGt {
|
||||
|
||||
fn evaluate(&self, v: Value) -> bool {
|
||||
match &self.comp {
|
||||
&Value::Integer(i) => {
|
||||
match self.comp {
|
||||
Value::Integer(i) => {
|
||||
match v {
|
||||
Value::Integer(j) => i > j,
|
||||
Value::Float(f) => (i as f64) > f,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
&Value::Float(f) => {
|
||||
Value::Float(f) => {
|
||||
match v {
|
||||
Value::Integer(i) => f > (i as f64),
|
||||
Value::Float(d) => f > d,
|
||||
|
|
|
@ -27,11 +27,11 @@ impl Filter for FieldIsEmpty {
|
|||
.map(|v| {
|
||||
match v {
|
||||
Some(Value::Array(a)) => a.is_empty(),
|
||||
Some(Value::Boolean(_)) => false,
|
||||
Some(Value::Float(_)) => false,
|
||||
Some(Value::Integer(_)) => false,
|
||||
Some(Value::String(s)) => s.is_empty(),
|
||||
Some(Value::Table(t)) => t.is_empty(),
|
||||
Some(Value::Boolean(_)) |
|
||||
Some(Value::Float(_)) |
|
||||
Some(Value::Integer(_)) => false,
|
||||
_ => true,
|
||||
}
|
||||
})
|
||||
|
|
|
@ -21,11 +21,11 @@ impl Type {
|
|||
|
||||
fn matches(&self, v: &Value) -> bool {
|
||||
match (self, v) {
|
||||
(&Type::String, &Value::String(_)) => true,
|
||||
(&Type::Integer, &Value::Integer(_)) => true,
|
||||
(&Type::Float, &Value::Float(_)) => true,
|
||||
(&Type::Boolean, &Value::Boolean(_)) => true,
|
||||
(&Type::Array, &Value::Array(_)) => true,
|
||||
(&Type::String, &Value::String(_)) |
|
||||
(&Type::Integer, &Value::Integer(_)) |
|
||||
(&Type::Float, &Value::Float(_)) |
|
||||
(&Type::Boolean, &Value::Boolean(_)) |
|
||||
(&Type::Array, &Value::Array(_)) |
|
||||
(&Type::Table, &Value::Table(_)) => true,
|
||||
_ => false,
|
||||
}
|
||||
|
|
|
@ -14,15 +14,15 @@ struct EqLt {
|
|||
impl Predicate for EqLt {
|
||||
|
||||
fn evaluate(&self, v: Value) -> bool {
|
||||
match &self.comp {
|
||||
&Value::Integer(i) => {
|
||||
match self.comp {
|
||||
Value::Integer(i) => {
|
||||
match v {
|
||||
Value::Integer(j) => i < j,
|
||||
Value::Float(f) => (i as f64) < f,
|
||||
_ => false,
|
||||
}
|
||||
},
|
||||
&Value::Float(f) => {
|
||||
Value::Float(f) => {
|
||||
match v {
|
||||
Value::Integer(i) => f < (i as f64),
|
||||
Value::Float(d) => f < d,
|
||||
|
|
|
@ -23,7 +23,7 @@ impl Filter for VersionEq {
|
|||
e.get_header()
|
||||
.read("imag.version")
|
||||
.map(|val| {
|
||||
val.map(|v| {
|
||||
val.map_or(false, |v| {
|
||||
match v {
|
||||
Value::String(s) => {
|
||||
match Version::parse(&s[..]) {
|
||||
|
@ -34,7 +34,6 @@ impl Filter for VersionEq {
|
|||
_ => false,
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ impl Filter for VersionGt {
|
|||
e.get_header()
|
||||
.read("imag.version")
|
||||
.map(|val| {
|
||||
val.map(|v| {
|
||||
val.map_or(false, |v| {
|
||||
match v {
|
||||
Value::String(s) => {
|
||||
match Version::parse(&s[..]) {
|
||||
|
@ -34,7 +34,6 @@ impl Filter for VersionGt {
|
|||
_ => false,
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ impl Filter for VersionLt {
|
|||
e.get_header()
|
||||
.read("imag.version")
|
||||
.map(|val| {
|
||||
val.map(|v| {
|
||||
val.map_or(false, |v| {
|
||||
match v {
|
||||
Value::String(s) => {
|
||||
match Version::parse(&s[..]) {
|
||||
|
@ -34,7 +34,6 @@ impl Filter for VersionLt {
|
|||
_ => false,
|
||||
}
|
||||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
|
|
@ -5,12 +5,18 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
|||
|
||||
[dependencies]
|
||||
itertools = "0.4"
|
||||
log = "0.3.4"
|
||||
log = "0.3"
|
||||
toml = "0.1.27"
|
||||
semver = "0.2"
|
||||
url = "0.5.5"
|
||||
url = "1.1"
|
||||
rust-crypto = "0.2.35"
|
||||
|
||||
[dependencies.libimagstore]
|
||||
path = "../libimagstore"
|
||||
|
||||
[dependencies.libimagerror]
|
||||
path = "../libimagerror"
|
||||
|
||||
[dependencies.libimagutil]
|
||||
path = "../libimagutil"
|
||||
|
||||
|
|
|
@ -1,90 +1,16 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::Error as FmtError;
|
||||
use std::fmt::{Display, Formatter};
|
||||
generate_error_module!(
|
||||
generate_error_types!(LinkError, LinkErrorKind,
|
||||
EntryHeaderReadError => "Error while reading an entry header",
|
||||
EntryHeaderWriteError => "Error while writing an entry header",
|
||||
ExistingLinkTypeWrong => "Existing link entry has wrong type",
|
||||
LinkTargetDoesNotExist => "Link target does not exist in the store",
|
||||
InternalConversionError => "Error while converting values internally",
|
||||
InvalidUri => "URI is not valid",
|
||||
StoreReadError => "Store read error",
|
||||
StoreWriteError => "Store write error"
|
||||
);
|
||||
);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum LinkErrorKind {
|
||||
EntryHeaderReadError,
|
||||
EntryHeaderWriteError,
|
||||
ExistingLinkTypeWrong,
|
||||
LinkTargetDoesNotExist,
|
||||
InternalConversionError,
|
||||
InvalidUri,
|
||||
StoreReadError,
|
||||
StoreWriteError,
|
||||
}
|
||||
pub use self::error::LinkError;
|
||||
pub use self::error::LinkErrorKind;
|
||||
|
||||
fn link_error_type_as_str(e: &LinkErrorKind) -> &'static str {
|
||||
match e {
|
||||
&LinkErrorKind::EntryHeaderReadError
|
||||
=> "Error while reading an entry header",
|
||||
|
||||
&LinkErrorKind::EntryHeaderWriteError
|
||||
=> "Error while writing an entry header",
|
||||
|
||||
&LinkErrorKind::ExistingLinkTypeWrong
|
||||
=> "Existing link entry has wrong type",
|
||||
|
||||
&LinkErrorKind::LinkTargetDoesNotExist
|
||||
=> "Link target does not exist in the store",
|
||||
|
||||
&LinkErrorKind::InternalConversionError
|
||||
=> "Error while converting values internally",
|
||||
|
||||
&LinkErrorKind::InvalidUri
|
||||
=> "URI is not valid",
|
||||
|
||||
&LinkErrorKind::StoreReadError
|
||||
=> "Store read error",
|
||||
|
||||
&LinkErrorKind::StoreWriteError
|
||||
=> "Store write error",
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LinkErrorKind {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
|
||||
try!(write!(fmt, "{}", link_error_type_as_str(self)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LinkError {
|
||||
kind: LinkErrorKind,
|
||||
cause: Option<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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,8 +31,8 @@ use url::Url;
|
|||
use crypto::sha1::Sha1;
|
||||
use crypto::digest::Digest;
|
||||
|
||||
/// "Link" Type, just an abstraction over FileLockEntry to have some convenience internally.
|
||||
struct Link<'a> {
|
||||
/// "Link" Type, just an abstraction over `FileLockEntry` to have some convenience internally.
|
||||
pub struct Link<'a> {
|
||||
link: FileLockEntry<'a>
|
||||
}
|
||||
|
||||
|
@ -45,28 +45,18 @@ impl<'a> Link<'a> {
|
|||
/// For interal use only. Load an Link from a store id, if this is actually a Link
|
||||
fn retrieve(store: &'a Store, id: StoreId) -> Result<Option<Link<'a>>> {
|
||||
store.retrieve(id)
|
||||
.map(|fle| {
|
||||
if let Some(_) = Link::get_link_uri_from_filelockentry(&fle) {
|
||||
Some(Link {
|
||||
link: fle
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|fle| Link::get_link_uri_from_filelockentry(&fle).map(|_| Link { link: fle }))
|
||||
.map_err(|e| LE::new(LEK::StoreReadError, Some(Box::new(e))))
|
||||
}
|
||||
|
||||
/// Get a link Url object from a FileLockEntry, ignore errors.
|
||||
/// Get a link Url object from a `FileLockEntry`, ignore errors.
|
||||
fn get_link_uri_from_filelockentry(file: &FileLockEntry<'a>) -> Option<Url> {
|
||||
file.get_header()
|
||||
.read("imag.content.uri")
|
||||
.ok()
|
||||
.and_then(|opt| {
|
||||
match opt {
|
||||
Some(Value::String(s)) => Url::parse(&s[..]).ok(),
|
||||
_ => None
|
||||
}
|
||||
.and_then(|opt| match opt {
|
||||
Some(Value::String(s)) => Url::parse(&s[..]).ok(),
|
||||
_ => None
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -78,7 +68,7 @@ impl<'a> Link<'a> {
|
|||
match opt {
|
||||
Ok(Some(Value::String(s))) => {
|
||||
Url::parse(&s[..])
|
||||
.map(|s| Some(s))
|
||||
.map(Some)
|
||||
.map_err(|e| LE::new(LEK::EntryHeaderReadError, Some(Box::new(e))))
|
||||
},
|
||||
Ok(None) => Ok(None),
|
||||
|
@ -105,7 +95,7 @@ pub trait ExternalLinker : InternalLinker {
|
|||
}
|
||||
|
||||
/// Check whether the StoreId starts with `/link/external/`
|
||||
fn is_link_store_id(id: &StoreId) -> bool {
|
||||
pub fn is_external_link_storeid(id: &StoreId) -> bool {
|
||||
debug!("Checking whether this is a /link/external/*: '{:?}'", id);
|
||||
id.starts_with("/link/external/")
|
||||
}
|
||||
|
@ -115,7 +105,7 @@ fn get_external_link_from_file(entry: &FileLockEntry) -> Result<Url> {
|
|||
.ok_or(LE::new(LEK::StoreReadError, None))
|
||||
}
|
||||
|
||||
/// Implement ExternalLinker for Entry, hiding the fact that there is no such thing as an external
|
||||
/// Implement `ExternalLinker` for `Entry`, hiding the fact that there is no such thing as an external
|
||||
/// link in an entry, but internal links to other entries which serve as external links, as one
|
||||
/// entry in the store can only have one external link.
|
||||
impl ExternalLinker for Entry {
|
||||
|
@ -129,7 +119,7 @@ impl ExternalLinker for Entry {
|
|||
.map(|vect| {
|
||||
debug!("Getting external links");
|
||||
vect.into_iter()
|
||||
.filter(is_link_store_id)
|
||||
.filter(is_external_link_storeid)
|
||||
.map(|id| {
|
||||
debug!("Retrieving entry for id: '{:?}'", id);
|
||||
match store.retrieve(id.clone()) {
|
||||
|
@ -156,7 +146,7 @@ impl ExternalLinker for Entry {
|
|||
for link in links { // for all links
|
||||
let hash = {
|
||||
let mut s = Sha1::new();
|
||||
s.input_str(&link.serialize()[..]);
|
||||
s.input_str(&link.as_str()[..]);
|
||||
s.result_str()
|
||||
};
|
||||
let file_id = ModuleEntryPath::new(format!("external/{}", hash)).into_storeid();
|
||||
|
@ -189,7 +179,7 @@ impl ExternalLinker for Entry {
|
|||
Err(e) => return Err(LE::new(LEK::StoreWriteError, Some(Box::new(e)))),
|
||||
};
|
||||
|
||||
let v = Value::String(link.serialize());
|
||||
let v = Value::String(link.into_string());
|
||||
|
||||
debug!("setting URL = '{:?}", v);
|
||||
table.insert(String::from("url"), v);
|
||||
|
@ -231,7 +221,7 @@ impl ExternalLinker for Entry {
|
|||
.and_then(|links| {
|
||||
debug!("Removing link = '{:?}' from links = {:?}", link, links);
|
||||
let links = links.into_iter()
|
||||
.filter(|l| l.serialize() != link.serialize())
|
||||
.filter(|l| l.as_str() != link.as_str())
|
||||
.collect();
|
||||
self.set_external_links(store, links)
|
||||
})
|
||||
|
|
|
@ -4,8 +4,9 @@ use libimagstore::storeid::StoreId;
|
|||
use libimagstore::store::Entry;
|
||||
use libimagstore::store::EntryHeader;
|
||||
use libimagstore::store::Result as StoreResult;
|
||||
use libimagerror::into::IntoError;
|
||||
|
||||
use error::{LinkError, LinkErrorKind};
|
||||
use error::LinkErrorKind as LEK;
|
||||
use result::Result;
|
||||
|
||||
use toml::Value;
|
||||
|
@ -50,7 +51,7 @@ impl InternalLinker for Entry {
|
|||
|
||||
let new_links = links_into_values(new_links);
|
||||
if new_links.iter().any(|o| o.is_none()) {
|
||||
return Err(LinkError::new(LinkErrorKind::InternalConversionError, None));
|
||||
return Err(LEK::InternalConversionError.into());
|
||||
}
|
||||
let new_links = new_links.into_iter().map(|o| o.unwrap()).collect();
|
||||
process_rw_result(self.get_header_mut().set("imag.links", Value::Array(new_links)))
|
||||
|
@ -92,9 +93,9 @@ impl InternalLinker for Entry {
|
|||
fn links_into_values(links: Vec<StoreId>) -> Vec<Option<Value>> {
|
||||
links
|
||||
.into_iter()
|
||||
.map(|s| s.to_str().map(|s| String::from(s)))
|
||||
.map(|s| s.to_str().map(String::from))
|
||||
.unique()
|
||||
.map(|elem| elem.map(|s| Value::String(s)))
|
||||
.map(|elem| elem.map(Value::String))
|
||||
.sorted_by(|a, b| {
|
||||
match (a, b) {
|
||||
(&Some(Value::String(ref a)), &Some(Value::String(ref b))) => Ord::cmp(a, b),
|
||||
|
@ -109,7 +110,7 @@ fn rewrite_links(header: &mut EntryHeader, links: Vec<StoreId>) -> Result<()> {
|
|||
|
||||
if links.iter().any(|o| o.is_none()) {
|
||||
// if any type convert failed we fail as well
|
||||
Err(LinkError::new(LinkErrorKind::InternalConversionError, None))
|
||||
Err(LEK::InternalConversionError.into())
|
||||
} else {
|
||||
// I know it is ugly
|
||||
let links = links.into_iter().map(|opt| opt.unwrap()).collect();
|
||||
|
@ -126,7 +127,7 @@ fn add_foreign_link(target: &mut Entry, from: StoreId) -> Result<()> {
|
|||
links.push(from);
|
||||
let links = links_into_values(links);
|
||||
if links.iter().any(|o| o.is_none()) {
|
||||
Err(LinkError::new(LinkErrorKind::InternalConversionError, None))
|
||||
Err(LEK::InternalConversionError.into())
|
||||
} else {
|
||||
let links = links.into_iter().map(|opt| opt.unwrap()).collect();
|
||||
process_rw_result(target.get_header_mut().set("imag.links", Value::Array(links)))
|
||||
|
@ -136,34 +137,26 @@ fn add_foreign_link(target: &mut Entry, from: StoreId) -> Result<()> {
|
|||
}
|
||||
|
||||
fn process_rw_result(links: StoreResult<Option<Value>>) -> Result<Vec<Link>> {
|
||||
if links.is_err() {
|
||||
debug!("RW action on store failed. Generating LinkError");
|
||||
let lerr = LinkError::new(LinkErrorKind::EntryHeaderReadError,
|
||||
Some(Box::new(links.unwrap_err())));
|
||||
return Err(lerr);
|
||||
}
|
||||
let links = links.unwrap();
|
||||
|
||||
if links.is_none() {
|
||||
debug!("We got no value from the header!");
|
||||
return Ok(vec![])
|
||||
}
|
||||
let links = links.unwrap();
|
||||
|
||||
let links = {
|
||||
match links {
|
||||
Value::Array(a) => a,
|
||||
_ => {
|
||||
debug!("We expected an Array for the links, but there was a non-Array!");
|
||||
return Err(LinkError::new(LinkErrorKind::ExistingLinkTypeWrong, None));
|
||||
},
|
||||
let links = match links {
|
||||
Err(e) => {
|
||||
debug!("RW action on store failed. Generating LinkError");
|
||||
return Err(LEK::EntryHeaderReadError.into_error_with_cause(Box::new(e)))
|
||||
},
|
||||
Ok(None) => {
|
||||
debug!("We got no value from the header!");
|
||||
return Ok(vec![])
|
||||
},
|
||||
Ok(Some(Value::Array(l))) => l,
|
||||
Ok(Some(_)) => {
|
||||
debug!("We expected an Array for the links, but there was a non-Array!");
|
||||
return Err(LEK::ExistingLinkTypeWrong.into());
|
||||
}
|
||||
};
|
||||
|
||||
if !links.iter().all(|l| match l { &Value::String(_) => true, _ => false }) {
|
||||
if !links.iter().all(|l| is_match!(*l, Value::String(_))) {
|
||||
debug!("At least one of the Values which were expected in the Array of links is a non-String!");
|
||||
debug!("Generating LinkError");
|
||||
return Err(LinkError::new(LinkErrorKind::ExistingLinkTypeWrong, None));
|
||||
return Err(LEK::ExistingLinkTypeWrong.into());
|
||||
}
|
||||
|
||||
let links : Vec<Link> = links.into_iter()
|
||||
|
|
|
@ -20,6 +20,8 @@ extern crate url;
|
|||
extern crate crypto;
|
||||
|
||||
#[macro_use] extern crate libimagstore;
|
||||
#[macro_use] extern crate libimagerror;
|
||||
#[macro_use] extern crate libimagutil;
|
||||
|
||||
module_entry_path_mod!("links", "0.1.0");
|
||||
|
||||
|
|
|
@ -5,9 +5,12 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
|||
|
||||
[dependencies]
|
||||
clap = "2.1.1"
|
||||
log = "0.3.5"
|
||||
log = "0.3"
|
||||
toml = "0.1.25"
|
||||
|
||||
[dependencies.libimagstore]
|
||||
path = "../libimagstore"
|
||||
|
||||
[dependencies.libimagerror]
|
||||
path = "../libimagerror"
|
||||
|
||||
|
|
|
@ -1,85 +1,12 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::Error as FmtError;
|
||||
use std::clone::Clone;
|
||||
use std::fmt::{Display, Formatter};
|
||||
generate_error_module!(
|
||||
generate_error_types!(ListError, ListErrorKind,
|
||||
FormatError => "FormatError",
|
||||
EntryError => "EntryError",
|
||||
IterationError => "IterationError",
|
||||
CLIError => "No CLI subcommand for listing entries"
|
||||
);
|
||||
);
|
||||
|
||||
/**
|
||||
* Kind of error
|
||||
*/
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ListErrorKind {
|
||||
FormatError,
|
||||
EntryError,
|
||||
IterationError,
|
||||
CLIError,
|
||||
}
|
||||
|
||||
fn counter_error_type_as_str(err: &ListErrorKind) -> &'static str{
|
||||
match err {
|
||||
&ListErrorKind::FormatError => "FormatError",
|
||||
&ListErrorKind::EntryError => "EntryError",
|
||||
&ListErrorKind::IterationError => "IterationError",
|
||||
&ListErrorKind::CLIError => "No CLI subcommand for listing entries",
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ListErrorKind {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
|
||||
try!(write!(fmt, "{}", counter_error_type_as_str(self)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Store error type
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
pub struct ListError {
|
||||
err_type: ListErrorKind,
|
||||
cause: Option<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)
|
||||
}
|
||||
|
||||
}
|
||||
pub use self::error::ListError;
|
||||
pub use self::error::ListErrorKind;
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ extern crate clap;
|
|||
extern crate toml;
|
||||
|
||||
extern crate libimagstore;
|
||||
#[macro_use] extern crate libimagerror;
|
||||
|
||||
pub mod cli;
|
||||
pub mod error;
|
||||
|
|
|
@ -27,12 +27,19 @@ impl<'a> Lister for CoreLister<'a> {
|
|||
use error::ListError as LE;
|
||||
use error::ListErrorKind as LEK;
|
||||
|
||||
entries.fold(Ok(()), |accu, entry| {
|
||||
accu.and_then(|_| {
|
||||
write!(stdout(), "{:?}\n", (self.lister)(&entry))
|
||||
.map_err(|e| LE::new(LEK::FormatError, Some(Box::new(e))))
|
||||
})
|
||||
})
|
||||
debug!("Called list()");
|
||||
let (r, n) = entries
|
||||
.fold((Ok(()), 0), |(accu, i), entry| {
|
||||
debug!("fold({:?}, {:?})", accu, entry);
|
||||
let r = accu.and_then(|_| {
|
||||
debug!("Listing Entry: {:?}", entry);
|
||||
write!(stdout(), "{:?}\n", (self.lister)(&entry))
|
||||
.map_err(|e| LE::new(LEK::FormatError, Some(Box::new(e))))
|
||||
});
|
||||
(r, i + 1)
|
||||
});
|
||||
debug!("Iterated over {} entries", n);
|
||||
r
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ impl Lister for PathLister {
|
|||
if self.absolute {
|
||||
pb.canonicalize().map_err(|e| LE::new(LEK::FormatError, Some(Box::new(e))))
|
||||
} else {
|
||||
Ok(pb)
|
||||
Ok(pb.into())
|
||||
}
|
||||
})
|
||||
.and_then(|pb| {
|
||||
|
|
17
libimagentrymarkdown/Cargo.toml
Normal file
17
libimagentrymarkdown/Cargo.toml
Normal 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"
|
||||
|
9
libimagentrymarkdown/README.md
Normal file
9
libimagentrymarkdown/README.md
Normal 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.
|
||||
|
||||
|
11
libimagentrymarkdown/src/error.rs
Normal file
11
libimagentrymarkdown/src/error.rs
Normal 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;
|
||||
|
||||
|
82
libimagentrymarkdown/src/html.rs
Normal file
82
libimagentrymarkdown/src/html.rs
Normal 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)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
12
libimagentrymarkdown/src/lib.rs
Normal file
12
libimagentrymarkdown/src/lib.rs
Normal 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;
|
||||
|
143
libimagentrymarkdown/src/link.rs
Normal file
143
libimagentrymarkdown/src/link.rs
Normal 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
6
libimagentrymarkdown/src/result.rs
Normal file
6
libimagentrymarkdown/src/result.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
use std::result::Result as RResult;
|
||||
|
||||
use error::MarkdownError;
|
||||
|
||||
pub type Result<T> = RResult<T, MarkdownError>;
|
||||
|
16
libimagentryselect/Cargo.toml
Normal file
16
libimagentryselect/Cargo.toml
Normal 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"
|
||||
|
9
libimagentryselect/src/lib.rs
Normal file
9
libimagentryselect/src/lib.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
extern crate clap;
|
||||
extern crate log;
|
||||
extern crate interactor;
|
||||
|
||||
extern crate libimagstore;
|
||||
extern crate libimagerror;
|
||||
|
||||
pub mod ui;
|
||||
|
53
libimagentryselect/src/ui.rs
Normal file
53
libimagentryselect/src/ui.rs
Normal 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)]),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
|||
|
||||
[dependencies]
|
||||
clap = "2.1.1"
|
||||
log = "0.3.5"
|
||||
log = "0.3"
|
||||
regex = "0.1"
|
||||
toml = "0.1.25"
|
||||
itertools = "0.4"
|
||||
|
@ -13,3 +13,9 @@ itertools = "0.4"
|
|||
[dependencies.libimagstore]
|
||||
path = "../libimagstore"
|
||||
|
||||
[dependencies.libimagerror]
|
||||
path = "../libimagerror"
|
||||
|
||||
[dependencies.libimagutil]
|
||||
path = "../libimagutil"
|
||||
|
||||
|
|
|
@ -1,69 +1,12 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::Error as FmtError;
|
||||
use std::clone::Clone;
|
||||
use std::fmt::{Display, Formatter};
|
||||
generate_error_module!(
|
||||
generate_error_types!(TagError, TagErrorKind,
|
||||
TagTypeError => "Entry Header Tag Type wrong",
|
||||
HeaderReadError => "Error while reading entry header",
|
||||
HeaderWriteError => "Error while writing entry header",
|
||||
NotATag => "String is not a tag"
|
||||
);
|
||||
);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TagErrorKind {
|
||||
TagTypeError,
|
||||
HeaderReadError,
|
||||
HeaderWriteError,
|
||||
NotATag,
|
||||
}
|
||||
|
||||
fn tag_error_type_as_str(e: &TagErrorKind) -> &'static str {
|
||||
match e {
|
||||
&TagErrorKind::TagTypeError => "Entry Header Tag Type wrong",
|
||||
&TagErrorKind::HeaderReadError => "Error while reading entry header",
|
||||
&TagErrorKind::HeaderWriteError => "Error while writing entry header",
|
||||
&TagErrorKind::NotATag => "String is not a tag",
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TagErrorKind {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
|
||||
try!(write!(fmt, "{}", tag_error_type_as_str(self)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TagError {
|
||||
kind: TagErrorKind,
|
||||
cause: Option<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)
|
||||
}
|
||||
|
||||
}
|
||||
pub use self::error::TagError;
|
||||
pub use self::error::TagErrorKind;
|
||||
|
||||
|
|
|
@ -7,22 +7,20 @@ use tagable::*;
|
|||
use ui::{get_add_tags, get_remove_tags};
|
||||
|
||||
pub fn exec_cli_for_entry(matches: &ArgMatches, entry: &mut FileLockEntry) -> Result<()> {
|
||||
match get_add_tags(matches) {
|
||||
Some(ts) => for t in ts {
|
||||
if let Some(ts) = get_add_tags(matches) {
|
||||
for t in ts {
|
||||
if let Err(e) = entry.add_tag(t) {
|
||||
return Err(e);
|
||||
}
|
||||
},
|
||||
None => { },
|
||||
}
|
||||
}
|
||||
|
||||
match get_remove_tags(matches) {
|
||||
Some(ts) => for t in ts {
|
||||
if let Some(ts) = get_remove_tags(matches) {
|
||||
for t in ts {
|
||||
if let Err(e) = entry.remove_tag(t) {
|
||||
return Err(e);
|
||||
}
|
||||
},
|
||||
None => { },
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -19,6 +19,8 @@ extern crate regex;
|
|||
extern crate toml;
|
||||
|
||||
extern crate libimagstore;
|
||||
#[macro_use] extern crate libimagerror;
|
||||
#[macro_use] extern crate libimagutil;
|
||||
|
||||
pub mod error;
|
||||
pub mod exec;
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
pub type Tag = String;
|
||||
pub type TagSlice<'a> = &'a str;
|
||||
|
|
|
@ -4,10 +4,11 @@ use std::ops::DerefMut;
|
|||
use itertools::Itertools;
|
||||
|
||||
use libimagstore::store::{Entry, EntryHeader, FileLockEntry};
|
||||
use libimagerror::into::IntoError;
|
||||
|
||||
use error::{TagError, TagErrorKind};
|
||||
use error::TagErrorKind;
|
||||
use result::Result;
|
||||
use tag::Tag;
|
||||
use tag::{Tag, TagSlice};
|
||||
use util::is_tag;
|
||||
|
||||
use toml::Value;
|
||||
|
@ -15,13 +16,13 @@ use toml::Value;
|
|||
pub trait Tagable {
|
||||
|
||||
fn get_tags(&self) -> Result<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 remove_tag(&mut self, t: Tag) -> Result<()>;
|
||||
|
||||
fn has_tag(&self, t: &Tag) -> Result<bool>;
|
||||
fn has_tags(&self, ts: &Vec<Tag>) -> Result<bool>;
|
||||
fn has_tag(&self, t: TagSlice) -> Result<bool>;
|
||||
fn has_tags(&self, ts: &[Tag]) -> Result<bool>;
|
||||
|
||||
}
|
||||
|
||||
|
@ -31,20 +32,20 @@ impl Tagable for EntryHeader {
|
|||
let tags = self.read("imag.tags");
|
||||
if tags.is_err() {
|
||||
let kind = TagErrorKind::HeaderReadError;
|
||||
return Err(TagError::new(kind, Some(Box::new(tags.unwrap_err()))));
|
||||
return Err(kind.into_error_with_cause(Box::new(tags.unwrap_err())));
|
||||
}
|
||||
let tags = tags.unwrap();
|
||||
|
||||
match tags {
|
||||
Some(Value::Array(tags)) => {
|
||||
if !tags.iter().all(|t| match t { &Value::String(_) => true, _ => false }) {
|
||||
return Err(TagError::new(TagErrorKind::TagTypeError, None));
|
||||
if !tags.iter().all(|t| is_match!(*t, Value::String(_))) {
|
||||
return Err(TagErrorKind::TagTypeError.into());
|
||||
}
|
||||
if tags.iter().any(|t| match t {
|
||||
&Value::String(ref s) => !is_tag(&s),
|
||||
if tags.iter().any(|t| match *t {
|
||||
Value::String(ref s) => !is_tag(s),
|
||||
_ => unreachable!()})
|
||||
{
|
||||
return Err(TagError::new(TagErrorKind::NotATag, None));
|
||||
return Err(TagErrorKind::NotATag.into());
|
||||
}
|
||||
|
||||
Ok(tags.iter()
|
||||
|
@ -58,32 +59,33 @@ impl Tagable for EntryHeader {
|
|||
.collect())
|
||||
},
|
||||
None => Ok(vec![]),
|
||||
_ => Err(TagError::new(TagErrorKind::TagTypeError, None)),
|
||||
_ => Err(TagErrorKind::TagTypeError.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_tags(&mut self, ts: Vec<Tag>) -> Result<()> {
|
||||
fn set_tags(&mut self, ts: &[Tag]) -> Result<()> {
|
||||
if ts.iter().any(|tag| !is_tag(tag)) {
|
||||
debug!("Not a tag: '{}'", ts.iter().filter(|t| !is_tag(t)).next().unwrap());
|
||||
return Err(TagError::new(TagErrorKind::NotATag, None));
|
||||
return Err(TagErrorKind::NotATag.into());
|
||||
}
|
||||
|
||||
let a = ts.iter().unique().map(|t| Value::String(t.clone())).collect();
|
||||
self.set("imag.tags", Value::Array(a))
|
||||
.map(|_| ())
|
||||
.map_err(|e| TagError::new(TagErrorKind::HeaderWriteError, Some(Box::new(e))))
|
||||
.map_err(Box::new)
|
||||
.map_err(|e| TagErrorKind::HeaderWriteError.into_error_with_cause(e))
|
||||
}
|
||||
|
||||
fn add_tag(&mut self, t: Tag) -> Result<()> {
|
||||
if !is_tag(&t) {
|
||||
debug!("Not a tag: '{}'", t);
|
||||
return Err(TagError::new(TagErrorKind::NotATag, None));
|
||||
return Err(TagErrorKind::NotATag.into());
|
||||
}
|
||||
|
||||
self.get_tags()
|
||||
.map(|mut tags| {
|
||||
tags.push(t);
|
||||
self.set_tags(tags.into_iter().unique().collect())
|
||||
self.set_tags(&tags.into_iter().unique().collect::<Vec<_>>()[..])
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
@ -91,40 +93,40 @@ impl Tagable for EntryHeader {
|
|||
fn remove_tag(&mut self, t: Tag) -> Result<()> {
|
||||
if !is_tag(&t) {
|
||||
debug!("Not a tag: '{}'", t);
|
||||
return Err(TagError::new(TagErrorKind::NotATag, None));
|
||||
return Err(TagErrorKind::NotATag.into());
|
||||
}
|
||||
|
||||
self.get_tags()
|
||||
.map(|mut tags| {
|
||||
tags.retain(|tag| tag.clone() != t);
|
||||
self.set_tags(tags)
|
||||
self.set_tags(&tags[..])
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
fn has_tag(&self, t: &Tag) -> Result<bool> {
|
||||
fn has_tag(&self, t: TagSlice) -> Result<bool> {
|
||||
let tags = self.read("imag.tags");
|
||||
if tags.is_err() {
|
||||
let kind = TagErrorKind::HeaderReadError;
|
||||
return Err(TagError::new(kind, Some(Box::new(tags.unwrap_err()))));
|
||||
return Err(kind.into_error_with_cause(Box::new(tags.unwrap_err())));
|
||||
}
|
||||
let tags = tags.unwrap();
|
||||
|
||||
if !tags.iter().all(|t| match t { &Value::String(_) => true, _ => false }) {
|
||||
return Err(TagError::new(TagErrorKind::TagTypeError, None));
|
||||
if !tags.iter().all(|t| is_match!(*t, Value::String(_))) {
|
||||
return Err(TagErrorKind::TagTypeError.into());
|
||||
}
|
||||
|
||||
Ok(tags
|
||||
.iter()
|
||||
.any(|tag| {
|
||||
match tag {
|
||||
&Value::String(ref s) => { s == t },
|
||||
match *tag {
|
||||
Value::String(ref s) => { s == t },
|
||||
_ => unreachable!()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn has_tags(&self, tags: &Vec<Tag>) -> Result<bool> {
|
||||
fn has_tags(&self, tags: &[Tag]) -> Result<bool> {
|
||||
let mut result = true;
|
||||
for tag in tags {
|
||||
let check = self.has_tag(tag);
|
||||
|
@ -147,7 +149,7 @@ impl Tagable for Entry {
|
|||
self.get_header().get_tags()
|
||||
}
|
||||
|
||||
fn set_tags(&mut self, ts: Vec<Tag>) -> Result<()> {
|
||||
fn set_tags(&mut self, ts: &[Tag]) -> Result<()> {
|
||||
self.get_header_mut().set_tags(ts)
|
||||
}
|
||||
|
||||
|
@ -159,11 +161,11 @@ impl Tagable for Entry {
|
|||
self.get_header_mut().remove_tag(t)
|
||||
}
|
||||
|
||||
fn has_tag(&self, t: &Tag) -> Result<bool> {
|
||||
fn has_tag(&self, t: TagSlice) -> Result<bool> {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -175,7 +177,7 @@ impl<'a> Tagable for FileLockEntry<'a> {
|
|||
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)
|
||||
}
|
||||
|
||||
|
@ -187,11 +189,11 @@ impl<'a> Tagable for FileLockEntry<'a> {
|
|||
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)
|
||||
}
|
||||
|
||||
fn has_tags(&self, ts: &Vec<Tag>) -> Result<bool> {
|
||||
fn has_tags(&self, ts: &[Tag]) -> Result<bool> {
|
||||
self.deref().has_tags(ts)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,27 +2,35 @@ use clap::{Arg, ArgMatches, App, SubCommand};
|
|||
|
||||
use tag::Tag;
|
||||
|
||||
/// Generates a clap::SubCommand to be integrated in the commandline-ui builder for building a
|
||||
/// Generates a `clap::SubCommand` to be integrated in the commandline-ui builder for building a
|
||||
/// "tags --add foo --remove bar" subcommand to do tagging action.
|
||||
pub fn tag_subcommand<'a, 'b>() -> App<'a, 'b> {
|
||||
SubCommand::with_name(tag_subcommand_name())
|
||||
.author("Matthias Beyer <mail@beyermatthias.de>")
|
||||
.version("0.1")
|
||||
.about("Add or remove tags")
|
||||
.arg(tag_add_arg())
|
||||
.arg(tag_remove_arg())
|
||||
}
|
||||
|
||||
.arg(Arg::with_name(tag_subcommand_add_arg_name())
|
||||
.short("a")
|
||||
.long("add")
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.help("Add tags, seperated by comma or by specifying multiple times"))
|
||||
pub fn tag_add_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(tag_subcommand_add_arg_name())
|
||||
.short("a")
|
||||
.long("add")
|
||||
.takes_value(true)
|
||||
.value_name("tags")
|
||||
.multiple(true)
|
||||
.help("Add tags, seperated by comma or by specifying multiple times")
|
||||
}
|
||||
|
||||
.arg(Arg::with_name(tag_subcommand_remove_arg_name())
|
||||
.short("r")
|
||||
.long("remove")
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.help("Remove tags, seperated by comma or by specifying multiple times"))
|
||||
pub fn tag_remove_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(tag_subcommand_remove_arg_name())
|
||||
.short("r")
|
||||
.long("remove")
|
||||
.takes_value(true)
|
||||
.value_name("tags")
|
||||
.multiple(true)
|
||||
.help("Remove tags, seperated by comma or by specifying multiple times")
|
||||
}
|
||||
|
||||
pub fn tag_subcommand_name() -> &'static str {
|
||||
|
@ -41,7 +49,7 @@ pub fn tag_subcommand_names() -> Vec<&'static str> {
|
|||
vec![tag_subcommand_add_arg_name(), tag_subcommand_remove_arg_name()]
|
||||
}
|
||||
|
||||
/// Generates a clap::Arg which can be integrated into the commandline-ui builder for building a
|
||||
/// Generates a `clap::Arg` which can be integrated into the commandline-ui builder for building a
|
||||
/// "-t" or "--tags" argument which takes values for tagging actions (add, remove)
|
||||
pub fn tag_argument<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(tag_argument_name())
|
||||
|
@ -60,14 +68,18 @@ pub fn tag_argument_name() -> &'static str {
|
|||
///
|
||||
/// Returns none if the argument was not specified
|
||||
pub fn get_add_tags(matches: &ArgMatches) -> Option<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
|
||||
///
|
||||
/// Returns none if the argument was not specified
|
||||
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>> {
|
||||
|
@ -79,7 +91,7 @@ fn extract_tags(matches: &ArgMatches, specifier: &str, specchar: char) -> Option
|
|||
.map(|argmatches| {
|
||||
argmatches
|
||||
.map(String::from)
|
||||
.filter(|s| s.chars().next() == Some(specchar))
|
||||
.filter(|s| s.starts_with(specchar))
|
||||
.map(|s| {
|
||||
String::from(s.split_at(1).1)
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use regex::Regex;
|
||||
|
||||
pub fn is_tag(s: &String) -> bool {
|
||||
Regex::new("^[a-zA-Z]([a-zA-Z0-9_-]*)$").unwrap().captures(&s[..]).is_some()
|
||||
pub fn is_tag(s: &str) -> bool {
|
||||
Regex::new("^[a-zA-Z]([a-zA-Z0-9_-]*)$").unwrap().captures(s).is_some()
|
||||
}
|
||||
|
|
|
@ -8,3 +8,6 @@ authors = ["Matthias Beyer <mail@beyermatthias.de>"]
|
|||
[dependencies.libimagstore]
|
||||
path = "../libimagstore"
|
||||
|
||||
[dependencies.libimagerror]
|
||||
path = "../libimagerror"
|
||||
|
||||
|
|
|
@ -1,80 +1,9 @@
|
|||
use std::error::Error;
|
||||
use std::fmt::Error as FmtError;
|
||||
use std::clone::Clone;
|
||||
use std::fmt::{Display, Formatter};
|
||||
generate_error_module!(
|
||||
generate_error_types!(ViewError, ViewErrorKind,
|
||||
Unknown => "Unknown view error"
|
||||
);
|
||||
);
|
||||
|
||||
/**
|
||||
* Kind of error
|
||||
*/
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ViewErrorKind {
|
||||
}
|
||||
|
||||
fn counter_error_type_as_str(e: &ViewErrorKind) -> &'static str {
|
||||
match e {
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ViewErrorKind {
|
||||
|
||||
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
|
||||
try!(write!(fmt, "{}", counter_error_type_as_str(self)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Store error type
|
||||
*/
|
||||
#[derive(Debug)]
|
||||
pub struct ViewError {
|
||||
err_type: ViewErrorKind,
|
||||
cause: Option<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)
|
||||
}
|
||||
|
||||
}
|
||||
pub use self::error::ViewError;
|
||||
pub use self::error::ViewErrorKind;
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue