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

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

View file

@ -5,6 +5,31 @@ So you want to contribute to imag! Thank you, that's awesome of you!
If you already have something in mind, go ahead with [the prerequisites
section](#prerequisites). If you don't know what you could do, start here.
All contribors agree to the
[developer certificate of origin](#developer-certificate-of-origin)
by contributing to imag.
## Without Github
If you do not want to use github for your contribution, this is completely okay
with us. Feel free to contact [me](https://github.com/matthiasbeyer) via mail,
feel also free to submit patches via mail (use `git format-patch` and
`git send-email`, always add a cover letter to describe your submission).
Also ensure that each commit has
[a "Signed-off-by: " line](https://stackoverflow.com/questions/1962094/what-is-the-sign-off-feature-in-git-for).
By adding that line, you agree to our
[developer certificate of origin](#developer-certificate-of-origin).
We do not have a mailinglist, so we do not have a process where all contributors
can review your PR. This means that once _I am_ okay with your patchset, I will
submit it as PR in the github repository, so more people can review it and CI
can test it. I might come back to you if something broke in CI or someone has a
suggestion how to improve your PR.
I will keep you as author of the commits.
The following sections describe the way how to contribute with github.
## Finding an issue
Finding an issue is simple: We have
@ -95,7 +120,167 @@ ask questions as well!
Feel free to reach out via mail.
## More information about the structure of this project
Here goes some notes on how this project is structured.
### Issue- and PR-Labels
Our labels are color coded as well as "namespaced". The color groups labels
exactly as the prefix does. The prefix is the first component which is seperated
from the others by `"/`". See below:
| Label | Description | search |
| --- | --- | --- |
| complexity/easy | Easy to do | [search][search-complexity/easy] |
| complexity/high | Not so easy to do | [search][search-complexity/high] |
| complexity/medium | Relatively easy | [search][search-complexity/medium] |
| kind/bug | Bug | [search][search-kind/bug] |
| kind/doc | Documentation related | [search][search-kind/doc] |
| kind/enhancement | Enhancement | [search][search-kind/enhancement] |
| kind/feature | Feature | [search][search-kind/feature] |
| kind/hotfix | Hotfix | [search][search-kind/hotfix] |
| kind/infrastructure | Infrastructure code | [search][search-kind/infrastructure] |
| kind/invalid | Not valid Issue/PR | [search][search-kind/invalid] |
| kind/nicetohave | Would be a nice thing | [search][search-kind/nicetohave] |
| kind/refactor | Refactor codebase | [search][search-kind/refactor] |
| meta/assigned | Is assigned | [search][search-meta/assigned] |
| meta/blocked | Blocked by other Issue/PR | [search][search-meta/blocked] |
| meta/blocker | Blocks other Issue/PR | [search][search-meta/blocker] |
| meta/decision-pending | Not clear what to do | [search][search-meta/decision-pending] |
| meta/dependencies | Dependency-related | [search][search-meta/dependencies] |
| meta/doc | Documentation related | [search][search-meta/doc] |
| meta/importance/high | Very Important | [search][search-meta/importance/high] |
| meta/importance/low | Not so important | [search][search-meta/importance/low] |
| meta/importance/medium | Rather important | [search][search-meta/importance/medium] |
| meta/on-hold | Do not work on this! | [search][search-meta/on-hold] |
| meta/ready | Ready for review/merge | [search][search-meta/ready] |
| meta/reopen-later | Reopen closed issue/pr later | [search][search-meta/reopen-later] |
| meta/WIP | Work in Progress | [search][search-meta/WIP] |
| nochange/duplicate | Duplicated | [search][search-nochange/duplicate] |
| nochange/question | Question | [search][search-nochange/question] |
| nochange/rfc | Request for comments | [search][search-nochange/rfc] |
| nochange/wontfix | Won't fix this issue | [search][search-nochange/wontfix] |
| part/bin/imag-counter | Targets binary: imag-counter | [search][search-part/bin/imag-counter] |
| part/bin/imag-link | Targets binary: imag-link | [search][search-part/bin/imag-link] |
| part/bin/imag-store | Targets binary: imag-store | [search][search-part/bin/imag-store] |
| part/bin/imag-tag | Targets binary: imag-tag | [search][search-part/bin/imag-tag] |
| part/bin/imag-view | Targets binary: imag-view | [search][search-part/bin/imag-view] |
| part/interface | Changes the interface | [search][search-part/interface] |
| part/lib/imagcounter | Targets library: imagcounter | [search][search-part/lib/imagcounter] |
| part/lib/imagentryfilter | Targets library: imagentryfilter | [search][search-part/lib/imagentryfilter] |
| part/lib/imagentrylink | Targets library: imagentrylink | [search][search-part/lib/imagentrylink] |
| part/lib/imagentrylist | Targets library: imagentrylist | [search][search-part/lib/imagentrylist] |
| part/lib/imagentrymarkup | Targets library: imagentrymarkup | [search][search-part/lib/imagentrymarkup] |
| part/lib/imagentryprinter | Targets library: imagentryprinter | [search][search-part/lib/imagentryprinter] |
| part/lib/imagentrytag | Targets library: imagentrytag | [search][search-part/lib/imagentrytag] |
| part/lib/imagentryview | Targets library: imagentryview | [search][search-part/lib/imagentryview] |
| part/lib/imagnotes | Targets library: imagnotes | [search][search-part/lib/imagnotes] |
| part/lib/imagrt | Targets library: imagrt | [search][search-part/lib/imagrt] |
| part/lib/imagstore | Targets library: imagstore | [search][search-part/lib/imagstore] |
| part/lib/imagstorestdhook | Targets library: imagstorestdhook | [search][search-part/lib/imagstorestdhook] |
| part/lib/imagutil | Targets library: | [search][search-part/lib/imagutil] |
| part/_new_binary | Introduces new binary | [search][search-part/_new_binary] |
| part/_new_library | Introduces new library | [search][search-part/_new_library] |
| test/change | Changes a test | [search][search-test/change] |
| test/missing | Test missing | [search][search-test/missing] |
| test/new | New test | [search][search-test/new] |
## Developer Certificate of Origin
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
## FAQ
_to be written_
[search-complexity/easy]: https://github.com/matthiasbeyer/imag/labels/
[search-complexity/high]: https://github.com/matthiasbeyer/imag/labels/complexity%2Fhigh
[search-complexity/medium]: https://github.com/matthiasbeyer/imag/labels/complexity%2Fmedium
[search-kind/bug]: https://github.com/matthiasbeyer/imag/labels/kind%2Fbug
[search-kind/doc]: https://github.com/matthiasbeyer/imag/labels/kind%2Fdoc
[search-kind/enhancement]: https://github.com/matthiasbeyer/imag/labels/kind%2Fenhancement
[search-kind/feature]: https://github.com/matthiasbeyer/imag/labels/kind%2Ffeature
[search-kind/hotfix]: https://github.com/matthiasbeyer/imag/labels/kind%2Fhotfix
[search-kind/infrastructure]: https://github.com/matthiasbeyer/imag/labels/kind%2Finfrastructure
[search-kind/invalid]: https://github.com/matthiasbeyer/imag/labels/kind%2Finvalid
[search-kind/nicetohave]: https://github.com/matthiasbeyer/imag/labels/kind%2Fnicetohave
[search-kind/refactor]: https://github.com/matthiasbeyer/imag/labels/kind%2Frefactor
[search-meta/assigned]: https://github.com/matthiasbeyer/imag/labels/meta%2Fassigned
[search-meta/blocked]: https://github.com/matthiasbeyer/imag/labels/meta%2Fblocked
[search-meta/blocker]: https://github.com/matthiasbeyer/imag/labels/meta%2Fblocker
[search-meta/decision-pending]: https://github.com/matthiasbeyer/imag/labels/meta%2Fdecision-pending
[search-meta/dependencies]: https://github.com/matthiasbeyer/imag/labels/meta%2Fdependencies
[search-meta/doc]: https://github.com/matthiasbeyer/imag/labels/meta%2Fdoc
[search-meta/importance/high]: https://github.com/matthiasbeyer/imag/labels/meta%2Fimportance%2Fhigh
[search-meta/importance/low]: https://github.com/matthiasbeyer/imag/labels/meta%2Fimportance%2Flow
[search-meta/importance/medium]: https://github.com/matthiasbeyer/imag/labels/meta%2Fimportance%2Fmedium
[search-meta/on-hold]: https://github.com/matthiasbeyer/imag/labels/meta%2Fon-hold
[search-meta/ready]: https://github.com/matthiasbeyer/imag/labels/meta%2Fready
[search-meta/reopen-later]: https://github.com/matthiasbeyer/imag/labels/meta%2Freopen-later
[search-meta/WIP]: https://github.com/matthiasbeyer/imag/labels/meta%2FWIP
[search-nochange/duplicate]: https://github.com/matthiasbeyer/imag/labels/nochange%2Fduplicate
[search-nochange/question]: https://github.com/matthiasbeyer/imag/labels/nochange%2Fquestion
[search-nochange/rfc]: https://github.com/matthiasbeyer/imag/labels/nochange%2Frfc
[search-nochange/wontfix]: https://github.com/matthiasbeyer/imag/labels/nochange%2Fwontfix
[search-part/bin/imag-counter]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-counter
[search-part/bin/imag-link]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-link
[search-part/bin/imag-store]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-store
[search-part/bin/imag-tag]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-tag
[search-part/bin/imag-view]: https://github.com/matthiasbeyer/imag/labels/part%2Fbin%2Fimag-view
[search-part/interface]: https://github.com/matthiasbeyer/imag/labels/part%2F_interface
[search-part/lib/imagcounter]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagcounter
[search-part/lib/imagentryfilter]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentryfilter
[search-part/lib/imagentrylink]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentrylink
[search-part/lib/imagentrylist]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentrylist
[search-part/lib/imagentrymarkup]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentrymarkup
[search-part/lib/imagentryprinter]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentryprinter
[search-part/lib/imagentrytag]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentrytag
[search-part/lib/imagentryview]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagentryview
[search-part/lib/imagnotes]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagnotes
[search-part/lib/imagrt]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagrt
[search-part/lib/imagstore]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagstore
[search-part/lib/imagstorestdhook]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagstorestdhook
[search-part/lib/imagutil]: https://github.com/matthiasbeyer/imag/labels/part%2Flib%2Fimagutil
[search-part/_new_binary]: https://github.com/matthiasbeyer/imag/labels/part%2F_new_binary
[search-part/_new_library]: https://github.com/matthiasbeyer/imag/labels/part%2F_new_library
[search-test/change]: https://github.com/matthiasbeyer/imag/labels/test%2Fchange
[search-test/missing]: https://github.com/matthiasbeyer/imag/labels/test%2Fmissing
[search-test/new]: https://github.com/matthiasbeyer/imag/labels/test%2Fnew

18
Makefile Normal file
View file

@ -0,0 +1,18 @@
toml = $@/Cargo.toml
bin = $@/target/debug/$@
default: all
.PHONY: clean
all: imag-counter imag-link imag-notes imag-store imag-tag imag-view
imag-%: prep
cargo build --manifest-path $(toml)
cp $(bin) out/
prep:
mkdir -p out/
clean:
rm -rf out/

View file

@ -1,7 +1,7 @@
# imag
Imag is a CLI PIM suite with a nice API-ish commandline interface, so you can
integrate it in your tools of coice (Editor, MUA, RSS reader, etc etc).
integrate it in your tools of choice (Editor, MUA, RSS reader, etc etc).
## Goal
@ -89,26 +89,26 @@ provided (as the libraries are work-in-progress).
### Building
To build a single module:
One can build all the modules simply by running `make` which defaults to building all the modules
and placing them in the `out/` directory of the project root.
```
$> cd <imag-proj-dir>/imag-<module>
$> cargo build
```
It may be tiresome to build all the modules by hand, but one can do something
like this:
```
$> for dir in \
>$(find ./ -maxdepth 1 -path "./imag-*" -name "imag-*" -type d)
>do
>pushd $dir; cargo build; popd
>done
$> make
...
$> ls out/
imag-counter imag-link imag-notes imag-store imag-tag imag-view
```
Building all the modules may take some time, so alternatively one can build only a specific module
by runing `$> make $module` where `$module` is one of the `imag-*` names, such as `imag-counter`,
`imag-link`, etc.
### Running
To run imag, simply call `./bin/imag`. This script has a function to search for
modules, which utilizes an environment variable called `IMAG_IS_THE_SHIT`.
To run imag with all components:
```
$> IMAG_IS_THE_SHIT=$(pwd) ./bin/imag
```

10
bin/Cargo.toml Normal file
View file

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

View file

@ -1,79 +0,0 @@
#!/usr/bin/env bash
version() {
echo "0.1.0"
}
help() {
local cmds="$(commands)"
echo " _ ";
echo " (_)_ __ ___ __ _ __ _ ";
echo " | | '_ \` _ \ / _\` |/ _\` |";
echo " | | | | | | | (_| | (_| |";
echo " |_|_| |_| |_|\__,_|\__, |";
echo " |___/ ";
echo " -------------------------";
cat <<EOS
Usage: imag [--version | --versions | -h | --help] <command> <args...>
imag - the personal information management suite for the commandline
imag is a PIM suite for the commandline. It consists of several commands,
called "modules". Each module implements one PIM aspect and all of these
modules can be used independently.
Available commands:
$(for cmd in $cmds; do
echo -e "\t$(echo $cmd | sed -r 's,(.*)/imag-(.*),\2,')";
done)
Call a command with "imag <command> <args>"
Each command can be called with "--help" to get the respective helptext.
Please visit https://github.com/matthiasbeyer/imag to view the source code,
follow the development of imag or maybe even contribute to imag.
imag is free software. It is released under the terms of LGPLv2.1
(c) 2016 Matthias Beyer and contributors
EOS
}
commands() {
[[ ! -z "$IMAG_IS_THE_SHIT" ]] && \
find $IMAG_IS_THE_SHIT -type f -iname "imag-*" -print 2>/dev/null
find ${PATH//:/ } -maxdepth 1 -type f -iname "imag-*" -print 2>/dev/null
}
main() {
case $1 in
--versions)
echo -n "imag "; version
for command in $(commands); do
$command --version
done
exit 0
;;
--version)
version
exit 0
;;
--help | -h)
help
exit 0
;;
*)
local cmd=$1; shift
local executable=$(commands | grep $cmd | head -n 1)
exec $executable $*
;;
esac
}
main $*

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

@ -0,0 +1,187 @@
extern crate crossbeam;
#[macro_use] extern crate version;
extern crate walkdir;
use std::env;
use std::process::exit;
use std::process::Command;
use std::process::Stdio;
use std::io::ErrorKind;
use walkdir::WalkDir;
use crossbeam::*;
fn help(cmds: Vec<String>) {
println!(r#"
_
(_)_ __ ___ __ _ __ _
| | '_ \` _ \/ _\`|/ _\`|
| | | | | | | (_| | (_| |
|_|_| |_| |_|\__,_|\__, |
|___/
-------------------------
Usage: imag [--version | --versions | -h | --help] <command> <args...>
imag - the personal information management suite for the commandline
imag is a PIM suite for the commandline. It consists of several commands,
called "modules". Each module implements one PIM aspect and all of these
modules can be used independently.
Available commands:
"#);
for cmd in cmds.iter() {
println!("\t{}", cmd);
}
println!(r#"
Call a command with 'imag <command> <args>'
Each command can be called with "--help" to get the respective helptext.
Please visit https://github.com/matthiasbeyer/imag to view the source code,
follow the development of imag or maybe even contribute to imag.
imag is free software. It is released under the terms of LGPLv2.1
(c) 2016 Matthias Beyer and contributors"#);
}
fn get_commands() -> Vec<String> {
let path = env::var("PATH");
if path.is_err() {
println!("PATH error: {:?}", path);
exit(1);
}
let pathelements = path.unwrap();
let pathelements = pathelements.split(":");
let joinhandles : Vec<ScopedJoinHandle<Vec<String>>> = pathelements
.map(|elem| {
crossbeam::scope(|scope| {
scope.spawn(|| {
WalkDir::new(elem)
.max_depth(1)
.into_iter()
.filter(|path| {
match path {
&Ok(ref path) => path.path().starts_with(format!("{}/imag-", elem)),
&Err(_) => false,
}
})
.filter_map(|x| x.ok())
.map(|path| {
path.path()
.to_str()
.map(String::from)
})
.filter_map(|x| x)
.collect()
})
})
})
.collect();
let mut execs = vec![];
for joinhandle in joinhandles.into_iter() {
let mut v = joinhandle.join();
execs.append(&mut v);
}
execs
}
fn find_command() -> Option<String> {
env::args().skip(1).filter(|x| !x.starts_with("-")).next()
}
fn find_args(command: &str) -> Vec<String> {
env::args()
.skip(1)
.position(|e| e == command)
.map(|pos| env::args().skip(pos + 2).collect::<Vec<String>>())
.unwrap_or(vec![])
}
fn main() {
let commands = get_commands();
let mut args = env::args();
let _ = args.next();
let first_arg = match find_command() {
Some(s) => s,
None => {
help(commands);
exit(0);
},
};
match &first_arg[..] {
"--help" | "-h" => {
help(commands);
exit(0);
},
"--version" => println!("imag {}", &version!()[..]),
"--versions" => {
let mut result = vec![];
for command in commands.iter() {
result.push(crossbeam::scope(|scope| {
scope.spawn(|| {
let v = Command::new(command).arg("--version").output();
match v {
Ok(v) => match String::from_utf8(v.stdout) {
Ok(s) => format!("{} -> {}", command, s),
Err(e) => format!("Failed calling {} -> {:?}", command, e),
},
Err(e) => format!("Failed calling {} -> {:?}", command, e),
}
})
}))
}
for versionstring in result.into_iter().map(|handle| handle.join()) {
println!("{}", versionstring);
}
},
s => {
match Command::new(format!("imag-{}", s))
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.args(&find_args(s)[..])
.spawn()
.and_then(|mut handle| handle.wait())
{
Ok(exit_status) => {
if !exit_status.success() {
println!("{} exited with non-zero exit code", s);
exit(exit_status.code().unwrap_or(42));
}
},
Err(e) => {
match e.kind() {
ErrorKind::NotFound => {
println!("No such command: 'imag-{}'", s);
exit(2);
},
ErrorKind::PermissionDenied => {
println!("No permission to execute: 'imag-{}'", s);
exit(1);
},
_ => {
println!("Error spawning: {:?}", e);
exit(1337);
}
}
}
}
},
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,14 +19,15 @@ extern crate clap;
extern crate libimagcounter;
extern crate libimagrt;
extern crate libimagerror;
extern crate libimagutil;
use std::process::exit;
use std::str::FromStr;
use libimagrt::runtime::Runtime;
use libimagrt::setup::generate_runtime_setup;
use libimagcounter::counter::Counter;
use libimagutil::trace::trace_error;
use libimagerror::trace::trace_error;
use libimagutil::key_value_split::IntoKeyValue;
mod create;
@ -49,20 +50,10 @@ enum Action {
}
fn main() {
let name = "imag-counter";
let version = &version!()[..];
let about = "Counter tool to count things";
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about));
let rt = {
let rt = Runtime::new(ui);
if rt.is_ok() {
rt.unwrap()
} else {
println!("Could not set up Runtime");
println!("{:?}", rt.unwrap_err());
exit(1);
}
};
let rt = generate_runtime_setup("imag-counter",
&version!()[..],
"Counter tool to count things",
build_ui);
rt.cli()
.subcommand_name()

View file

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

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

@ -0,0 +1,38 @@
[package]
name = "imag-diary"
version = "0.1.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies]
chrono = "0.2"
version = "2.0"
clap = "2.1.1"
log = "0.3.5"
[dependencies.libimagrt]
path = "../libimagrt"
[dependencies.libimagdiary]
path = "../libimagdiary"
[dependencies.libimagentrylist]
path = "../libimagentrylist"
[dependencies.libimagentryview]
path = "../libimagentryview"
[dependencies.libimagerror]
path = "../libimagerror"
[dependencies.libimaginteraction]
path = "../libimaginteraction"
[dependencies.libimagutil]
path = "../libimagutil"
[dependencies.libimagstore]
path = "../libimagstore"
[dependencies.libimagtimeui]
path = "../libimagtimeui"

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

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

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

@ -0,0 +1,66 @@
use std::process::exit;
use chrono::naive::datetime::NaiveDateTime;
use libimagdiary::diary::Diary;
use libimagdiary::diaryid::DiaryId;
use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error;
use libimagtimeui::datetime::DateTime;
use libimagtimeui::parse::Parse;
use util::get_diary_name;
pub fn delete(rt: &Runtime) {
use libimaginteraction::ask::ask_bool;
let diaryname = get_diary_name(rt);
if diaryname.is_none() {
warn!("No diary selected. Use either the configuration file or the commandline option");
exit(1);
}
let diaryname = diaryname.unwrap();
let diary = Diary::open(rt.store(), &diaryname[..]);
debug!("Diary opened: {:?}", diary);
let datetime : Option<NaiveDateTime> = rt
.cli()
.subcommand_matches("delete")
.unwrap()
.value_of("datetime")
.map(|dt| { debug!("DateTime = {:?}", dt); dt })
.and_then(DateTime::parse)
.map(|dt| dt.into());
let to_del = match datetime {
Some(dt) => Some(diary.retrieve(DiaryId::from_datetime(diaryname.clone(), dt))),
None => diary.get_youngest_entry(),
};
let to_del = match to_del {
Some(Ok(e)) => e,
Some(Err(e)) => {
trace_error(&e);
exit(1);
},
None => {
warn!("No entry");
exit(1);
},
};
if !ask_bool(&format!("Deleting {:?}", to_del.get_location())[..], Some(true)) {
info!("Aborting delete action");
return;
}
match diary.delete_entry(to_del) {
Ok(_) => info!("Ok"),
Err(e) => {
trace_error(&e);
exit(1);
},
}
}

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

@ -0,0 +1,47 @@
use std::process::exit;
use chrono::naive::datetime::NaiveDateTime;
use libimagdiary::diary::Diary;
use libimagdiary::diaryid::DiaryId;
use libimagdiary::error::DiaryError as DE;
use libimagdiary::error::DiaryErrorKind as DEK;
use libimagrt::edit::Edit;
use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error;
use libimagtimeui::datetime::DateTime;
use libimagtimeui::parse::Parse;
use util::get_diary_name;
pub fn edit(rt: &Runtime) {
let diaryname = get_diary_name(rt);
if diaryname.is_none() {
warn!("No diary name");
exit(1);
}
let diaryname = diaryname.unwrap();
let diary = Diary::open(rt.store(), &diaryname[..]);
let datetime : Option<NaiveDateTime> = rt
.cli()
.subcommand_matches("edit")
.unwrap()
.value_of("datetime")
.and_then(DateTime::parse)
.map(|dt| dt.into());
let to_edit = match datetime {
Some(dt) => Some(diary.retrieve(DiaryId::from_datetime(diaryname.clone(), dt))),
None => diary.get_youngest_entry(),
};
match to_edit {
Some(Ok(mut e)) => e.edit_content(rt).map_err(|e| DE::new(DEK::IOError, Some(Box::new(e)))),
Some(Err(e)) => Err(e),
None => Err(DE::new(DEK::EntryNotInDiary, None)),
}
.map_err(|e| trace_error(&e)).ok();
}

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

@ -0,0 +1,52 @@
use std::path::PathBuf;
use std::process::exit;
use libimagdiary::diary::Diary;
use libimagdiary::error::DiaryError as DE;
use libimagdiary::error::DiaryErrorKind as DEK;
use libimagentrylist::listers::core::CoreLister;
use libimagentrylist::lister::Lister;
use libimagrt::runtime::Runtime;
use libimagstore::storeid::StoreId;
use libimagerror::trace::trace_error;
use util::get_diary_name;
pub fn list(rt: &Runtime) {
let diaryname = get_diary_name(rt);
if diaryname.is_none() {
warn!("No diary selected. Use either the configuration file or the commandline option");
exit(1);
}
let diaryname = diaryname.unwrap();
fn location_to_listing_string(id: &StoreId, base: &PathBuf) -> String {
id.strip_prefix(base)
.map_err(|e| trace_error(&e))
.ok()
.and_then(|p| p.to_str().map(String::from))
.unwrap_or(String::from("<<Path Parsing Error>>"))
}
let diary = Diary::open(rt.store(), &diaryname[..]);
debug!("Diary opened: {:?}", diary);
diary.entries()
.and_then(|es| {
debug!("Iterator for listing: {:?}", es);
let es = es.filter_map(|a| {
debug!("Filtering: {:?}", a);
a.ok()
}).map(|e| e.into());
let base = rt.store().path();
CoreLister::new(&move |e| location_to_listing_string(e.get_location(), base))
.list(es) // TODO: Do not ignore non-ok()s
.map_err(|e| DE::new(DEK::IOError, Some(Box::new(e))))
})
.map(|_| debug!("Ok"))
.map_err(|e| trace_error(&e))
.ok();
}

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

@ -0,0 +1,72 @@
#[macro_use] extern crate log;
#[macro_use] extern crate version;
extern crate clap;
extern crate chrono;
extern crate libimagdiary;
extern crate libimagentrylist;
extern crate libimagentryview;
extern crate libimaginteraction;
extern crate libimagrt;
extern crate libimagstore;
extern crate libimagutil;
extern crate libimagtimeui;
#[macro_use] extern crate libimagerror;
use std::process::exit;
use libimagrt::runtime::Runtime;
mod create;
mod delete;
mod edit;
mod list;
mod ui;
mod util;
mod view;
use create::create;
use delete::delete;
use edit::edit;
use list::list;
use ui::build_ui;
use view::view;
fn main() {
let name = "imag-diary";
let version = &version!()[..];
let about = "Personal Diary/Diaries";
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about));
let rt = {
let rt = Runtime::new(ui);
if rt.is_ok() {
rt.unwrap()
} else {
println!("Could not set up Runtime");
println!("{:?}", rt.err().unwrap());
exit(1);
}
};
rt.cli()
.subcommand_name()
.map(|name| {
debug!("Call {}", name);
match name {
"create" => create(&rt),
"delete" => delete(&rt),
"edit" => edit(&rt),
"list" => list(&rt),
"diary" => diary(&rt),
"view" => view(&rt),
_ => {
debug!("Unknown command"); // More error handling
},
}
});
}
fn diary(rt: &Runtime) {
unimplemented!()
}

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

@ -0,0 +1,107 @@
use clap::{Arg, ArgGroup, App, SubCommand};
pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
app
.arg(Arg::with_name("diaryname")
.long("diary")
.short("d")
.takes_value(true)
.required(false)
.help("Use other than default diary"))
.subcommand(SubCommand::with_name("create")
.about("Create a diary entry")
.version("0.1")
.arg(Arg::with_name("no-edit")
.long("no-edit")
.short("e")
.takes_value(false)
.required(false)
.help("Do not edit after creating"))
.arg(Arg::with_name("timed")
.long("timed")
.short("t")
.takes_value(true)
.required(false)
.help("By default, one entry is created per day. With --timed=h[ourly] or
--timed=m[inutely] one can create per-hour and per-minute entries (more like
a microblog then"))
.arg(Arg::with_name("hour")
.long("hour")
.takes_value(true)
.required(false)
.help("When using --timed, override the hour component"))
.arg(Arg::with_name("minute")
.long("minute")
.takes_value(true)
.required(false)
.help("When using --timed, override the minute component"))
// When using --hour or --minute, --timed must be present
.group(ArgGroup::with_name("timing-hourly")
.args(&["hour"])
.requires("timed"))
.group(ArgGroup::with_name("timing-minutely")
.args(&["minute"])
.requires("timed"))
)
.subcommand(SubCommand::with_name("edit")
.about("Edit a diary entry")
.version("0.1")
.arg(Arg::with_name("datetime")
.long("datetime")
.short("d")
.takes_value(true)
.required(false)
.help("Specify the date and time which entry should be edited. If none is
specified, the last entry is edited. If the diary entry does not exist for
this time, this fails. Format: YYYY-MM-DDT[HH[:mm[:ss]]]"))
)
.subcommand(SubCommand::with_name("list")
.about("List diary entries")
.version("0.1"))
.subcommand(SubCommand::with_name("delete")
.about("Delete a diary entry")
.version("0.1")
.arg(Arg::with_name("datetime")
.long("datetime")
.short("d")
.takes_value(true)
.required(false)
.help("Specify the date and time which entry should be deleted. If none is
specified, the last entry is deleted. If the diary entry does not exist for
this time, this fails. Format: YYYY-MM-DDT[HH[:mm[:ss]]]"))
.arg(Arg::with_name("select")
.long("select")
.short("s")
.takes_value(false)
.required(false)
.help("Use interactive selection"))
.arg(Arg::with_name("yes")
.long("yes")
.short("y")
.takes_value(false)
.required(false)
.help("Do not ask for confirmation."))
)
.subcommand(SubCommand::with_name("view")
.about("View entries, currently only supports plain viewing")
.version("0.1")
.arg(Arg::with_name("show-header")
.long("header")
.takes_value(false)
.required(false)
.help("Show the header when printing the entries"))
)
}

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

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

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

@ -0,0 +1,36 @@
use std::process::exit;
use libimagdiary::diary::Diary;
use libimagentryview::viewer::Viewer;
use libimagentryview::builtin::plain::PlainViewer;
use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error;
use util::get_diary_name;
pub fn view(rt: &Runtime) {
let diaryname = get_diary_name(rt);
if diaryname.is_none() {
warn!("No diary name");
exit(1);
}
let diaryname = diaryname.unwrap();
let diary = Diary::open(rt.store(), &diaryname[..]);
let show_header = rt.cli().subcommand_matches("view").unwrap().is_present("show-header");
match diary.entries() {
Ok(entries) => {
let pv = PlainViewer::new(show_header);
for entry in entries.into_iter().filter_map(Result::ok) {
let id = entry.diary_id();
println!("{} :\n", id);
pv.view_entry(&entry);
println!("\n---\n");
}
},
Err(e) => {
trace_error(&e);
},
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

@ -0,0 +1,31 @@
use std::process::exit;
use libimagstore::storeid::build_entry_path;
use libimagrt::runtime::Runtime;
use libimagerror::trace::trace_error;
use retrieve::print_entry;
pub fn get(rt: &Runtime) {
rt.cli()
.subcommand_matches("get")
.map(|scmd| {
scmd.value_of("id")
.map(|id| {
let path = build_entry_path(rt.store(), id);
if path.is_err() {
trace_error(&path.unwrap_err());
exit(1);
}
let path = path.unwrap();
debug!("path = {:?}", path);
match rt.store().get(path) {
Ok(Some(entry)) => print_entry(rt, scmd, entry),
Ok(None) => info!("No entry found"),
Err(e) => trace_error(&e),
}
})
});
}

View file

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

View file

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

View file

@ -9,17 +9,20 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("p")
.takes_value(true)
.required(false)
.help("Create at this store path"))
.help("Create at this store path")
.value_name("PATH"))
.arg(Arg::with_name("id")
.long("id")
.short("i")
.takes_value(true)
.required(false)
.help("Same as --path, for consistency"))
.help("Same as --path, for consistency")
.value_name("PATH"))
.arg(Arg::with_name("from-raw")
.long("from-raw")
.takes_value(true)
.help("Create a new entry by reading this file ('-' for stdin)"))
.help("Create a new entry by reading this file ('-' for stdin)")
.value_name("FILE"))
.group(ArgGroup::with_name("create-destination-group")
.args(&["path", "id"])
@ -32,12 +35,14 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.long("content")
.short("c")
.takes_value(true)
.help("Content for the Entry from commandline"))
.help("Content for the Entry from commandline")
.value_name("CONTENT"))
.arg(Arg::with_name("content-from")
.long("content-from")
.short("f")
.takes_value(true)
.help("Content for the Entry from this file ('-' for stdin)"))
.help("Content for the Entry from this file ('-' for stdin)")
.value_name("CONTENT"))
.group(ArgGroup::with_name("create-content-group")
.args(&["content", "content-from"])
@ -48,12 +53,13 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("h")
.takes_value(true)
.multiple(true)
.help("Set a header field. Specify as 'header.field.value=value', multiple allowed"))
.help("Set a header field. Specify as 'header.field.value=value', multiple allowed")
.value_name("header.field.value=value"))
)
)
.subcommand(SubCommand::with_name("retrieve")
.about("Get an entry from the store")
.about("Retrieve an entry from the store (implicitely creates the entry)")
.version("0.1")
.arg(Arg::with_name("id")
.long("id")
@ -95,6 +101,51 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
)
)
.subcommand(SubCommand::with_name("get")
.about("Get an entry from the store (fails if non-existent)")
.version("0.1")
.arg(Arg::with_name("id")
.long("id")
.short("i")
.takes_value(true)
.required(true)
.help("Retrieve by Store Path, where root (/) is the store itself")
.value_name("PATH"))
.arg(Arg::with_name("content")
.long("content")
.short("c")
.help("Print content"))
.arg(Arg::with_name("header")
.long("header")
.short("h")
.help("Print header"))
.arg(Arg::with_name("header-json")
.long("header-json")
.short("j")
.help("Print header as json"))
.arg(Arg::with_name("raw")
.long("raw")
.short("r")
.help("Print Entries as they are in the store"))
.subcommand(SubCommand::with_name("filter-header")
.about("Retrieve Entries by filtering")
.version("0.1")
.arg(Arg::with_name("header-field-where")
.long("where")
.short("w")
.takes_value(true)
.help("Filter with 'header.field=foo' where the header field 'header.field' equals 'foo'")
.value_name("header.field=foo")
)
.arg(Arg::with_name("header-field-grep")
.long("grep")
.short("g")
.takes_value(true)
.help("Filter with 'header.field=[a-zA-Z0-9]*' where the header field 'header.field' matches '[a-zA-Z0-9]*'"))
)
)
.subcommand(SubCommand::with_name("update")
.about("Get an entry from the store")
.version("0.1")
@ -103,12 +154,14 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("i")
.takes_value(true)
.required(true)
.help("Update Store Entry with this path. Root (/) is the store itself"))
.help("Update Store Entry with this path. Root (/) is the store itself")
.value_name("PATH"))
.arg(Arg::with_name("content")
.long("content")
.short("c")
.takes_value(true)
.help("Take the content for the new Entry from this file ('-' for stdin)"))
.help("Take the content for the new Entry from this file ('-' for stdin)")
.value_name("CONTENT"))
.arg(Arg::with_name("header")
.long("header")
.short("h")
@ -125,7 +178,7 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("i")
.takes_value(true)
.required(true)
.help("Remove Store Entry with this path. Root (/) is the store itself"))
.help("Remove Store Entry with this path. Root (/) is the store itself")
.value_name("PATH"))
)
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,87 +1,12 @@
use std::error::Error;
use std::fmt::Error as FmtError;
use std::clone::Clone;
use std::fmt::{Display, Formatter};
generate_error_module!(
generate_error_types!(ViewError, ViewErrorKind,
StoreError => "Store error",
NoVersion => "No version specified",
PatternError => "Error in Pattern",
GlobBuildError => "Could not build glob() Argument"
);
);
/**
* Kind of store error
*/
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ViewErrorKind {
StoreError,
NoVersion,
PatternError,
GlobBuildError,
}
fn view_error_type_as_str(e: &ViewErrorKind) -> &'static str {
match e {
&ViewErrorKind::StoreError => "Store error",
&ViewErrorKind::NoVersion => "No version specified",
&ViewErrorKind::PatternError => "Error in Pattern",
&ViewErrorKind::GlobBuildError => "Could not build glob() Argument",
}
}
impl Display for ViewErrorKind {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "{}", view_error_type_as_str(self)));
Ok(())
}
}
/**
* View error type
*/
#[derive(Debug)]
pub struct ViewError {
err_type: ViewErrorKind,
cause: Option<Box<Error>>,
}
impl ViewError {
/**
* Build a new ViewError from an ViewErrorKind, optionally with cause
*/
pub fn new(errtype: ViewErrorKind, cause: Option<Box<Error>>)
-> ViewError
{
ViewError {
err_type: errtype,
cause: cause,
}
}
/**
* Get the error type of this ViewError
*/
pub fn err_type(&self) -> ViewErrorKind {
self.err_type.clone()
}
}
impl Display for ViewError {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "[{}]", view_error_type_as_str(&self.err_type.clone())));
Ok(())
}
}
impl Error for ViewError {
fn description(&self) -> &str {
view_error_type_as_str(&self.err_type.clone())
}
fn cause(&self) -> Option<&Error> {
self.cause.as_ref().map(|e| &**e)
}
}
pub use self::error::ViewError;
pub use self::error::ViewErrorKind;

