Merge pull request #316 from matthiasbeyer/imag-diary/init

Imag diary/init
This commit is contained in:
Matthias Beyer 2016-06-09 16:51:34 +02:00
commit af88d64f44
11 changed files with 543 additions and 1 deletions

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

@ -82,15 +82,18 @@ impl<'a> Iterator for DiaryEntryIterator<'a> {
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)

View File

@ -23,7 +23,7 @@ extern crate regex;
extern crate itertools;
#[macro_use] extern crate libimagstore;
extern crate libimagutil;
#[macro_use] extern crate libimagutil;
#[macro_use] extern crate libimagerror;
extern crate libimagrt;