Merge pull request #309 from matthiasbeyer/libimagdiary/init

Libimagdiary/init
This commit is contained in:
Matthias Beyer 2016-06-09 15:40:58 +02:00
commit c099fc7270
10 changed files with 636 additions and 0 deletions

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

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

@ -0,0 +1,108 @@
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() {
continue;
}
let id = id.unwrap();
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 {
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;
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>;