View file

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

View file

@ -7,14 +7,16 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("i")
.takes_value(true)
.required(true)
.help("View this entry at this store path"))
.help("View this entry at this store path")
.value_name("ID"))
.arg(Arg::with_name("version")
.long("version")
.short("V")
.takes_value(true)
.required(false)
.help("View this version (youngest if not specified)"))
.help("View this version (youngest if not specified)")
.value_name("VERSION"))
.arg(Arg::with_name("versions")
.long("versions")
@ -71,21 +73,24 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("b")
.takes_value(true) // optional, which browser
.required(false)
.help("View content in $BROWSER (fails if no env variable $BROWSER)"))
.help("View content in $BROWSER (fails if no env variable $BROWSER)")
.value_name("BROWSER"))
.arg(Arg::with_name("view-in-texteditor")
.long("editor")
.short("e")
.takes_value(true) // optional, which editor
.required(false)
.help("View content in $EDITOR"))
.help("View content in $EDITOR")
.value_name("EDITOR"))
.arg(Arg::with_name("view-in-custom")
.long("custom")
.short("c")
.takes_value(true) // non-optional, call-string
.required(false)
.help("View content in custom program, for example 'libreoffice %e', replace '%e' with entry path"))
.help("View content in custom program, for example 'libreoffice %e', replace '%e' with entry path")
.value_name("PROGRAM"))
.group(ArgGroup::with_name("viewer")
.args(&["view-in-stdout",
@ -105,15 +110,15 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
.short("f")
.takes_value(true) // "markdown" or "textile" or "restructuredtex"
.required(true)
.help("Compile from"))
.help("Compile from")
.value_name("FORMAT"))
.arg(Arg::with_name("to")
.long("to")
.short("t")
.takes_value(true) // "html" or "HTML" or ... json maybe?
.required(true)
.help("Compile to"))
.help("Compile to")
.value_name("FORMAT"))
)
}

