From 2e6df0e458a52ade3189f2613376ab50cfbd9c5e Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 28 Sep 2019 14:08:59 +0200 Subject: [PATCH 01/12] Add imag-calendar This patch adds a imag-calendar command which features only a "import" command by now. This can be used to import calendar entries (events) to imag. Signed-off-by: Matthias Beyer --- Cargo.toml | 1 + bin/domain/imag-calendar/Cargo.toml | 46 ++++++++ bin/domain/imag-calendar/src/main.rs | 150 +++++++++++++++++++++++++++ bin/domain/imag-calendar/src/ui.rs | 71 +++++++++++++ scripts/release.sh | 1 + 5 files changed, 269 insertions(+) create mode 100644 bin/domain/imag-calendar/Cargo.toml create mode 100644 bin/domain/imag-calendar/src/main.rs create mode 100644 bin/domain/imag-calendar/src/ui.rs diff --git a/Cargo.toml b/Cargo.toml index 97a0d53b..1e52bdaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ members = [ "bin/core/imag-tag", "bin/core/imag-view", "bin/domain/imag-bookmark", + "bin/domain/imag-calendar", "bin/domain/imag-contact", "bin/domain/imag-diary", "bin/domain/imag-habit", diff --git a/bin/domain/imag-calendar/Cargo.toml b/bin/domain/imag-calendar/Cargo.toml new file mode 100644 index 00000000..6e320c9a --- /dev/null +++ b/bin/domain/imag-calendar/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "imag-calendar" +version = "0.10.0" +authors = ["Matthias Beyer "] +edition = "2018" + +description = "Part of the imag core distribution: imag-calendar command" + +keywords = ["imag", "PIM", "personal", "information", "management"] +readme = "../../../README.md" +license = "LGPL-2.1" + +documentation = "https://imag-pim.org/doc/" +repository = "https://github.com/matthiasbeyer/imag" +homepage = "http://imag-pim.org" + +[badges] +travis-ci = { repository = "matthiasbeyer/imag" } +is-it-maintained-issue-resolution = { repository = "matthiasbeyer/imag" } +is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" } +maintenance = { status = "actively-developed" } + +[dependencies] +log = "0.4" +failure = "0.1" +walkdir = "2.2.8" + +libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" } +libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } +libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" } +libimagutil = { version = "0.10.0", path = "../../../lib/etc/libimagutil" } +libimagentryref = { version = "0.10.0", path = "../../../lib/entry/libimagentryref" } +libimagentryedit = { version = "0.10.0", path = "../../../lib/entry/libimagentryedit" } +libimaginteraction = { version = "0.10.0", path = "../../../lib/etc/libimaginteraction" } +libimagcalendar = { version = "0.10.0", path = "../../../lib/domain/libimagcalendar" } + +[dependencies.clap] +version = "2.33.0" +default-features = false +features = ["color", "suggestions", "wrap_help"] + +[dependencies.toml-query] +version = "0.9.2" +default-features = false +features = ["typed"] + diff --git a/bin/domain/imag-calendar/src/main.rs b/bin/domain/imag-calendar/src/main.rs new file mode 100644 index 00000000..624a2c63 --- /dev/null +++ b/bin/domain/imag-calendar/src/main.rs @@ -0,0 +1,150 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +#![forbid(unsafe_code)] + +#![deny( + 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, +)] + +#[macro_use] extern crate failure; +#[macro_use] extern crate log; +extern crate clap; +extern crate toml_query; +extern crate walkdir; + +#[macro_use] extern crate libimagrt; +extern crate libimagcalendar; +extern crate libimagerror; +extern crate libimagstore; +extern crate libimagutil; + +use std::path::PathBuf; + +use failure::Error; +use failure::Fallible as Result; +use toml_query::read::Partial; +use toml_query::read::TomlValueReadExt; +use walkdir::DirEntry; +use walkdir::WalkDir; + +use libimagcalendar::store::EventStore; +use libimagerror::exit::ExitUnwrap; +use libimagerror::iter::TraceIterator; +use libimagerror::trace::MapErrTrace; +use libimagrt::runtime::Runtime; +use libimagrt::setup::generate_runtime_setup; + +mod ui; + +fn main() { + let version = make_imag_version!(); + let rt = generate_runtime_setup("imag-calendar", + &version, + "Calendar management tool", + crate::ui::build_ui); + + + if let Some(name) = rt.cli().subcommand_name() { + debug!("Call {}", name); + match name { + "import" => import(&rt), + other => { + warn!("Right now, only the 'import' command is available"); + debug!("Unknown command"); + let _ = rt.handle_unknown_subcommand("imag-calendar", other, rt.cli()) + .map_err_trace_exit_unwrap() + .code() + .map(::std::process::exit); + }, + } + } +} + +fn import(rt: &Runtime) { + let scmd = rt.cli().subcommand_matches("import").unwrap(); // safe by clap + let collection_name = rt.cli().value_of("calendar-ref-collection-name").unwrap(); // default by clap + let do_fail = scmd.is_present("import-fail"); + let force_override = scmd.is_present("import-force-override"); + let ref_config = rt.config() + .ok_or_else(|| format_err!("No configuration, cannot continue!")) + .map_err_trace_exit_unwrap() + .read_partial::() + .map_err(Error::from) + .map_err_trace_exit_unwrap() + .ok_or_else(|| format_err!("Configuration missing: {}", libimagentryref::reference::Config::LOCATION)) + .map_err_trace_exit_unwrap(); + + scmd.values_of("filesordirs") + .unwrap() // save by clap + .into_iter() + .map(PathBuf::from) + .map(|path| if path.is_dir() { // Find all files + Box::new(WalkDir::new(path) + .follow_links(false) + .into_iter() + .filter_entry(is_not_hidden) + .filter_map(|r| match r { + Err(e) => Some(Err(Error::from(e))), + Ok(fe) => { + if fe.file_type().is_file() { + Some(Ok(fe.into_path())) + } else { + None // filter out directories + } + } + })) as Box>> + } else { // is file, ensured by clap validator + Box::new(std::iter::once(Ok(path))) + }) + .flat_map(|it| it) // From Iter>> to Iter> + .trace_unwrap_exit() //... to Iter + .map(|path| { + let v = rt.store().import_from_path(path, collection_name, &ref_config, force_override)?; + Ok(v.into_iter() + .filter_map(|result| if do_fail { + Some(result.map_err_trace_exit_unwrap()) + } else { + match result { + Err(e) => { warn!("Error while importing: {}", e); None } + Ok(fle) => Some(fle), + } + })) + }) + .trace_unwrap_exit() + .flat_map(|it| it) + .for_each(|fle| rt.report_touched(fle.get_location()).unwrap_or_exit()); +} + +/// helper function to check whether a DirEntry points to something hidden (starting with dot) +fn is_not_hidden(entry: &DirEntry) -> bool { + !entry.file_name().to_str().map(|s| s.starts_with(".")).unwrap_or(false) +} + diff --git a/bin/domain/imag-calendar/src/ui.rs b/bin/domain/imag-calendar/src/ui.rs new file mode 100644 index 00000000..3a8f94c7 --- /dev/null +++ b/bin/domain/imag-calendar/src/ui.rs @@ -0,0 +1,71 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use clap::{Arg, App, SubCommand}; + +pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { + app + .arg(Arg::with_name("calendar-ref-collection-name") + .long("ref-collection") + .takes_value(true) + .required(false) + .multiple(false) + .default_value("calendars") + .help("Name (Key) of the basepath setting in the configuration file to use")) + + .subcommand(SubCommand::with_name("import") + .about("Import directory of calendar files or files directl") + .version("0.1") + .arg(Arg::with_name("filesordirs") + .index(1) + .takes_value(true) + .required(true) + .multiple(true) + .value_name("PATH") + .validator(import_validator) + .help("Import files from this directory (or specify files directly)")) + + .arg(Arg::with_name("import-fail") + .short("F") + .long("fail") + .takes_value(false) + .required(false) + .multiple(false) + .help("Fail if a file cannot be parsed (if directory is given, all files found must be icalendar files)")) + + .arg(Arg::with_name("import-force-override") + .long("force") + .takes_value(false) + .required(false) + .multiple(false) + .help("Override if entry for event already exists")) + ) +} + +fn import_validator>(s: A) -> Result<(), String> { + use libimagutil::cli_validators::*; + + is_existing_path(s.as_ref())?; + + match (is_file(s.as_ref()), is_directory(s.as_ref())) { + (Err(_), Err(_)) => Err(format!("Not a file or directory: {}", s.as_ref())), + _ => Ok(()) + } +} + diff --git a/scripts/release.sh b/scripts/release.sh index 98dbba99..21ea10ae 100644 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -41,6 +41,7 @@ CRATES=( ./lib/domain/libimagwiki ./bin/domain/imag-habit ./bin/domain/imag-diary + ./bin/domain/imag-calendar ./bin/domain/imag-contact ./bin/domain/imag-notes ./bin/domain/imag-bookmark From 61f71d67cc47bd95b99cc19da1eae5df04b2511b Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sat, 28 Sep 2019 14:13:48 +0200 Subject: [PATCH 02/12] Add example ref configuration for calendar Signed-off-by: Matthias Beyer --- bin/domain/imag-calendar/src/main.rs | 13 ++++++++++++- imagrc.toml | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/bin/domain/imag-calendar/src/main.rs b/bin/domain/imag-calendar/src/main.rs index 624a2c63..7e286901 100644 --- a/bin/domain/imag-calendar/src/main.rs +++ b/bin/domain/imag-calendar/src/main.rs @@ -102,6 +102,14 @@ fn import(rt: &Runtime) { .ok_or_else(|| format_err!("Configuration missing: {}", libimagentryref::reference::Config::LOCATION)) .map_err_trace_exit_unwrap(); + // sanity check + debug!("Doing sanity check on config, to see whether the configuration required for importing is there"); + if ref_config.get(collection_name).is_none() { + error!("Configuration missing: {}.{}", libimagentryref::reference::Config::LOCATION, collection_name); + ::std::process::exit(1); + } + + debug!("Starting import..."); scmd.values_of("filesordirs") .unwrap() // save by clap .into_iter() @@ -115,7 +123,9 @@ fn import(rt: &Runtime) { Err(e) => Some(Err(Error::from(e))), Ok(fe) => { if fe.file_type().is_file() { - Some(Ok(fe.into_path())) + let path = fe.into_path(); + trace!("Found file: {}", path.display()); + Some(Ok(path)) } else { None // filter out directories } @@ -127,6 +137,7 @@ fn import(rt: &Runtime) { .flat_map(|it| it) // From Iter>> to Iter> .trace_unwrap_exit() //... to Iter .map(|path| { + trace!("Importing {}", path.display()); let v = rt.store().import_from_path(path, collection_name, &ref_config, force_override)?; Ok(v.into_iter() .filter_map(|result| if do_fail { diff --git a/imagrc.toml b/imagrc.toml index d17268e7..3b353803 100644 --- a/imagrc.toml +++ b/imagrc.toml @@ -353,6 +353,7 @@ execute_in_store = false music = "/home/user/music" mail = "/home/user/mail" contacts = "/home/user/contacts" +calendars = "/home/user/calendars" [mail] # The name of the mail reference collection From 8dbb2f1590d535832ec7d90e8c07f565f8a2339f Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 3 Oct 2019 12:54:44 +0200 Subject: [PATCH 03/12] Add basic listing functionality Signed-off-by: Matthias Beyer --- bin/domain/imag-calendar/Cargo.toml | 7 +-- bin/domain/imag-calendar/src/main.rs | 66 ++++++++++++++++++++++++++++ bin/domain/imag-calendar/src/ui.rs | 5 +++ bin/domain/imag-calendar/src/util.rs | 60 +++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 bin/domain/imag-calendar/src/util.rs diff --git a/bin/domain/imag-calendar/Cargo.toml b/bin/domain/imag-calendar/Cargo.toml index 6e320c9a..62e21521 100644 --- a/bin/domain/imag-calendar/Cargo.toml +++ b/bin/domain/imag-calendar/Cargo.toml @@ -21,9 +21,10 @@ is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" } maintenance = { status = "actively-developed" } [dependencies] -log = "0.4" -failure = "0.1" -walkdir = "2.2.8" +log = "0.4" +failure = "0.1" +walkdir = "2.2.8" +vobject = "0.7" libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" } libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } diff --git a/bin/domain/imag-calendar/src/main.rs b/bin/domain/imag-calendar/src/main.rs index 7e286901..f5b9bbdb 100644 --- a/bin/domain/imag-calendar/src/main.rs +++ b/bin/domain/imag-calendar/src/main.rs @@ -47,8 +47,11 @@ extern crate libimagstore; extern crate libimagutil; use std::path::PathBuf; +use std::result::Result as RResult; +use std::io::Write; use failure::Error; +use failure::err_msg; use failure::Fallible as Result; use toml_query::read::Partial; use toml_query::read::TomlValueReadExt; @@ -56,6 +59,7 @@ use walkdir::DirEntry; use walkdir::WalkDir; use libimagcalendar::store::EventStore; +use libimagerror::io::ToExitCode; use libimagerror::exit::ExitUnwrap; use libimagerror::iter::TraceIterator; use libimagerror::trace::MapErrTrace; @@ -63,6 +67,7 @@ use libimagrt::runtime::Runtime; use libimagrt::setup::generate_runtime_setup; mod ui; +mod util; fn main() { let version = make_imag_version!(); @@ -76,6 +81,7 @@ fn main() { debug!("Call {}", name); match name { "import" => import(&rt), + "list" => list(&rt), other => { warn!("Right now, only the 'import' command is available"); debug!("Unknown command"); @@ -154,6 +160,66 @@ fn import(rt: &Runtime) { .for_each(|fle| rt.report_touched(fle.get_location()).unwrap_or_exit()); } +fn list(rt: &Runtime) { + use util::*; + + let scmd = rt.cli().subcommand_matches("list").unwrap(); // safe by clap + let ref_config = rt.config() + .ok_or_else(|| format_err!("No configuration, cannot continue!")) + .map_err_trace_exit_unwrap() + .read_partial::() + .map_err(Error::from) + .map_err_trace_exit_unwrap() + .ok_or_else(|| format_err!("Configuration missing: {}", libimagentryref::reference::Config::LOCATION)) + .map_err_trace_exit_unwrap(); + + let event_filter = |pefle: &ParsedEventFLE| true; // TODO: impl filtering + + rt.store() + .all_events() + .map_err_trace_exit_unwrap() + .trace_unwrap_exit() + .map(|sid| rt.store().get(sid)) + .trace_unwrap_exit() + .map(|oe| oe.ok_or_else(|| err_msg("Missing entry while calling all_events()"))) + .trace_unwrap_exit() + .map(|ev| ParsedEventFLE::parse(ev, &ref_config)) + .trace_unwrap_exit() + .filter(|e| event_filter(e)) + .for_each(|parsed_event| { + for event in parsed_event.get_data().events().filter_map(RResult::ok) { + macro_rules! get_data { + ($t:expr, $text:expr) => { + ($t).map(|obj| obj.into_raw()).unwrap_or_else(|| String::from($text)) + } + } + + let summary = get_data!(event.summary(), ""); + let uid = get_data!(event.uid(), ""); + let description = get_data!(event.description(), ""); + let dtstart = get_data!(event.dtstart(), ""); + let dtend = get_data!(event.dtend(), ""); + let location = get_data!(event.location(), ""); + + let summary_underline = std::iter::repeat('-').take(summary.len()).collect::(); + + writeln!(rt.stdout(), + "{summary}\n{summary_underline}\n\n{uid}\n{description}\n{dtstart}\n{dtend}\n{location}\n\n", + summary = summary, + summary_underline = summary_underline, + uid = uid, + description = description, + dtstart = dtstart, + dtend = dtend, + location = location) + .to_exit_code() + .unwrap_or_exit(); + } + + rt.report_touched(parsed_event.get_entry().get_location()).unwrap_or_exit(); + }); +} + /// helper function to check whether a DirEntry points to something hidden (starting with dot) fn is_not_hidden(entry: &DirEntry) -> bool { !entry.file_name().to_str().map(|s| s.starts_with(".")).unwrap_or(false) diff --git a/bin/domain/imag-calendar/src/ui.rs b/bin/domain/imag-calendar/src/ui.rs index 3a8f94c7..148e2ce7 100644 --- a/bin/domain/imag-calendar/src/ui.rs +++ b/bin/domain/imag-calendar/src/ui.rs @@ -56,6 +56,11 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .multiple(false) .help("Override if entry for event already exists")) ) + + .subcommand(SubCommand::with_name("list") + .about("List calendar entries") + .version("0.1") + ) } fn import_validator>(s: A) -> Result<(), String> { diff --git a/bin/domain/imag-calendar/src/util.rs b/bin/domain/imag-calendar/src/util.rs new file mode 100644 index 00000000..6c142337 --- /dev/null +++ b/bin/domain/imag-calendar/src/util.rs @@ -0,0 +1,60 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use vobject::icalendar::ICalendar; +use failure::Fallible as Result; +use failure::Error; + +use libimagstore::store::FileLockEntry; +use libimagentryref::reference::fassade::RefFassade; +use libimagentryref::reference::Ref; +use libimagentryref::reference::Config; +use libimagentryref::hasher::default::DefaultHasher; + +pub struct ParsedEventFLE<'a> { + inner: FileLockEntry<'a>, + data: ICalendar, +} + +impl<'a> ParsedEventFLE<'a> { + + /// Because libimagcalendar only links to the actual calendar data, we need to read the data and + /// parse it. + /// With this function, a FileLockEntry can be parsed to a ParsedEventFileLockEntry + /// (ParsedEventFLE). + pub fn parse(fle: FileLockEntry<'a>, refconfig: &Config) -> Result { + fle.as_ref_with_hasher::() + .get_path(refconfig) + .and_then(|p| ::std::fs::read_to_string(p).map_err(Error::from)) + .and_then(|s| ICalendar::build(&s).map_err(Error::from)) + .map(|cal| ParsedEventFLE { + inner: fle, + data: cal, + }) + } + + pub fn get_entry(&self) -> &FileLockEntry<'a> { + &self.inner + } + + pub fn get_data(&self) -> &ICalendar { + &self.data + } +} + From 14dc03f40f373efa690d6f766e5273223bf3d765 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 3 Oct 2019 13:27:06 +0200 Subject: [PATCH 04/12] Enhance listing functionality with handlebars templating Signed-off-by: Matthias Beyer --- bin/domain/imag-calendar/Cargo.toml | 9 +++-- bin/domain/imag-calendar/src/main.rs | 57 +++++++++++++-------------- bin/domain/imag-calendar/src/ui.rs | 7 ++++ bin/domain/imag-calendar/src/util.rs | 59 ++++++++++++++++++++++++++++ imagrc.toml | 15 +++++++ 5 files changed, 114 insertions(+), 33 deletions(-) diff --git a/bin/domain/imag-calendar/Cargo.toml b/bin/domain/imag-calendar/Cargo.toml index 62e21521..7801ac80 100644 --- a/bin/domain/imag-calendar/Cargo.toml +++ b/bin/domain/imag-calendar/Cargo.toml @@ -21,10 +21,11 @@ is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" } maintenance = { status = "actively-developed" } [dependencies] -log = "0.4" -failure = "0.1" -walkdir = "2.2.8" -vobject = "0.7" +log = "0.4" +failure = "0.1" +walkdir = "2.2.8" +vobject = "0.7" +handlebars = "2" libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" } libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } diff --git a/bin/domain/imag-calendar/src/main.rs b/bin/domain/imag-calendar/src/main.rs index f5b9bbdb..a94e2ee2 100644 --- a/bin/domain/imag-calendar/src/main.rs +++ b/bin/domain/imag-calendar/src/main.rs @@ -39,6 +39,7 @@ extern crate clap; extern crate toml_query; extern crate walkdir; +extern crate handlebars; #[macro_use] extern crate libimagrt; extern crate libimagcalendar; @@ -163,7 +164,10 @@ fn import(rt: &Runtime) { fn list(rt: &Runtime) { use util::*; - let scmd = rt.cli().subcommand_matches("list").unwrap(); // safe by clap + let scmd = rt.cli().subcommand_matches("list").unwrap(); // safe by clap + let list_format = get_event_print_format("calendar.list_format", rt, &scmd) + .map_err_trace_exit_unwrap(); + let ref_config = rt.config() .ok_or_else(|| format_err!("No configuration, cannot continue!")) .map_err_trace_exit_unwrap() @@ -173,8 +177,14 @@ fn list(rt: &Runtime) { .ok_or_else(|| format_err!("Configuration missing: {}", libimagentryref::reference::Config::LOCATION)) .map_err_trace_exit_unwrap(); + + debug!("List format: {:?}", list_format); + debug!("Ref config : {:?}", ref_config); + let event_filter = |pefle: &ParsedEventFLE| true; // TODO: impl filtering + let mut listed_events = 0; + rt.store() .all_events() .map_err_trace_exit_unwrap() @@ -186,37 +196,25 @@ fn list(rt: &Runtime) { .map(|ev| ParsedEventFLE::parse(ev, &ref_config)) .trace_unwrap_exit() .filter(|e| event_filter(e)) - .for_each(|parsed_event| { - for event in parsed_event.get_data().events().filter_map(RResult::ok) { - macro_rules! get_data { - ($t:expr, $text:expr) => { - ($t).map(|obj| obj.into_raw()).unwrap_or_else(|| String::from($text)) - } - } + .for_each(|parsed_entry| { + parsed_entry + .get_data() + .events() + .filter_map(RResult::ok) + .filter(event_filter) + .for_each(|event| { + listed_events = listed_events + 1; + let data = build_data_object_for_handlebars(listed_events, &event); - let summary = get_data!(event.summary(), ""); - let uid = get_data!(event.uid(), ""); - let description = get_data!(event.description(), ""); - let dtstart = get_data!(event.dtstart(), ""); - let dtend = get_data!(event.dtend(), ""); - let location = get_data!(event.location(), ""); + let rendered = list_format + .render("format", &data) + .map_err(Error::from) + .map_err_trace_exit_unwrap(); - let summary_underline = std::iter::repeat('-').take(summary.len()).collect::(); + writeln!(rt.stdout(), "{}", rendered).to_exit_code().unwrap_or_exit() + }); - writeln!(rt.stdout(), - "{summary}\n{summary_underline}\n\n{uid}\n{description}\n{dtstart}\n{dtend}\n{location}\n\n", - summary = summary, - summary_underline = summary_underline, - uid = uid, - description = description, - dtstart = dtstart, - dtend = dtend, - location = location) - .to_exit_code() - .unwrap_or_exit(); - } - - rt.report_touched(parsed_event.get_entry().get_location()).unwrap_or_exit(); + rt.report_touched(parsed_entry.get_entry().get_location()).unwrap_or_exit(); }); } @@ -225,3 +223,4 @@ fn is_not_hidden(entry: &DirEntry) -> bool { !entry.file_name().to_str().map(|s| s.starts_with(".")).unwrap_or(false) } + diff --git a/bin/domain/imag-calendar/src/ui.rs b/bin/domain/imag-calendar/src/ui.rs index 148e2ce7..8386357e 100644 --- a/bin/domain/imag-calendar/src/ui.rs +++ b/bin/domain/imag-calendar/src/ui.rs @@ -60,6 +60,13 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .subcommand(SubCommand::with_name("list") .about("List calendar entries") .version("0.1") + .arg(Arg::with_name("format") + .long("format") + .short("F") + .takes_value(true) + .required(false) + .multiple(false) + .help("Override the format used to list one event")) ) } diff --git a/bin/domain/imag-calendar/src/util.rs b/bin/domain/imag-calendar/src/util.rs index 6c142337..ab9f8eba 100644 --- a/bin/domain/imag-calendar/src/util.rs +++ b/bin/domain/imag-calendar/src/util.rs @@ -17,10 +17,18 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // +use std::collections::BTreeMap; + +use clap::ArgMatches; use vobject::icalendar::ICalendar; +use vobject::icalendar::Event; +use handlebars::Handlebars; use failure::Fallible as Result; use failure::Error; +use failure::err_msg; +use toml_query::read::TomlValueReadTypeExt; +use libimagrt::runtime::Runtime; use libimagstore::store::FileLockEntry; use libimagentryref::reference::fassade::RefFassade; use libimagentryref::reference::Ref; @@ -58,3 +66,54 @@ impl<'a> ParsedEventFLE<'a> { } } +pub fn get_event_print_format(config_value_path: &'static str, rt: &Runtime, scmd: &ArgMatches) + -> Result +{ + scmd.value_of("format") + .map(String::from) + .map(Ok) + .unwrap_or_else(|| { + rt.config() + .ok_or_else(|| err_msg("No configuration file"))? + .read_string(config_value_path)? + .ok_or_else(|| err_msg("Configuration 'contact.list_format' does not exist")) + }) + .and_then(|fmt| { + let mut hb = Handlebars::new(); + hb.register_template_string("format", fmt)?; + + hb.register_escape_fn(::handlebars::no_escape); + ::libimaginteraction::format::register_all_color_helpers(&mut hb); + ::libimaginteraction::format::register_all_format_helpers(&mut hb); + + Ok(hb) + }) +} + +pub fn build_data_object_for_handlebars<'a>(i: usize, event: &Event<'a>) + -> BTreeMap<&'static str, String> +{ + macro_rules! process_opt { + ($t:expr, $text:expr) => { + ($t).map(|obj| obj.into_raw()).unwrap_or_else(|| String::from($text)) + } + } + + let mut data = BTreeMap::new(); + + data.insert("i" , format!("{}", i)); + data.insert("dtend" , process_opt!(event.dtend() , "")); + data.insert("dtstart" , process_opt!(event.dtstart() , "")); + data.insert("dtstamp" , process_opt!(event.dtstamp() , "")); + data.insert("uid" , process_opt!(event.uid() , "")); + data.insert("description" , process_opt!(event.description() , "")); + data.insert("summary" , process_opt!(event.summary() , "")); + data.insert("url" , process_opt!(event.url() , "")); + data.insert("location" , process_opt!(event.location() , "")); + data.insert("class" , process_opt!(event.class() , "")); + data.insert("categories" , process_opt!(event.categories() , "")); + data.insert("transp" , process_opt!(event.transp() , "")); + data.insert("rrule" , process_opt!(event.rrule() , "")); + + data +} diff --git a/imagrc.toml b/imagrc.toml index 3b353803..0bd1c291 100644 --- a/imagrc.toml +++ b/imagrc.toml @@ -334,6 +334,21 @@ Email : {{EMAIL}} Address : {{ADR}} """ +[calendar] +list_format = "{{lpad 5 i}} | {{abbrev 5 uid}} | {{summary}} | {{location}}" +show_format = """ +{{i}} - {{uid}} + +Summary : {{summary}} +Start : {{dtstart}} +End : {{dtend}} +Url : {{url}} +Location : {{location}} + +{{description}} + +""" + [log] logs = ["default"] default = "default" From 92a0713ed0e33a2ca907495491acf881562f76eb Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 3 Oct 2019 14:36:50 +0200 Subject: [PATCH 05/12] Add filtering for past events Signed-off-by: Matthias Beyer --- bin/domain/imag-calendar/Cargo.toml | 1 + bin/domain/imag-calendar/src/filters.rs | 86 +++++++++++++++++++++++++ bin/domain/imag-calendar/src/main.rs | 13 +++- bin/domain/imag-calendar/src/ui.rs | 7 ++ 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 bin/domain/imag-calendar/src/filters.rs diff --git a/bin/domain/imag-calendar/Cargo.toml b/bin/domain/imag-calendar/Cargo.toml index 7801ac80..187468f3 100644 --- a/bin/domain/imag-calendar/Cargo.toml +++ b/bin/domain/imag-calendar/Cargo.toml @@ -26,6 +26,7 @@ failure = "0.1" walkdir = "2.2.8" vobject = "0.7" handlebars = "2" +chrono = "0.4" libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" } libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } diff --git a/bin/domain/imag-calendar/src/filters.rs b/bin/domain/imag-calendar/src/filters.rs new file mode 100644 index 00000000..aab19590 --- /dev/null +++ b/bin/domain/imag-calendar/src/filters.rs @@ -0,0 +1,86 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use chrono::NaiveDateTime; +use failure::Fallible as Result; +use vobject::icalendar::Event; + +/// Generate a filter function to filter shown events +/// +/// If `do_filter` is false, this filter returns true all the time. +/// +/// If an event is in the past, relative to the `today` parameter, the function returns false, +/// else it returns true. +/// +/// # Details +/// +/// The date of the event is determined by using the "dtend" or "dtstamp" members of the event +/// object. These fields are parsed to NaiveDateTime objects and then compared to the `today` object. +/// +/// If an parsing error happens in the "dtend" parsing step, "dtstamp" is used. If this results also +/// in a parsing error, the first error is returned. +/// +pub fn filter_past(do_filter: bool, today: NaiveDateTime) -> impl FnOnce(&Event) -> Result { + move |pe| if do_filter { + let uid = || pe.uid() + .map(|uid| uid.into_raw()) + .unwrap_or_else(|| String::from("")); + + let dtend_is_pre_today : Result = pe.dtend() + .map(|dtend| Ok(try_to_parse_datetime(dtend.raw())? < today)) + .unwrap_or_else(|| Err({ + format_err!("Entry with UID {} has no end time, cannot determine whether to list it", + uid()) + })); + + let dtstamp_is_pre_today : Result = pe.dtstamp() + .map(|dtstamp| Ok(try_to_parse_datetime(dtstamp.raw())? < today)) + .unwrap_or_else(|| Err({ + format_err!("Entry with UID {} has no timestamp, cannot determine whether to list it", + uid()) + })); + + trace!("dtend_is_pre_today = {:?}", dtend_is_pre_today); + trace!("dtstamp_is_pre_today = {:?}", dtstamp_is_pre_today); + + match (dtend_is_pre_today, dtstamp_is_pre_today) { + (Ok(b), _) => return Ok(!b), + (_, Ok(b)) => return Ok(!b), + (Err(e), _) => return Err(e) + } + } else { + Ok(true) + } +} + +fn try_to_parse_datetime(s: &str) -> Result { + const FORMATS : &[&'static str] = &[ + "%Y%m%dT%H%M%S", + "%Y%m%dT%H%M%SZ" + ]; + + for format in FORMATS { + if let Ok(parsed) = NaiveDateTime::parse_from_str(s, format) { + return Ok(parsed); + } + } + + Err(format_err!("Cannot parse datetime: {}", s)) +} + diff --git a/bin/domain/imag-calendar/src/main.rs b/bin/domain/imag-calendar/src/main.rs index a94e2ee2..379374df 100644 --- a/bin/domain/imag-calendar/src/main.rs +++ b/bin/domain/imag-calendar/src/main.rs @@ -40,6 +40,7 @@ extern crate clap; extern crate toml_query; extern crate walkdir; extern crate handlebars; +extern crate chrono; #[macro_use] extern crate libimagrt; extern crate libimagcalendar; @@ -58,6 +59,7 @@ use toml_query::read::Partial; use toml_query::read::TomlValueReadExt; use walkdir::DirEntry; use walkdir::WalkDir; +use vobject::icalendar::Event; use libimagcalendar::store::EventStore; use libimagerror::io::ToExitCode; @@ -67,6 +69,7 @@ use libimagerror::trace::MapErrTrace; use libimagrt::runtime::Runtime; use libimagrt::setup::generate_runtime_setup; +mod filters; mod ui; mod util; @@ -168,6 +171,8 @@ fn list(rt: &Runtime) { let list_format = get_event_print_format("calendar.list_format", rt, &scmd) .map_err_trace_exit_unwrap(); + let do_filter_past = !scmd.is_present("list-past"); + let ref_config = rt.config() .ok_or_else(|| format_err!("No configuration, cannot continue!")) .map_err_trace_exit_unwrap() @@ -181,7 +186,11 @@ fn list(rt: &Runtime) { debug!("List format: {:?}", list_format); debug!("Ref config : {:?}", ref_config); - let event_filter = |pefle: &ParsedEventFLE| true; // TODO: impl filtering + let event_filter = |e: &'_ Event| { // what a crazy hack to make the compiler happy + debug!("Filtering event: {:?}", e); + let f = filters::filter_past(do_filter_past, chrono::Local::now().naive_local()); + f(e) + }; let mut listed_events = 0; @@ -195,7 +204,6 @@ fn list(rt: &Runtime) { .trace_unwrap_exit() .map(|ev| ParsedEventFLE::parse(ev, &ref_config)) .trace_unwrap_exit() - .filter(|e| event_filter(e)) .for_each(|parsed_entry| { parsed_entry .get_data() @@ -223,4 +231,3 @@ fn is_not_hidden(entry: &DirEntry) -> bool { !entry.file_name().to_str().map(|s| s.starts_with(".")).unwrap_or(false) } - diff --git a/bin/domain/imag-calendar/src/ui.rs b/bin/domain/imag-calendar/src/ui.rs index 8386357e..9792ea9b 100644 --- a/bin/domain/imag-calendar/src/ui.rs +++ b/bin/domain/imag-calendar/src/ui.rs @@ -67,6 +67,13 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .required(false) .multiple(false) .help("Override the format used to list one event")) + + .arg(Arg::with_name("list-past") + .long("past") + .takes_value(false) + .required(false) + .multiple(false) + .help("List past events")) ) } From 3da1d9e1d93ac55fd534b4e6cfc4c3a56f87f225 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 3 Oct 2019 14:55:30 +0200 Subject: [PATCH 06/12] Refactor to use new helper function Signed-off-by: Matthias Beyer --- bin/domain/imag-calendar/src/filters.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/bin/domain/imag-calendar/src/filters.rs b/bin/domain/imag-calendar/src/filters.rs index aab19590..ad382f37 100644 --- a/bin/domain/imag-calendar/src/filters.rs +++ b/bin/domain/imag-calendar/src/filters.rs @@ -75,12 +75,7 @@ fn try_to_parse_datetime(s: &str) -> Result { "%Y%m%dT%H%M%SZ" ]; - for format in FORMATS { - if let Ok(parsed) = NaiveDateTime::parse_from_str(s, format) { - return Ok(parsed); - } - } - - Err(format_err!("Cannot parse datetime: {}", s)) + ::libimagutil::date::try_to_parse_datetime_from_string(s, FORMATS.iter()) + .ok_or_else(|| format_err!("Cannot parse datetime: {}", s)) } From 81c6ee83140e866bf4b72ecc1769ae8f2d5773f2 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 3 Oct 2019 15:51:45 +0200 Subject: [PATCH 07/12] Rewrite helper to be less complicated Signed-off-by: Matthias Beyer --- bin/domain/imag-calendar/src/filters.rs | 74 +++++++++++-------------- bin/domain/imag-calendar/src/main.rs | 14 ++++- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/bin/domain/imag-calendar/src/filters.rs b/bin/domain/imag-calendar/src/filters.rs index ad382f37..063936b9 100644 --- a/bin/domain/imag-calendar/src/filters.rs +++ b/bin/domain/imag-calendar/src/filters.rs @@ -20,52 +20,44 @@ use chrono::NaiveDateTime; use failure::Fallible as Result; use vobject::icalendar::Event; +use libimagerror::trace::MapErrTrace; -/// Generate a filter function to filter shown events -/// -/// If `do_filter` is false, this filter returns true all the time. -/// -/// If an event is in the past, relative to the `today` parameter, the function returns false, -/// else it returns true. -/// -/// # Details -/// -/// The date of the event is determined by using the "dtend" or "dtstamp" members of the event -/// object. These fields are parsed to NaiveDateTime objects and then compared to the `today` object. -/// -/// If an parsing error happens in the "dtend" parsing step, "dtstamp" is used. If this results also -/// in a parsing error, the first error is returned. -/// -pub fn filter_past(do_filter: bool, today: NaiveDateTime) -> impl FnOnce(&Event) -> Result { - move |pe| if do_filter { - let uid = || pe.uid() - .map(|uid| uid.into_raw()) - .unwrap_or_else(|| String::from("")); +pub fn event_is_before<'a>(event: &Event<'a>, before_spec: &NaiveDateTime) -> bool { + let uid = || event.uid() + .map(|uid| uid.into_raw()) + .unwrap_or_else(|| String::from("")); - let dtend_is_pre_today : Result = pe.dtend() - .map(|dtend| Ok(try_to_parse_datetime(dtend.raw())? < today)) - .unwrap_or_else(|| Err({ - format_err!("Entry with UID {} has no end time, cannot determine whether to list it", - uid()) - })); + let dtend_is_before_spec : Result = event.dtend() + .map(|dtend| { + let datetime = try_to_parse_datetime(dtend.raw())?; + let result = datetime < *before_spec; + trace!("{} < {} => {}", datetime, before_spec, result); + Ok(result) + }) + .unwrap_or_else(|| Err({ + format_err!("Entry with UID {} has no end time, cannot determine whether to list it", + uid()) + })); - let dtstamp_is_pre_today : Result = pe.dtstamp() - .map(|dtstamp| Ok(try_to_parse_datetime(dtstamp.raw())? < today)) - .unwrap_or_else(|| Err({ - format_err!("Entry with UID {} has no timestamp, cannot determine whether to list it", - uid()) - })); + let dtstamp_is_before_spec : Result = event.dtstamp() + .map(|dtstamp| { + let datetime = try_to_parse_datetime(dtstamp.raw())?; + let result = datetime < *before_spec; + trace!("{} < {} => {}", datetime, before_spec, result); + Ok(result) + }) + .unwrap_or_else(|| Err({ + format_err!("Entry with UID {} has no timestamp, cannot determine whether to list it", + uid()) + })); - trace!("dtend_is_pre_today = {:?}", dtend_is_pre_today); - trace!("dtstamp_is_pre_today = {:?}", dtstamp_is_pre_today); + trace!("dtend_is_before_spec = {:?}", dtend_is_before_spec); + trace!("dtstamp_is_before_spec = {:?}", dtstamp_is_before_spec); - match (dtend_is_pre_today, dtstamp_is_pre_today) { - (Ok(b), _) => return Ok(!b), - (_, Ok(b)) => return Ok(!b), - (Err(e), _) => return Err(e) - } - } else { - Ok(true) + match (dtend_is_before_spec, dtstamp_is_before_spec) { + (Ok(b), _) => return b, + (_, Ok(b)) => return b, + (Err(e), _) => return Err(e).map_err_trace_exit_unwrap() } } diff --git a/bin/domain/imag-calendar/src/main.rs b/bin/domain/imag-calendar/src/main.rs index 379374df..4039cde3 100644 --- a/bin/domain/imag-calendar/src/main.rs +++ b/bin/domain/imag-calendar/src/main.rs @@ -171,7 +171,7 @@ fn list(rt: &Runtime) { let list_format = get_event_print_format("calendar.list_format", rt, &scmd) .map_err_trace_exit_unwrap(); - let do_filter_past = !scmd.is_present("list-past"); + let do_filter_past = !scmd.is_present("list-past"); let ref_config = rt.config() .ok_or_else(|| format_err!("No configuration, cannot continue!")) @@ -185,11 +185,19 @@ fn list(rt: &Runtime) { debug!("List format: {:?}", list_format); debug!("Ref config : {:?}", ref_config); + let today = ::chrono::Local::now().naive_local(); let event_filter = |e: &'_ Event| { // what a crazy hack to make the compiler happy debug!("Filtering event: {:?}", e); - let f = filters::filter_past(do_filter_past, chrono::Local::now().naive_local()); - f(e) + + // generate a function `filter_past` which filters out the past or not + let allow_all_past_events = |event| if do_filter_past { + filters::event_is_before(event, &today) + } else { + true + }; + + allow_all_past_events(e) }; let mut listed_events = 0; From 9801a3c29550519caa6633eaefff6707ae34ea46 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 3 Oct 2019 15:52:16 +0200 Subject: [PATCH 08/12] Add helper function to parse string with kairos Signed-off-by: Matthias Beyer --- bin/domain/imag-calendar/Cargo.toml | 1 + bin/domain/imag-calendar/src/main.rs | 1 + bin/domain/imag-calendar/src/util.rs | 25 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/bin/domain/imag-calendar/Cargo.toml b/bin/domain/imag-calendar/Cargo.toml index 187468f3..192a7402 100644 --- a/bin/domain/imag-calendar/Cargo.toml +++ b/bin/domain/imag-calendar/Cargo.toml @@ -27,6 +27,7 @@ walkdir = "2.2.8" vobject = "0.7" handlebars = "2" chrono = "0.4" +kairos = "0.3" libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" } libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } diff --git a/bin/domain/imag-calendar/src/main.rs b/bin/domain/imag-calendar/src/main.rs index 4039cde3..82d091cf 100644 --- a/bin/domain/imag-calendar/src/main.rs +++ b/bin/domain/imag-calendar/src/main.rs @@ -41,6 +41,7 @@ extern crate toml_query; extern crate walkdir; extern crate handlebars; extern crate chrono; +extern crate kairos; #[macro_use] extern crate libimagrt; extern crate libimagcalendar; diff --git a/bin/domain/imag-calendar/src/util.rs b/bin/domain/imag-calendar/src/util.rs index ab9f8eba..3908b552 100644 --- a/bin/domain/imag-calendar/src/util.rs +++ b/bin/domain/imag-calendar/src/util.rs @@ -27,6 +27,7 @@ use failure::Fallible as Result; use failure::Error; use failure::err_msg; use toml_query::read::TomlValueReadTypeExt; +use chrono::NaiveDateTime; use libimagrt::runtime::Runtime; use libimagstore::store::FileLockEntry; @@ -34,6 +35,7 @@ use libimagentryref::reference::fassade::RefFassade; use libimagentryref::reference::Ref; use libimagentryref::reference::Config; use libimagentryref::hasher::default::DefaultHasher; +use libimagerror::trace::MapErrTrace; pub struct ParsedEventFLE<'a> { inner: FileLockEntry<'a>, @@ -117,3 +119,26 @@ pub fn build_data_object_for_handlebars<'a>(i: usize, event: &Event<'a>) data } + +pub fn kairos_parse(spec: &str) -> Result { + match ::kairos::parser::parse(spec).map_err_trace_exit_unwrap() { + ::kairos::parser::Parsed::Iterator(_) => { + trace!("before-filter spec resulted in iterator"); + Err(format_err!("Not a moment in time: {}", spec)) + } + + ::kairos::parser::Parsed::TimeType(tt) => { + trace!("before-filter spec resulted in timetype"); + let tt = tt.calculate() + .map_err_trace_exit_unwrap() + .get_moment().unwrap_or_else(|| { + error!("Not a moment in time: {}", spec); + ::std::process::exit(1); + }) + .clone(); + + trace!("Before filter spec {} => {}", spec, tt); + Ok(tt) + } + } +} From d9ac0c0b077b99a025b17cb2c9b86fefa02e81ff Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 3 Oct 2019 15:54:17 +0200 Subject: [PATCH 09/12] Add argument to only list events before a certain date Signed-off-by: Matthias Beyer --- bin/domain/imag-calendar/src/main.rs | 9 ++++++++- bin/domain/imag-calendar/src/ui.rs | 7 +++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/bin/domain/imag-calendar/src/main.rs b/bin/domain/imag-calendar/src/main.rs index 82d091cf..3d8f74d0 100644 --- a/bin/domain/imag-calendar/src/main.rs +++ b/bin/domain/imag-calendar/src/main.rs @@ -173,6 +173,7 @@ fn list(rt: &Runtime) { .map_err_trace_exit_unwrap(); let do_filter_past = !scmd.is_present("list-past"); + let do_filter_before = scmd.value_of("list-before"); let ref_config = rt.config() .ok_or_else(|| format_err!("No configuration, cannot continue!")) @@ -198,7 +199,13 @@ fn list(rt: &Runtime) { true }; - allow_all_past_events(e) + let do_filter_before = do_filter_before.map(|spec| kairos_parse(spec).map_err_trace_exit_unwrap()); + + let allow_events_before_date = |event| do_filter_before.as_ref().map(|spec| { + filters::event_is_before(event, spec) + }).unwrap_or(true); + + allow_all_past_events(e) && allow_events_before_date(e) }; let mut listed_events = 0; diff --git a/bin/domain/imag-calendar/src/ui.rs b/bin/domain/imag-calendar/src/ui.rs index 9792ea9b..4c1c1898 100644 --- a/bin/domain/imag-calendar/src/ui.rs +++ b/bin/domain/imag-calendar/src/ui.rs @@ -74,6 +74,13 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .required(false) .multiple(false) .help("List past events")) + + .arg(Arg::with_name("list-before") + .long("before") + .takes_value(true) + .required(false) + .multiple(false) + .help("List events which are dated before certain date")) ) } From d201797b986dc8d0e92c5a3cd61e2a61294bb608 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 3 Oct 2019 15:57:38 +0200 Subject: [PATCH 10/12] Add argument to only list events after a certain date Signed-off-by: Matthias Beyer --- bin/domain/imag-calendar/src/filters.rs | 4 ++++ bin/domain/imag-calendar/src/main.rs | 10 +++++++++- bin/domain/imag-calendar/src/ui.rs | 7 +++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/bin/domain/imag-calendar/src/filters.rs b/bin/domain/imag-calendar/src/filters.rs index 063936b9..20743671 100644 --- a/bin/domain/imag-calendar/src/filters.rs +++ b/bin/domain/imag-calendar/src/filters.rs @@ -61,6 +61,10 @@ pub fn event_is_before<'a>(event: &Event<'a>, before_spec: &NaiveDateTime) -> bo } } +pub fn event_is_after<'a>(event: &Event<'a>, after_spec: &NaiveDateTime) -> bool { + !event_is_before(event, after_spec) +} + fn try_to_parse_datetime(s: &str) -> Result { const FORMATS : &[&'static str] = &[ "%Y%m%dT%H%M%S", diff --git a/bin/domain/imag-calendar/src/main.rs b/bin/domain/imag-calendar/src/main.rs index 3d8f74d0..21258d17 100644 --- a/bin/domain/imag-calendar/src/main.rs +++ b/bin/domain/imag-calendar/src/main.rs @@ -174,6 +174,7 @@ fn list(rt: &Runtime) { let do_filter_past = !scmd.is_present("list-past"); let do_filter_before = scmd.value_of("list-before"); + let do_filter_after = scmd.value_of("list-after"); let ref_config = rt.config() .ok_or_else(|| format_err!("No configuration, cannot continue!")) @@ -205,7 +206,14 @@ fn list(rt: &Runtime) { filters::event_is_before(event, spec) }).unwrap_or(true); - allow_all_past_events(e) && allow_events_before_date(e) + + let do_filter_after = do_filter_after.map(|spec| kairos_parse(spec).map_err_trace_exit_unwrap()); + + let allow_events_after_date = |event| do_filter_after.as_ref().map(|spec| { + filters::event_is_after(event, spec) + }).unwrap_or(true); + + allow_all_past_events(e) && allow_events_before_date(e) && allow_events_after_date(e) }; let mut listed_events = 0; diff --git a/bin/domain/imag-calendar/src/ui.rs b/bin/domain/imag-calendar/src/ui.rs index 4c1c1898..76a565ec 100644 --- a/bin/domain/imag-calendar/src/ui.rs +++ b/bin/domain/imag-calendar/src/ui.rs @@ -81,6 +81,13 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .required(false) .multiple(false) .help("List events which are dated before certain date")) + + .arg(Arg::with_name("list-after") + .long("after") + .takes_value(true) + .required(false) + .multiple(false) + .help("List events which are dated after certain date")) ) } From d40bf2956fd53032212a0df851107cc99292c918 Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 3 Oct 2019 19:33:43 +0200 Subject: [PATCH 11/12] Make helper type derive Debug Signed-off-by: Matthias Beyer --- bin/domain/imag-calendar/src/util.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/domain/imag-calendar/src/util.rs b/bin/domain/imag-calendar/src/util.rs index 3908b552..f0145de2 100644 --- a/bin/domain/imag-calendar/src/util.rs +++ b/bin/domain/imag-calendar/src/util.rs @@ -37,6 +37,7 @@ use libimagentryref::reference::Config; use libimagentryref::hasher::default::DefaultHasher; use libimagerror::trace::MapErrTrace; +#[derive(Debug)] pub struct ParsedEventFLE<'a> { inner: FileLockEntry<'a>, data: ICalendar, From f1ec71431e50c796d7c73ad9250b3a1a658c551d Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Thu, 3 Oct 2019 19:33:56 +0200 Subject: [PATCH 12/12] Implement show subcommand Signed-off-by: Matthias Beyer --- bin/domain/imag-calendar/src/main.rs | 52 ++++++++++++++++++++++++++++ bin/domain/imag-calendar/src/ui.rs | 19 ++++++++++ bin/domain/imag-calendar/src/util.rs | 45 ++++++++++++++++++++++++ 3 files changed, 116 insertions(+) diff --git a/bin/domain/imag-calendar/src/main.rs b/bin/domain/imag-calendar/src/main.rs index 21258d17..c87d77d0 100644 --- a/bin/domain/imag-calendar/src/main.rs +++ b/bin/domain/imag-calendar/src/main.rs @@ -87,6 +87,7 @@ fn main() { match name { "import" => import(&rt), "list" => list(&rt), + "show" => show(&rt), other => { warn!("Right now, only the 'import' command is available"); debug!("Unknown command"); @@ -250,6 +251,57 @@ fn list(rt: &Runtime) { }); } +fn show(rt: &Runtime) { + let scmd = rt.cli().subcommand_matches("show").unwrap(); // safe by clap + let ref_config = rt.config() + .ok_or_else(|| format_err!("No configuration, cannot continue!")) + .map_err_trace_exit_unwrap() + .read_partial::() + .map_err(Error::from) + .map_err_trace_exit_unwrap() + .ok_or_else(|| format_err!("Configuration missing: {}", libimagentryref::reference::Config::LOCATION)) + .map_err_trace_exit_unwrap(); + + let list_format = util::get_event_print_format("calendar.show_format", rt, &scmd) + .map_err_trace_exit_unwrap(); + + let mut shown_events = 0; + + scmd.values_of("show-ids") + .unwrap() // safe by clap + .into_iter() + .filter_map(|id| { + util::find_event_by_id(rt.store(), id, &ref_config) + .map(|entry| { debug!("Found => {:?}", entry); entry }) + .map_err_trace_exit_unwrap() + .map(|parsed| (parsed, id)) + }) + .for_each(|(parsed_entry, id)| { + parsed_entry + .get_data() + .events() + .filter_map(RResult::ok) + .filter(|pent| { + let relevant = pent.uid().map(|uid| uid.raw().starts_with(id)).unwrap_or(false); + debug!("Relevant {} => {}", parsed_entry.get_entry().get_location(), relevant); + relevant + }) + .for_each(|event| { + shown_events = shown_events + 1; + let data = util::build_data_object_for_handlebars(shown_events, &event); + + let rendered = list_format + .render("format", &data) + .map_err(Error::from) + .map_err_trace_exit_unwrap(); + + writeln!(rt.stdout(), "{}", rendered).to_exit_code().unwrap_or_exit() + }); + + rt.report_touched(parsed_entry.get_entry().get_location()).unwrap_or_exit(); + }); +} + /// helper function to check whether a DirEntry points to something hidden (starting with dot) fn is_not_hidden(entry: &DirEntry) -> bool { !entry.file_name().to_str().map(|s| s.starts_with(".")).unwrap_or(false) diff --git a/bin/domain/imag-calendar/src/ui.rs b/bin/domain/imag-calendar/src/ui.rs index 76a565ec..f62680ba 100644 --- a/bin/domain/imag-calendar/src/ui.rs +++ b/bin/domain/imag-calendar/src/ui.rs @@ -89,6 +89,25 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> { .multiple(false) .help("List events which are dated after certain date")) ) + + .subcommand(SubCommand::with_name("show") + .about("Show one or several calendar entries") + .version("0.1") + .arg(Arg::with_name("show-ids") + .index(1) + .takes_value(true) + .required(true) + .multiple(true) + .help("UIDs of Calendar entries to show")) + + .arg(Arg::with_name("format") + .long("format") + .short("F") + .takes_value(true) + .required(false) + .multiple(false) + .help("Override the format used to show events")) + ) } fn import_validator>(s: A) -> Result<(), String> { diff --git a/bin/domain/imag-calendar/src/util.rs b/bin/domain/imag-calendar/src/util.rs index f0145de2..e51b0a04 100644 --- a/bin/domain/imag-calendar/src/util.rs +++ b/bin/domain/imag-calendar/src/util.rs @@ -31,11 +31,13 @@ use chrono::NaiveDateTime; use libimagrt::runtime::Runtime; use libimagstore::store::FileLockEntry; +use libimagstore::store::Store; use libimagentryref::reference::fassade::RefFassade; use libimagentryref::reference::Ref; use libimagentryref::reference::Config; use libimagentryref::hasher::default::DefaultHasher; use libimagerror::trace::MapErrTrace; +use crate::libimagcalendar::store::EventStore; #[derive(Debug)] pub struct ParsedEventFLE<'a> { @@ -143,3 +145,46 @@ pub fn kairos_parse(spec: &str) -> Result { } } } + +pub fn find_event_by_id<'a>(store: &'a Store, id: &str, refconfig: &Config) -> Result>> { + if let Some(entry) = store.get_event_by_uid(id)? { + debug!("Found directly: {} -> {}", id, entry.get_location()); + return ParsedEventFLE::parse(entry, refconfig).map(Some) + } + + for sid in store.all_events()? { + let sid = sid?; + + let event = store.get(sid.clone())?.ok_or_else(|| { + format_err!("Cannot get {}, which should be there.", sid) + })?; + + trace!("Checking whether {} is represented by {}", id, event.get_location()); + let parsed = ParsedEventFLE::parse(event, refconfig)?; + + if parsed + .get_data() + .events() + .filter_map(|event| if event + .as_ref() + .map(|e| { + trace!("Checking whether {:?} starts with {}", e.uid(), id); + e.uid().map(|uid| uid.raw().starts_with(id)).unwrap_or(false) + }) + .unwrap_or(false) + { + trace!("Seems to be relevant"); + Some(event) + } else { + None + }) + .next() + .is_some() + { + return Ok(Some(parsed)) + } + } + + Ok(None) +} +