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 + } +} +