31
imagrc.toml Normal file
View file

@ -0,0 +1,31 @@
# This is a example configuration file for the imag suite.
# It is written in TOML
[store]
# Hooks which get executed right before the Store is closed.
# They get the store path as StoreId passed, so they can alter the complete
# store, so these hooks should be chosen carefully.
store-unload-hook-aspects = [ "debug" ]
pre-create-hook-aspects = [ "debug" ]
post-create-hook-aspects = [ "debug" ]
pre-retrieve-hook-aspects = [ "debug" ]
post-retrieve-hook-aspects = [ "debug" ]
pre-update-hook-aspects = [ "debug" ]
post-update-hook-aspects = [ "debug" ]
pre-delete-hook-aspects = [ "debug" ]
post-delete-hook-aspects = [ "debug" ]
[store.aspects]
[[aspects.debug]]
parallel = false
[store.hooks]
[[hooks.debug]]
aspect = "debug"

View file

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

View file

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

View file

@ -1,87 +1,12 @@
use std::error::Error;
use std::fmt::Error as FmtError;
use std::clone::Clone;
use std::fmt::{Display, Formatter};
generate_error_module!(
generate_error_types!(CounterError, CounterErrorKind,
StoreReadError => "Store read error",
StoreWriteError => "Store write error",
HeaderTypeError => "Header type error",
HeaderFieldMissingError => "Header field missing error"
);
);
/**
* Kind of error
*/
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CounterErrorKind {
StoreReadError,
StoreWriteError,
HeaderTypeError,
HeaderFieldMissingError,
}
fn counter_error_type_as_str(e: &CounterErrorKind) -> &'static str {
match e {
&CounterErrorKind::StoreReadError => "Store read error",
&CounterErrorKind::StoreWriteError => "Store write error",
&CounterErrorKind::HeaderTypeError => "Header type error",
&CounterErrorKind::HeaderFieldMissingError => "Header field missing error",
}
}
impl Display for CounterErrorKind {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "{}", counter_error_type_as_str(self)));
Ok(())
}
}
/**
* Store error type
*/
#[derive(Debug)]
pub struct CounterError {
err_type: CounterErrorKind,
cause: Option<Box<Error>>,
}
impl CounterError {
/**
* Build a new CounterError from an CounterErrorKind, optionally with cause
*/
pub fn new(errtype: CounterErrorKind, cause: Option<Box<Error>>)
-> CounterError
{
CounterError {
err_type: errtype,
cause: cause,
}
}
/**
* Get the error type of this CounterError
*/
pub fn err_type(&self) -> CounterErrorKind {
self.err_type.clone()
}
}
impl Display for CounterError {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "[{}]", counter_error_type_as_str(&self.err_type.clone())));
Ok(())
}
}
impl Error for CounterError {
fn description(&self) -> &str {
counter_error_type_as_str(&self.err_type.clone())
}
fn cause(&self) -> Option<&Error> {
self.cause.as_ref().map(|e| &**e)
}
}
pub use self::error::CounterError;
pub use self::error::CounterErrorKind;

