From 9f5dd9dad6b13476bab2c6eb3c6528f8ad49311a Mon Sep 17 00:00:00 2001 From: Till Höppner Date: Thu, 25 Feb 2016 06:48:03 +0100 Subject: Refactor... everything. --- base/Cargo.toml | 9 +++ base/src/context.rs | 9 +++ base/src/error.rs | 54 +++++++++++++++ base/src/event.rs | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++ base/src/format.rs | 18 +++++ base/src/lib.rs | 49 ++++++++++++++ base/src/mod.rs | 47 +++++++++++++ 7 files changed, 374 insertions(+) create mode 100644 base/Cargo.toml create mode 100644 base/src/context.rs create mode 100644 base/src/error.rs create mode 100644 base/src/event.rs create mode 100644 base/src/format.rs create mode 100644 base/src/lib.rs create mode 100644 base/src/mod.rs (limited to 'base') diff --git a/base/Cargo.toml b/base/Cargo.toml new file mode 100644 index 0000000..539990b --- /dev/null +++ b/base/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "ilc-base" +version = "0.1.0" +authors = ["Till Höppner "] + +[dependencies] +log = "0.3.5" +chrono = "0.2.19" +rustc-serialize = "0.3.18" diff --git a/base/src/context.rs b/base/src/context.rs new file mode 100644 index 0000000..4393457 --- /dev/null +++ b/base/src/context.rs @@ -0,0 +1,9 @@ + +use chrono::naive::date::NaiveDate; +use chrono::offset::fixed::FixedOffset; + +pub struct Context { + pub timezone: FixedOffset, + pub override_date: Option, + pub channel: Option, +} diff --git a/base/src/error.rs b/base/src/error.rs new file mode 100644 index 0000000..2806c78 --- /dev/null +++ b/base/src/error.rs @@ -0,0 +1,54 @@ +use std::{error, fmt, io, result}; +use std::error::Error as E; + +use chrono::format::ParseError; + +pub type Result = result::Result; + +#[derive(Debug)] +pub enum Error { + Parse(String), + Chrono(ParseError), + Io(io::Error), + Custom(Box), +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str(self.description()) + } +} + +impl error::Error for Error { + fn description(&self) -> &str { + use self::Error::*; + match self { + &Parse(_) => "error while parsing", + &Chrono(_) => "error while parsing time strings", + &Io(_) => "error during input/output", + &Custom(ref e) => e.description(), + } + } + + fn cause(&self) -> Option<&error::Error> { + use self::Error::*; + match self { + &Parse(ref _e) => None, + &Chrono(ref e) => Some(e), + &Io(ref e) => Some(e), + &Custom(ref e) => e.cause(), + } + } +} + +impl From for Error { + fn from(err: ParseError) -> Error { + Error::Chrono(err) + } +} + +impl From for Error { + fn from(err: io::Error) -> Error { + Error::Io(err) + } +} diff --git a/base/src/event.rs b/base/src/event.rs new file mode 100644 index 0000000..e357800 --- /dev/null +++ b/base/src/event.rs @@ -0,0 +1,188 @@ +// Copyright 2015 Till Höppner +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Common structures to represent the actual log data in memory. +//! These will be used by all formats for encoding and decoding. + +use std::borrow::Cow; +use std::cmp::Ordering; + +use chrono::naive::time::NaiveTime; +use chrono::offset::fixed::FixedOffset; +use chrono::offset::local::Local; +use chrono::offset::TimeZone; + +/// A whole log, in memory. This structure does not specify its +/// use. It may represent a private query, or the log of a channel. +pub struct Log<'a> { + pub entries: Vec>, +} + +/// Different log formats carry different amounts of information. Some might +/// hold enough information to calculate precise timestamps, others might +/// only suffice for the time of day. +#[derive(Clone, Debug, PartialEq, Eq, Ord, Hash, RustcEncodable, RustcDecodable)] +pub enum Time { + Unknown, + Hms(u8, u8, u8), + Timestamp(i64), +} + +impl Time { + pub fn from_format(tz: &FixedOffset, s: &str, f: &str) -> Time { + tz.datetime_from_str(s, f) + .map(|d| d.timestamp()) + .map(Time::Timestamp) + .unwrap_or(Time::Unknown) + } + + pub fn with_format(&self, tz: &FixedOffset, f: &str) -> String { + match self { + &Time::Unknown => panic!("Time data for this event is not present"), + &Time::Hms(h, m, s) => { + format!("{}", + NaiveTime::from_hms(h as u32, m as u32, s as u32).format(f)) + } + &Time::Timestamp(t) => format!("{}", tz.timestamp(t, 0).format(f)), + } + } + + pub fn as_timestamp(&self) -> i64 { + use self::Time::*; + match self { + &Unknown => 0, + &Hms(h, m, s) => { + Local::today() + .and_hms(h as u32, m as u32, s as u32) + .timestamp() + } + &Timestamp(i) => i, + } + } + + pub fn to_timestamp(&self) -> Time { + Time::Timestamp(self.as_timestamp()) + } +} + +impl PartialOrd for Time { + fn partial_cmp(&self, other: &Time) -> Option { + use self::Time::*; + match (self, other) { + (&Unknown, _) | (_, &Unknown) => None, + (&Hms(a_h, a_m, a_s), &Hms(b_h, b_m, b_s)) => { + if (a_h >= b_h && a_m >= b_m && a_s > b_s) || + (a_h >= b_h && a_m > b_m && a_s >= b_s) || + (a_h > b_h && a_m >= b_m && a_s >= b_s) { + Some(Ordering::Greater) + } else { + Some(Ordering::Less) + } + } + (&Timestamp(a), &Timestamp(b)) => Some(a.cmp(&b)), + _ => unimplemented!(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)] +pub struct Event<'a> { + pub ty: Type<'a>, + pub time: Time, + pub channel: Option>, +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)] +pub struct User<'a> { + nicks: Cow<'a, str>, +} + +/// All representable events, such as messages, quits, joins +/// and topic changes. +#[derive(Clone, Debug, Hash, PartialEq, Eq, RustcEncodable, RustcDecodable)] +pub enum Type<'a> { + Connect, + Disconnect, + Msg { + from: Cow<'a, str>, + content: Cow<'a, str>, + }, + Action { + from: Cow<'a, str>, + content: Cow<'a, str>, + }, + Join { + nick: Cow<'a, str>, + mask: Option>, + }, + Part { + nick: Cow<'a, str>, + mask: Option>, + reason: Option>, + }, + Quit { + nick: Cow<'a, str>, + mask: Option>, + reason: Option>, + }, + Nick { + old_nick: Cow<'a, str>, + new_nick: Cow<'a, str>, + }, + Notice { + from: Cow<'a, str>, + content: Cow<'a, str>, + }, + Kick { + kicked_nick: Cow<'a, str>, + kicking_nick: Option>, + kick_message: Option>, + }, + Topic { + topic: Cow<'a, str>, + }, + TopicChange { + nick: Option>, + new_topic: Cow<'a, str>, + }, + Mode { + nick: Option>, + mode: Cow<'a, str>, + masks: Cow<'a, str>, + }, +} + +impl<'a> Type<'a> { + pub fn involves(&self, needle: &str) -> bool { + use self::Type::*; + match self { + &Msg { ref from, .. } => from == needle, + &Action { ref from, .. } => from == needle, + &Join { ref nick, .. } => nick == needle, + &Part { ref nick, .. } => nick == needle, + &Quit { ref nick, .. } => nick == needle, + &Nick { ref old_nick, ref new_nick, .. } => old_nick == needle || new_nick == needle, + &Notice { ref from, .. } => from == needle, + &Kick { ref kicked_nick, ref kicking_nick, .. } => { + *kicked_nick == Cow::Borrowed(needle) || + kicking_nick.as_ref().map_or(false, |k| k.as_ref() == Cow::Borrowed(needle)) + } + &TopicChange { ref nick, .. } => nick.as_ref().map_or(false, |k| k.as_ref() == needle), + &Mode { ref nick, .. } => { + nick.as_ref().map_or(false, |k| k.as_ref() == Cow::Borrowed(needle)) + } + _ => false, + } + } +} diff --git a/base/src/format.rs b/base/src/format.rs new file mode 100644 index 0000000..46b2da6 --- /dev/null +++ b/base/src/format.rs @@ -0,0 +1,18 @@ +use std::borrow::Cow; + +pub fn rejoin(s: &[&str], splits: &[char]) -> Cow<'static, str> { + let len = s.iter().map(|s| s.len()).fold(0, |a, b| a + b); + let mut out = s.iter() + .zip(splits.iter()) + .fold(String::with_capacity(len), |mut s, (b, &split)| { + s.push_str(b); + s.push(split); + s + }); + out.pop(); + Cow::Owned(out) +} + +pub fn strip_one(s: &str) -> String { + if s.len() >= 2 { s[1..(s.len() - 1)].to_owned() } else { String::new() } +} diff --git a/base/src/lib.rs b/base/src/lib.rs new file mode 100644 index 0000000..9e96478 --- /dev/null +++ b/base/src/lib.rs @@ -0,0 +1,49 @@ +// Copyright 2015 Till Höppner +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![feature(slice_patterns)] +extern crate chrono; +#[macro_use] +extern crate log; +extern crate rustc_serialize; +// extern crate bincode; +// extern crate rmp; +// extern crate rmp_serialize as msgpack; + +pub mod event; +// pub mod format; +pub mod context; +pub mod error; +pub mod format; + +use std::io::{BufRead, Write}; + +pub use context::Context; +pub use event::Event; +pub use error::*; + +pub trait Encode { + fn encode<'a>(&'a self, + context: &'a Context, + output: &'a mut Write, + event: &'a Event) + -> error::Result<()>; +} + +pub trait Decode { + fn decode<'a>(&'a mut self, + context: &'a Context, + input: &'a mut BufRead) + -> Box>> + 'a>; +} diff --git a/base/src/mod.rs b/base/src/mod.rs new file mode 100644 index 0000000..5374598 --- /dev/null +++ b/base/src/mod.rs @@ -0,0 +1,47 @@ +// Copyright 2015 Till Höppner +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits and structs for conversion between various formats. +//! As the source format may not provide the same information as the +//! target format, all formats must allow for omittable information. + +use std::iter; +use std::io::{BufRead, Write}; +use std::borrow::Cow; + +use event::Event; +use context::Context; + +pub use self::energymech::Energymech; +pub use self::weechat::Weechat; +pub use self::binary::Binary; +pub use self::msgpack::Msgpack; + +mod energymech; +mod weechat; +// pub mod irssi; +mod binary; +mod msgpack; + + +pub struct Dummy; + +impl Decode for Dummy { + fn decode<'a>(&'a mut self, + _context: &'a Context, + _input: &'a mut BufRead) + -> Box>> + 'a> { + Box::new(iter::empty()) + } +} -- cgit v1.2.3