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