View file

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

26
libimagdiary/Cargo.toml Normal file
View file

@ -0,0 +1,26 @@
[package]
name = "libimagdiary"
version = "0.1.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies]
chrono = "0.2"
log = "0.3.5"
semver = "0.2"
toml = "0.1.25"
regex = "0.1"
lazy_static = "0.2"
itertools = "0.4"
[dependencies.libimagstore]
path = "../libimagstore"
[dependencies.libimagerror]
path = "../libimagerror"
[dependencies.libimagutil]
path = "../libimagutil"
[dependencies.libimagrt]
path = "../libimagrt"

View file

@ -0,0 +1,19 @@
use toml::Value;
use libimagrt::runtime::Runtime;
pub fn get_default_diary_name(rt: &Runtime) -> Option<String> {
get_diary_config_section(rt)
.and_then(|config| {
match config.lookup("default_diary") {
Some(&Value::String(ref s)) => Some(s.clone()),
_ => None,
}
})
}
pub fn get_diary_config_section<'a>(rt: &'a Runtime) -> Option<&'a Value> {
rt.config()
.map(|config| config.config())
.and_then(|config| config.lookup("diary"))
}

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

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

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

@ -0,0 +1,217 @@
use std::convert::Into;
use std::fmt::{Display, Formatter, Error as FmtError};
use chrono::naive::datetime::NaiveDateTime;
use chrono::naive::time::NaiveTime;
use chrono::naive::date::NaiveDate;
use chrono::Datelike;
use chrono::Timelike;
use libimagstore::storeid::StoreId;
use libimagstore::storeid::IntoStoreId;
use module_path::ModuleEntryPath;
#[derive(Debug, Clone)]
pub struct DiaryId {
name: String,
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
}
impl DiaryId {
pub fn new(name: String, y: i32, m: u32, d: u32, h: u32, min: u32) -> DiaryId {
DiaryId {
name: name,
year: y,
month: m,
day: d,
hour: h,
minute: min,
}
}
pub fn from_datetime<DT: Datelike + Timelike>(diary_name: String, dt: DT) -> DiaryId {
DiaryId::new(diary_name, dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute())
}
pub fn diary_name(&self) -> &String {
&self.name
}
pub fn year(&self) -> i32 {
self.year
}
pub fn month(&self) -> u32 {
self.month
}
pub fn day(&self) -> u32 {
self.day
}
pub fn hour(&self) -> u32 {
self.hour
}
pub fn minute(&self) -> u32 {
self.minute
}
pub fn with_diary_name(mut self, name: String) -> DiaryId {
self.name = name;
self
}
pub fn with_year(mut self, year: i32) -> DiaryId {
self.year = year;
self
}
pub fn with_month(mut self, month: u32) -> DiaryId {
self.month = month;
self
}
pub fn with_day(mut self, day: u32) -> DiaryId {
self.day = day;
self
}
pub fn with_hour(mut self, hour: u32) -> DiaryId {
self.hour = hour;
self
}
pub fn with_minute(mut self, minute: u32) -> DiaryId {
self.minute = minute;
self
}
pub fn now(name: String) -> DiaryId {
use chrono::offset::local::Local;
let now = Local::now();
let now_date = now.date().naive_local();
let now_time = now.time();
let dt = NaiveDateTime::new(now_date, now_time);
DiaryId::new(name, dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute())
}
}
impl Default for DiaryId {
/// Create a default DiaryId which is a diaryid for a diary named "default" with
/// time = 0000-00-00 00:00:00
fn default() -> DiaryId {
let dt = NaiveDateTime::new(NaiveDate::from_ymd(0, 0, 0), NaiveTime::from_hms(0, 0, 0));
DiaryId::from_datetime(String::from("default"), dt)
}
}
impl IntoStoreId for DiaryId {
fn into_storeid(self) -> StoreId {
let s : String = self.into();
ModuleEntryPath::new(s).into_storeid()
}
}
impl Into<String> for DiaryId {
fn into(self) -> String {
format!("{}/{:0>4}/{:0>2}/{:0>2}/{:0>2}:{:0>2}",
self.name, self.year, self.month, self.day, self.hour, self.minute)
}
}
impl Display for DiaryId {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
write!(fmt, "{}/{:0>4}/{:0>2}/{:0>2}/{:0>2}:{:0>2}",
self.name, self.year, self.month, self.day, self.hour, self.minute)
}
}
impl Into<NaiveDateTime> for DiaryId {
fn into(self) -> NaiveDateTime {
let d = NaiveDate::from_ymd(self.year, self.month, self.day);
let t = NaiveTime::from_hms(self.hour, self.minute, 0);
NaiveDateTime::new(d, t)
}
}
pub trait FromStoreId : Sized {
fn from_storeid(&StoreId) -> Option<Self>;
}
use std::path::Component;
fn component_to_str<'a>(com: Component<'a>) -> Option<&'a str> {
match com {
Component::Normal(s) => Some(s),
_ => None
}.and_then(|s| s.to_str())
}
impl FromStoreId for DiaryId {
fn from_storeid(s: &StoreId) -> Option<DiaryId> {
use std::str::FromStr;
let mut cmps = s.components().rev();
let (hour, minute) = match cmps.next().and_then(component_to_str)
.and_then(|time| {
let mut time = time.split(":");
let hour = time.next().and_then(|s| FromStr::from_str(s).ok());
let minute = time.next()
.and_then(|s| s.split("~").next())
.and_then(|s| FromStr::from_str(s).ok());
debug!("Hour = {:?}", hour);
debug!("Minute = {:?}", minute);
match (hour, minute) {
(Some(h), Some(m)) => Some((h, m)),
_ => None,
}
})
{
Some(s) => s,
None => return None,
};
let day :Option<u32> = cmps.next().and_then(component_to_str).and_then(|s| FromStr::from_str(s).ok());
let month :Option<u32> = cmps.next().and_then(component_to_str).and_then(|s| FromStr::from_str(s).ok());
let year :Option<i32> = cmps.next().and_then(component_to_str).and_then(|s| FromStr::from_str(s).ok());
let name = cmps.next().and_then(component_to_str).map(String::from);
debug!("Day = {:?}", day);
debug!("Month = {:?}", month);
debug!("Year = {:?}", year);
debug!("Name = {:?}", name);
let day = if day.is_none() { return None; } else { day.unwrap() };
let month = if month.is_none() { return None; } else { month.unwrap() };
let year = if year.is_none() { return None; } else { year.unwrap() };
let name = if name.is_none() { return None; } else { name.unwrap() };
Some(DiaryId::new(name, year, month, day, hour, minute))
}
}

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

@ -0,0 +1,71 @@
use std::ops::Deref;
use std::ops::DerefMut;
use libimagstore::store::FileLockEntry;
use libimagrt::edit::Edit;
use libimagrt::edit::EditResult;
use libimagrt::runtime::Runtime;
use diaryid::DiaryId;
use diaryid::FromStoreId;
#[derive(Debug)]
pub struct Entry<'a>(FileLockEntry<'a>);
impl<'a> Deref for Entry<'a> {
type Target = FileLockEntry<'a>;
fn deref(&self) -> &FileLockEntry<'a> {
&self.0
}
}
impl<'a> DerefMut for Entry<'a> {
fn deref_mut(&mut self) -> &mut FileLockEntry<'a> {
&mut self.0
}
}
impl<'a> Entry<'a> {
pub fn new(fle: FileLockEntry<'a>) -> Entry<'a> {
Entry(fle)
}
/// Get the diary id for this entry.
///
/// TODO: calls Option::unwrap() as it assumes that an existing Entry has an ID that is parsable
pub fn diary_id(&self) -> DiaryId {
DiaryId::from_storeid(&self.0.get_location().clone()).unwrap()
}
}
impl<'a> Into<FileLockEntry<'a>> for Entry<'a> {
fn into(self) -> FileLockEntry<'a> {
self.0
}
}
impl<'a> From<FileLockEntry<'a>> for Entry<'a> {
fn from(fle: FileLockEntry<'a>) -> Entry<'a> {
Entry::new(fle)
}
}
impl<'a> Edit for Entry<'a> {
fn edit_content(&mut self, rt: &Runtime) -> EditResult<()> {
self.0.edit_content(rt)
}
}

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

@ -0,0 +1,16 @@
generate_error_module!(
generate_error_types!(DiaryError, DiaryErrorKind,
StoreWriteError => "Error writing store",
StoreReadError => "Error reading store",
CannotFindDiary => "Cannot find diary",
CannotCreateNote => "Cannot create Note object for diary entry",
DiaryEditError => "Cannot edit diary entry",
PathConversionError => "Error while converting paths internally",
EntryNotInDiary => "Entry not in Diary",
IOError => "IO Error"
);
);
pub use self::error::DiaryError;
pub use self::error::DiaryErrorKind;

View file

@ -0,0 +1,26 @@
use std::path::PathBuf;
use libimagstore::store::Entry;
pub trait IsInDiary {
fn is_in_diary(&self, name: &str) -> bool;
}
impl IsInDiary for Entry {
fn is_in_diary(&self, name: &str) -> bool {
self.get_location().is_in_diary(name)
}
}
impl IsInDiary for PathBuf {
fn is_in_diary(&self, name: &str) -> bool {
self.to_str().map(|s| s.contains(name)).unwrap_or(false)
}
}

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

@ -0,0 +1,111 @@
use std::fmt::{Debug, Formatter, Error as FmtError};
use std::result::Result as RResult;
use libimagstore::store::Store;
use libimagstore::storeid::StoreIdIterator;
use diaryid::DiaryId;
use diaryid::FromStoreId;
use is_in_diary::IsInDiary;
use entry::Entry as DiaryEntry;
use error::DiaryError as DE;
use error::DiaryErrorKind as DEK;
use result::Result;
/// A iterator for iterating over diary entries
pub struct DiaryEntryIterator<'a> {
store: &'a Store,
name: &'a str,
iter: StoreIdIterator,
year: Option<i32>,
month: Option<u32>,
day: Option<u32>,
}
impl<'a> Debug for DiaryEntryIterator<'a> {
fn fmt(&self, fmt: &mut Formatter) -> RResult<(), FmtError> {
write!(fmt, "DiaryEntryIterator<name = {}, year = {:?}, month = {:?}, day = {:?}>",
self.name, self.year, self.month, self.day)
}
}
impl<'a> DiaryEntryIterator<'a> {
pub fn new(diaryname: &'a str, store: &'a Store, iter: StoreIdIterator) -> DiaryEntryIterator<'a> {
DiaryEntryIterator {
store: store,
name: diaryname,
iter: iter,
year: None,
month: None,
day: None,
}
}
// Filter by year, get all diary entries for this year
pub fn year(mut self, year: i32) -> DiaryEntryIterator<'a> {
self.year = Some(year);
self
}
// Filter by month, get all diary entries for this month (every year)
pub fn month(mut self, month: u32) -> DiaryEntryIterator<'a> {
self.month = Some(month);
self
}
// Filter by day, get all diary entries for this day (every year, every year)
pub fn day(mut self, day: u32) -> DiaryEntryIterator<'a> {
self.day = Some(day);
self
}
}
impl<'a> Iterator for DiaryEntryIterator<'a> {
type Item = Result<DiaryEntry<'a>>;
fn next(&mut self) -> Option<Result<DiaryEntry<'a>>> {
loop {
let next = self.iter.next();
debug!("Next element: {:?}", next);
if next.is_none() {
return None;
}
let next = next.unwrap();
if next.is_in_diary(self.name) {
debug!("Seems to be in diary: {:?}", next);
let id = DiaryId::from_storeid(&next);
if id.is_none() {
debug!("Couldn't parse {:?} into DiaryId", next);
continue;
}
let id = id.unwrap();
debug!("Success parsing id = {:?}", id);
let y = match self.year { None => true, Some(y) => y == id.year() };
let m = match self.month { None => true, Some(m) => m == id.month() };
let d = match self.day { None => true, Some(d) => d == id.day() };
if y && m && d {
debug!("Return = {:?}", id);
return Some(self
.store
.retrieve(next)
.map(|fle| DiaryEntry::new(fle))
.map_err(|e| DE::new(DEK::StoreReadError, Some(Box::new(e))))
);
}
} else {
debug!("Not in the requested diary ({}): {:?}", self.name, next);
}
}
}
}

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

@ -0,0 +1,39 @@
#![deny(
dead_code,
non_camel_case_types,
non_snake_case,
path_statements,
trivial_numeric_casts,
unstable_features,
unused_allocation,
unused_import_braces,
unused_imports,
unused_must_use,
unused_mut,
unused_qualifications,
while_true,
)]
extern crate chrono;
#[macro_use] extern crate log;
#[macro_use] extern crate lazy_static;
extern crate semver;
extern crate toml;
extern crate regex;
extern crate itertools;
#[macro_use] extern crate libimagstore;
#[macro_use] extern crate libimagutil;
#[macro_use] extern crate libimagerror;
extern crate libimagrt;
module_entry_path_mod!("diary", "0.1.0");
pub mod config;
pub mod error;
pub mod diaryid;
pub mod diary;
pub mod is_in_diary;
pub mod entry;
pub mod iter;
pub mod result;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,90 +1,16 @@
use std::error::Error;
use std::fmt::Error as FmtError;
use std::fmt::{Display, Formatter};
generate_error_module!(
generate_error_types!(LinkError, LinkErrorKind,
EntryHeaderReadError => "Error while reading an entry header",
EntryHeaderWriteError => "Error while writing an entry header",
ExistingLinkTypeWrong => "Existing link entry has wrong type",
LinkTargetDoesNotExist => "Link target does not exist in the store",
InternalConversionError => "Error while converting values internally",
InvalidUri => "URI is not valid",
StoreReadError => "Store read error",
StoreWriteError => "Store write error"
);
);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LinkErrorKind {
EntryHeaderReadError,
EntryHeaderWriteError,
ExistingLinkTypeWrong,
LinkTargetDoesNotExist,
InternalConversionError,
InvalidUri,
StoreReadError,
StoreWriteError,
}
pub use self::error::LinkError;
pub use self::error::LinkErrorKind;
fn link_error_type_as_str(e: &LinkErrorKind) -> &'static str {
match e {
&LinkErrorKind::EntryHeaderReadError
=> "Error while reading an entry header",
&LinkErrorKind::EntryHeaderWriteError
=> "Error while writing an entry header",
&LinkErrorKind::ExistingLinkTypeWrong
=> "Existing link entry has wrong type",
&LinkErrorKind::LinkTargetDoesNotExist
=> "Link target does not exist in the store",
&LinkErrorKind::InternalConversionError
=> "Error while converting values internally",
&LinkErrorKind::InvalidUri
=> "URI is not valid",
&LinkErrorKind::StoreReadError
=> "Store read error",
&LinkErrorKind::StoreWriteError
=> "Store write error",
}
}
impl Display for LinkErrorKind {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "{}", link_error_type_as_str(self)));
Ok(())
}
}
#[derive(Debug)]
pub struct LinkError {
kind: LinkErrorKind,
cause: Option<Box<Error>>,
}
impl LinkError {
pub fn new(errtype: LinkErrorKind, cause: Option<Box<Error>>) -> LinkError {
LinkError {
kind: errtype,
cause: cause,
}
}
}
impl Display for LinkError {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "[{}]", link_error_type_as_str(&self.kind)));
Ok(())
}
}
impl Error for LinkError {
fn description(&self) -> &str {
link_error_type_as_str(&self.kind)
}
fn cause(&self) -> Option<&Error> {
self.cause.as_ref().map(|e| &**e)
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,85 +1,12 @@
use std::error::Error;
use std::fmt::Error as FmtError;
use std::clone::Clone;
use std::fmt::{Display, Formatter};
generate_error_module!(
generate_error_types!(ListError, ListErrorKind,
FormatError => "FormatError",
EntryError => "EntryError",
IterationError => "IterationError",
CLIError => "No CLI subcommand for listing entries"
);
);
/**
* Kind of error
*/
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ListErrorKind {
FormatError,
EntryError,
IterationError,
CLIError,
}
fn counter_error_type_as_str(err: &ListErrorKind) -> &'static str{
match err {
&ListErrorKind::FormatError => "FormatError",
&ListErrorKind::EntryError => "EntryError",
&ListErrorKind::IterationError => "IterationError",
&ListErrorKind::CLIError => "No CLI subcommand for listing entries",
}
}
impl Display for ListErrorKind {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "{}", counter_error_type_as_str(self)));
Ok(())
}
}
/**
* Store error type
*/
#[derive(Debug)]
pub struct ListError {
err_type: ListErrorKind,
cause: Option<Box<Error>>,
}
impl ListError {
/**
* Build a new ListError from an ListErrorKind, optionally with cause
*/
pub fn new(errtype: ListErrorKind, cause: Option<Box<Error>>) -> ListError {
ListError {
err_type: errtype,
cause: cause,
}
}
/**
* Get the error type of this ListError
*/
pub fn err_type(&self) -> ListErrorKind {
self.err_type.clone()
}
}
impl Display for ListError {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "[{}]", counter_error_type_as_str(&self.err_type.clone())));
Ok(())
}
}
impl Error for ListError {
fn description(&self) -> &str {
counter_error_type_as_str(&self.err_type.clone())
}
fn cause(&self) -> Option<&Error> {
self.cause.as_ref().map(|e| &**e)
}
}
pub use self::error::ListError;
pub use self::error::ListErrorKind;

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,9 @@
# libimagentrymarkdown
Helper crate to add useful functionality in a wrapper around
[hoedown](https://crates.io/crates/hoedown) for imag.
Adds functionality to extract links, parse content into HTML and other things
which might be useful for markdown rendering in imag.

View file

@ -0,0 +1,11 @@
generate_error_module!(
generate_error_types!(MarkdownError, MarkdownErrorKind,
MarkdownRenderError => "Markdown render error",
LinkParsingError => "Link parsing error"
);
);
pub use self::error::MarkdownError;
pub use self::error::MarkdownErrorKind;

View file

@ -0,0 +1,82 @@
use hoedown::{Markdown, Html as MdHtml};
use hoedown::renderer::html::Flags as HtmlFlags;
use hoedown::renderer::Render;
use result::Result;
use error::MarkdownErrorKind;
use libimagerror::into::IntoError;
pub type HTML = String;
pub fn to_html(buffer: &str) -> Result<HTML> {
let md = Markdown::new(buffer);
let mut html = MdHtml::new(HtmlFlags::empty(), 0);
html.render(&md)
.to_str()
.map(String::from)
.map_err(Box::new)
.map_err(|e| MarkdownErrorKind::MarkdownRenderError.into_error_with_cause(e))
}
pub mod iter {
use result::Result;
use libimagstore::store::Entry;
use super::HTML;
use super::to_html;
pub struct ToHtmlIterator<I: Iterator<Item = Entry>> {
i: I
}
impl<I: Iterator<Item = Entry>> ToHtmlIterator<I> {
fn new(i: I) -> ToHtmlIterator<I> {
ToHtmlIterator { i: i }
}
}
impl<I: Iterator<Item = Entry>> Iterator for ToHtmlIterator<I> {
type Item = Result<HTML>;
fn next(&mut self) -> Option<Self::Item> {
self.i.next().map(|entry| to_html(&entry.get_content()[..]))
}
}
impl<I: Iterator<Item = Entry>> From<I> for ToHtmlIterator<I> {
fn from(obj: I) -> ToHtmlIterator<I> {
ToHtmlIterator::new(obj)
}
}
/// Iterate over `(Entry, Result<HTML>)` tuples
pub struct WithHtmlIterator<I: Iterator<Item = Entry>> {
i: I
}
impl<I: Iterator<Item = Entry>> WithHtmlIterator<I> {
fn new(i: I) -> WithHtmlIterator<I> {
WithHtmlIterator { i: i }
}
}
impl<I: Iterator<Item = Entry>> Iterator for WithHtmlIterator<I> {
type Item = (Entry, Result<HTML>);
fn next(&mut self) -> Option<Self::Item> {
self.i.next().map(|entry| {
let html = to_html(&entry.get_content()[..]);
(entry, html)
})
}
}
}

View file

@ -0,0 +1,12 @@
#[macro_use] extern crate log;
extern crate crossbeam;
extern crate hoedown;
extern crate url;
extern crate libimagstore;
#[macro_use] extern crate libimagerror;
pub mod error;
pub mod html;
pub mod link;
pub mod result;

View file

@ -0,0 +1,143 @@
use error::MarkdownErrorKind as MEK;
use result::Result;
use hoedown::renderer::Render;
use hoedown::Buffer;
use hoedown::Markdown;
use url::Url;
use libimagerror::into::IntoError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Link {
pub title: String,
pub link: String,
}
impl Link {
/// Translate a `Link` into a `UrlLink`
fn into_urllink(self) -> Result<UrlLink> {
Url::parse(&self.link[..])
.map(move |link| UrlLink { title: self.title, link: link, })
.map_err(Box::new)
.map_err(|e| MEK::LinkParsingError.into_error_with_cause(e))
}
}
pub struct UrlLink {
pub title: String,
pub link: Url,
}
struct LinkExtractor {
links: Vec<Link>,
}
impl LinkExtractor {
pub fn new() -> LinkExtractor {
LinkExtractor { links: vec![] }
}
pub fn links(self) -> Vec<Link> {
self.links
}
}
impl Render for LinkExtractor {
fn link(&mut self,
_: &mut Buffer,
content: Option<&Buffer>,
link: Option<&Buffer>,
_: Option<&Buffer>)
-> bool
{
let link = link.and_then(|l| l.to_str().ok()).map(String::from);
let content = content.and_then(|l| l.to_str().ok()).map(String::from);
match (link, content) {
(Some(link), Some(content)) => {
self.links.push(Link { link: link, title: content });
false
},
(_, _) => {
false
},
}
}
}
pub fn extract_links(buf: &str) -> Vec<Link> {
let mut le = LinkExtractor::new();
le.render(&Markdown::new(buf));
le.links()
}
#[cfg(test)]
mod test {
use super::{Link, extract_links};
#[test]
fn test_one_link() {
let testtext = "Some [example text](http://example.com).";
let exp = Link {
title: String::from("example text"),
link: String::from("http://example.com"),
};
let mut links = extract_links(testtext);
assert_eq!(1, links.len());
assert_eq!(exp, links.pop().unwrap())
}
#[test]
fn test_two_similar_links() {
let testtext = r#"
Some [example text](http://example.com).
Some more [example text](http://example.com).
"#;
let exp = Link {
title: String::from("example text"),
link: String::from("http://example.com"),
};
let mut links = extract_links(&testtext[..]);
assert_eq!(2, links.len());
assert_eq!(exp, links.pop().unwrap());
assert_eq!(exp, links.pop().unwrap());
}
#[test]
fn test_two_links() {
let testtext = r#"
Some [example text](http://example.com).
Some more [foo](http://example.com/foo).
"#;
let exp1 = Link {
title: String::from("example text"),
link: String::from("http://example.com"),
};
let exp2 = Link {
title: String::from("foo"),
link: String::from("http://example.com/foo"),
};
let mut links = extract_links(&testtext[..]);
assert_eq!(2, links.len());
assert_eq!(exp2, links.pop().unwrap());
assert_eq!(exp1, links.pop().unwrap());
}
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,53 @@
use std::path::PathBuf;
use clap::{Arg, ArgMatches};
use libimagstore::storeid::StoreId;
use libimagerror::trace::trace_error;
pub fn id_argument<'a, 'b>() -> Arg<'a, 'b> {
Arg::with_name(id_argument_name())
.short(id_argument_short())
.long(id_argument_long())
.takes_value(true)
.multiple(true)
.help("Specify the Store-Id")
}
pub fn id_argument_name() -> &'static str {
"id-argument"
}
pub fn id_argument_short() -> &'static str {
"i"
}
pub fn id_argument_long() -> &'static str {
"id"
}
pub fn get_id(matches: &ArgMatches) -> Option<Vec<StoreId>> {
matches.values_of(id_argument_name())
.map(|vals| {
vals.into_iter()
.map(String::from)
.map(StoreId::from)
.collect()
})
}
pub fn get_or_select_id(matches: &ArgMatches, store_path: &PathBuf) -> Option<Vec<StoreId>> {
use interactor::{pick_file, default_menu_cmd};
get_id(matches).or_else(|| {
match pick_file(default_menu_cmd, store_path.clone()) {
Err(e) => {
trace_error(&e);
None
},
Ok(p) => Some(vec![StoreId::from(p)]),
}
})
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,80 +1,9 @@
use std::error::Error;
use std::fmt::Error as FmtError;
use std::clone::Clone;
use std::fmt::{Display, Formatter};
generate_error_module!(
generate_error_types!(ViewError, ViewErrorKind,
Unknown => "Unknown view error"
);
);
/**
* Kind of error
*/
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ViewErrorKind {
}
fn counter_error_type_as_str(e: &ViewErrorKind) -> &'static str {
match e {
_ => "",
}
}
impl Display for ViewErrorKind {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "{}", counter_error_type_as_str(self)));
Ok(())
}
}
/**
* Store error type
*/
#[derive(Debug)]
pub struct ViewError {
err_type: ViewErrorKind,
cause: Option<Box<Error>>,
}
impl ViewError {
/**
* Build a new ViewError from an ViewErrorKind, optionally with cause
*/
pub fn new(errtype: ViewErrorKind, cause: Option<Box<Error>>)
-> ViewError
{
ViewError {
err_type: errtype,
cause: cause,
}
}
/**
* Get the error type of this ViewError
*/
pub fn err_type(&self) -> ViewErrorKind {
self.err_type.clone()
}
}
impl Display for ViewError {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "[{}]", counter_error_type_as_str(&self.err_type.clone())));
Ok(())
}
}
impl Error for ViewError {
fn description(&self) -> &str {
counter_error_type_as_str(&self.err_type.clone())
}
fn cause(&self) -> Option<&Error> {
self.cause.as_ref().map(|e| &**e)
}
}
pub use self::error::ViewError;
pub use self::error::ViewErrorKind;

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