aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTill Höppner2016-02-23 17:00:53 +0100
committerTill Höppner2016-02-24 18:21:24 +0100
commite866dceae987acd51d43bd457351bd2188c5f95a (patch)
tree2cc5a67492bdf00ff68041656a1e870b2b4de17f
parent815f31f5cef61709c50087c9f7601ea330929bb7 (diff)
downloadilc-e866dceae987acd51d43bd457351bd2188c5f95a.tar.gz
ilc-e866dceae987acd51d43bd457351bd2188c5f95a.tar.xz
ilc-e866dceae987acd51d43bd457351bd2188c5f95a.zip
Test CI
-rw-r--r--.travis.yml93
-rw-r--r--Cargo.toml1
-rw-r--r--appveyor.yml65
-rw-r--r--ci/before_deploy.sh15
-rw-r--r--ci/install.sh43
-rw-r--r--ci/script.sh36
-rw-r--r--rustfmt.toml3
-rw-r--r--src/ageset.rs16
-rw-r--r--src/app/freq.rs51
-rw-r--r--src/app/mod.rs138
-rw-r--r--src/chain.rs36
-rw-r--r--src/context.rs2
-rw-r--r--src/event.rs73
-rw-r--r--src/format/binary.rs25
-rw-r--r--src/format/energymech.rs299
-rw-r--r--src/format/irssi.rs205
-rw-r--r--src/format/mod.rs38
-rw-r--r--src/format/msgpack.rs26
-rw-r--r--src/format/weechat.rs265
-rw-r--r--src/lambda.rs18
-rw-r--r--src/lazy.rs74
-rw-r--r--src/lib.rs20
-rw-r--r--src/main.rs155
23 files changed, 1221 insertions, 476 deletions
diff --git a/.travis.yml b/.travis.yml
index 7f1b897..5c8cff9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,9 +1,94 @@
+sudo: false
+
language: rust
-rust:
- - stable
- - beta
- - nightly
+
+rust: nightly
+
+os:
+ - linux
+ - osx
+
+env:
+ global:
+ # This will be part of the release tarball
+ - PROJECT_NAME=ilc
+
matrix:
allow_failures:
- rust: stable
- rust: beta
+
+ # Clear the whole matrix
+ exclude:
+ - os: linux
+ - os: osx
+ # Add each target manually
+ # pattern shown below
+ include:
+ # WARNING Experimental target. Tests are executed using qemu user emulation, but this approach
+ # may have problems when too many threads are spawned. Also, by the next Rust stable release,
+ # this target will be replaced by `armv7-unknown-linux-gnueabihf`.
+ - os: linux
+ env: TARGET=arm-unknown-linux-gnueabihf
+ # Extra packages only for this build job
+ addons:
+ apt:
+ packages:
+ # Cross compiler and cross compiled C libraries
+ - gcc-arm-linux-gnueabihf
+ - libc6-armhf-cross
+ - libc6-dev-armhf-cross
+ # Emulator
+ - qemu-user
+ - os: linux
+ env: TARGET=i686-unknown-linux-gnu
+ addons:
+ apt:
+ packages:
+ # Cross compiler and cross compiled C libraries
+ - gcc-multilib
+ - os: linux
+ env: TARGET=x86_64-unknown-linux-gnu
+ - os: linux
+ env: TARGET=x86_64-unknown-linux-musl
+ - os: osx
+ env: TARGET=i686-apple-darwin
+ - os: osx
+ env: TARGET=x86_64-apple-darwin
+
+install:
+ - sh ci/install.sh
+
+script:
+ - sh ci/script.sh
+
+before_deploy:
+ - sh ci/before_deploy.sh
+
+deploy:
+ provider: releases
+ # - Go to 'https://github.com/settings/tokens/new' and generate a Token with only the
+ # `public_repo` scope enabled
+ # - Call `travis encrypt $github_token` where $github_token is the token you got in the previous
+ # step and `travis` is the official Travis CI gem (see https://rubygems.org/gems/travis/)
+ # - Enter the "encrypted value" below
+ api_key:
+ secure: BnQxKEtnxUL6K8T8WHsmC09KgTZho22z5MBlVEcX+glHMLkzehCs+LaiXQ0lsvs8Z7ngxEs+FG4qfCXtMeFJh3n4tzTYAe9xvNFhieRZKUKkkycNTTraRL1Pzpj7i+dT3/OG+vSMhgwa+I6gZwuPVHkcY1EU8fdzI95R7SLJJREM/yAE5/seYZNxA0TX4BiZIZksg+bzwSr1WJEsX0N9rv3ANqkemjMvHkeYQb6dogo8iIIBG03L/OKvuHELsdVdiyiIdq2YU62x3wPQc2w/StDkrk+dq5eMW9H7Gh0MqDGF4ZKkWlQzrxPxJnJBbWRPcKczRgGMXeIXKfBU52Pn4L92dRC7RpJmKOVZwDgyeqNLXvHYiMpL0NH3DU3V2LFpVNHJtK3f6hsVPBS0w00kpg7iQhN5EejHe7GlD6SF41J2W53XsN1+5qmNew+El+Ugnk7Jp4GFDPpYAR5u9FW7GAChGTyFjBkAOYGlnl9ZrtMC53+pPuDtGwywszf7+MXpB2HmSWy/eC1tnEE9tZt9rAVO0BTM1gCPkvDNA0czvA76gwNphEG7QCADI89WiGiVDruOGrMpF7Yi6NavmfQUwflMI2vZ+rqeBhVocK9pYLd/lg3yXyue//EejW+BE42R+IbB3OfFGgpK1+oHnbWr8UobwD3sa3iKJo232wPKozU=
+ file: ${PROJECT_NAME}-${TRAVIS_TAG}-${TARGET}.tar.gz
+ # don't delete the artifacts from previous phases
+ skip_cleanup: true
+ # deploy when a new tag is pushed
+ on:
+ tags: true
+
+branches:
+ only:
+ # Pushes and PR to the master branch
+ - master
+ # IMPORTANT Ruby regex to match tags. Required, or travis won't trigger deploys when a new tag
+ # is pushed. This regex matches semantic versions like v1.2.3-rc4+2016.02.22
+ - /^v\d+\.\d+\.\d+.*$/
+
+notifications:
+ email:
+ on_success: never
diff --git a/Cargo.toml b/Cargo.toml
index 38b9094..38e294b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,7 @@ doc = false
name = "ilc"
[dependencies]
+tendril = "0.2.1"
bincode = "0.4.0"
blist = "0.0.4"
chrono = "0.2.18"
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..2370cac
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,65 @@
+environment:
+ global:
+ # This will be used as part of the zipfile name
+ PROJECT_NAME: ilc
+ RUST_VERSION: nightly
+ matrix:
+ - TARGET: i686-pc-windows-gnu
+ MSYS2_BITS: 32
+ - TARGET: i686-pc-windows-msvc
+ - TARGET: x86_64-pc-windows-gnu
+ MSYS2_BITS: 64
+ - TARGET: x86_64-pc-windows-msvc
+
+# Install Rust and Cargo
+# (Shamelessly stolen from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
+install:
+ - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:RUST_VERSION}-${env:TARGET}.exe"
+ - rust-%RUST_VERSION%-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
+ - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
+ - if defined MSYS2_BITS set PATH=%PATH%;C:\msys64\mingw%MSYS2_BITS%\bin
+ - rustc -V
+ - cargo -V
+
+# ???
+build: false
+
+# Equivalent to Travis' `script` phase
+test_script:
+ - cargo build --verbose
+ - cargo run
+ - cargo test
+ - cargo build --release
+
+# Equivalent to `before_deploy` phase
+after_test:
+ - mkdir staging
+ - copy target\release\ilc.exe staging
+ - cd staging
+ # release zipfile will look like 'rust-everywhere-v1.2.3-x86_64-pc-windows-msvc'
+ - 7z a ../%PROJECT_NAME%-%APPVEYOR_REPO_TAG_NAME%-%TARGET%.zip *
+
+# IMPORTANT All the artifacts need to be listed here, or they won't be uploaded to GitHub
+artifacts:
+ - path: $(PROJECT_NAME)-$(APPVEYOR_REPO_TAG_NAME)-$(TARGET).zip
+ name: $(PROJECT_NAME)-$(APPVEYOR_REPO_TAG_NAME)-$(TARGET).zip
+ type: zip
+
+deploy:
+ description: 'Windows release'
+ # All the zipped artifacts will be deployed
+ artifact: /.*\.zip/
+ # - Go to 'https://github.com/settings/tokens/new' and generate a Token with only the
+ # `public_repo` scope enabled
+ # - Then go to 'https://ci.appveyor.com/tools/encrypt' and enter the newly generated token.
+ # - Enter the "encrypted value" below
+ auth_token:
+ secure: qv5P4J0gzS1fd4JpHwLCuFF5Ay8uszKJYocTsDf+r3Sr8alloL6PznLb0C5Y909h
+ provider: GitHub
+ # deploy when a new tag is pushed
+ on:
+ appveyor_repo_tag: true
+
+branches:
+ only:
+ - master
diff --git a/ci/before_deploy.sh b/ci/before_deploy.sh
new file mode 100644
index 0000000..b8f67d4
--- /dev/null
+++ b/ci/before_deploy.sh
@@ -0,0 +1,15 @@
+# `before_deploy` phase: here we package the build artifacts
+
+set -ex
+
+# create a "staging" directory
+mkdir staging
+
+# TODO update this part to copy the artifacts that make sense for your project
+# NOTE All Cargo build artifacts will be under the 'target/$TARGET/{debug,release}'
+cp target/$TARGET/release/ilc* staging
+
+cd staging
+
+# release tarball will look like 'rust-everywhere-v1.2.3-x86_64-unknown-linux-gnu.tar.gz'
+tar czf ../${PROJECT_NAME}-${TRAVIS_TAG}-${TARGET}.tar.gz *
diff --git a/ci/install.sh b/ci/install.sh
new file mode 100644
index 0000000..81ff38c
--- /dev/null
+++ b/ci/install.sh
@@ -0,0 +1,43 @@
+# `install` phase: install stuff needed for the `script` phase
+
+set -ex
+
+case $TARGET in
+ # Install standard libraries needed for cross compilation
+ arm-unknown-linux-gnueabihf | \
+ i686-apple-darwin | \
+ i686-unknown-linux-gnu | \
+ x86_64-unknown-linux-musl)
+ if [ "$TARGET" = "arm-unknown-linux-gnueabihf" ]; then
+ # information about the cross compiler
+ arm-linux-gnueabihf-gcc -v
+
+ # tell cargo which linker to use for cross compilation
+ mkdir -p .cargo
+ cat >.cargo/config <<EOF
+[target.$TARGET]
+linker = "arm-linux-gnueabihf-gcc"
+EOF
+ fi
+
+ # e.g. 1.6.0
+ # doesn't work for nightly
+ # version=$(rustc -V | cut -d' ' -f2)
+ version=$(rustc -V | cut -d' ' -f2 | cut -d'-' -f2)
+ tarball=rust-std-${version}-${TARGET}
+
+ curl -Os http://static.rust-lang.org/dist/${tarball}.tar.gz
+
+ tar xzf ${tarball}.tar.gz
+
+ ${tarball}/install.sh --prefix=$(rustc --print sysroot)
+
+ rm -r ${tarball}
+ rm ${tarball}.tar.gz
+ ;;
+ # Nothing to do for native builds
+ *)
+ ;;
+esac
+
+# TODO if you need to install extra stuff add it here
diff --git a/ci/script.sh b/ci/script.sh
new file mode 100644
index 0000000..e5f113b
--- /dev/null
+++ b/ci/script.sh
@@ -0,0 +1,36 @@
+# `script` phase: you usually build, test and generate docs in this phase
+
+set -ex
+
+# TODO modify this phase as you see fit
+# PROTIP Always pass `--target $TARGET` to cargo commands, this makes cargo output build artifacts
+# to target/$TARGET/{debug,release} which can reduce the number of needed conditionals in the
+# `before_deploy`/packaging phase
+
+case $TARGET in
+ # use an emulator to run the cross compiled binaries
+ arm-unknown-linux-gnueabihf)
+ # build tests but don't run them
+ cargo test --target $TARGET --no-run
+
+ # run tests in emulator
+ find target/$TARGET/debug -maxdepth 1 -executable -type f | \
+ xargs qemu-arm -L /usr/arm-linux-gnueabihf
+
+ # build the main executable
+ cargo build --target $TARGET
+
+ # run the main executable using the emulator
+ qemu-arm -L /usr/arm-linux-gnueabihf target/$TARGET/debug/ilc
+ ;;
+ *)
+ cargo build --target $TARGET --verbose
+ # this isn't even a temporary solution. :(
+ # cargo test --target $TARGET --verbose
+ ;;
+esac
+
+cargo build --target $TARGET --release
+
+# sanity check the file type
+file target/$TARGET/release/ilc
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..bc654d8
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,3 @@
+single_line_if_else = true
+normalise_comments = false
+reorder_imports = true
diff --git a/src/ageset.rs b/src/ageset.rs
index 084afba..c97240f 100644
--- a/src/ageset.rs
+++ b/src/ageset.rs
@@ -9,14 +9,16 @@ use blist::BList;
/// if the criteria is met.
pub struct AgeSet<T> {
fifo: BList<T>,
- set: HashSet<T>
+ set: HashSet<T>,
}
-impl<T> AgeSet<T> where T: Eq + Hash + Clone {
+impl<T> AgeSet<T>
+ where T: Eq + Hash + Clone
+{
pub fn new() -> Self {
AgeSet {
fifo: BList::new(),
- set: HashSet::new()
+ set: HashSet::new(),
}
}
@@ -24,13 +26,17 @@ impl<T> AgeSet<T> where T: Eq + Hash + Clone {
self.set.contains(t)
}
- pub fn prune<F>(&mut self, kill: F) where F: Fn(&T) -> bool {
+ pub fn prune<F>(&mut self, kill: F)
+ where F: Fn(&T) -> bool
+ {
while let Some(ref e) = self.fifo.front().map(T::clone) {
if kill(&e) {
let removed = self.fifo.pop_front().unwrap();
self.set.remove(&e);
assert!(*e == removed);
- } else { break }
+ } else {
+ break;
+ }
}
}
diff --git a/src/app/freq.rs b/src/app/freq.rs
index 6d80f2e..88a8e1f 100644
--- a/src/app/freq.rs
+++ b/src/app/freq.rs
@@ -2,14 +2,14 @@ use clap::ArgMatches;
use std::collections::HashMap;
-use ilc::event::{ Event, Type };
+use ilc::event::{Event, Type};
use super::*;
struct Person {
lines: u32,
alpha_lines: u32,
- words: u32
+ words: u32,
}
fn words_alpha(s: &str) -> (u32, bool) {
@@ -18,30 +18,37 @@ fn words_alpha(s: &str) -> (u32, bool) {
for w in s.split_whitespace() {
if !w.is_empty() {
words += 1;
- if w.chars().any(char::is_alphabetic) { alpha = true }
+ if w.chars().any(char::is_alphabetic) {
+ alpha = true
+ }
}
}
(words, alpha)
}
fn strip_nick_prefix(s: &str) -> &str {
- if s.is_empty() { return s }
+ if s.is_empty() {
+ return s;
+ }
match s.as_bytes()[0] {
b'~' | b'&' | b'@' | b'%' | b'+' => &s[1..],
- _ => s
+ _ => s,
}
}
-pub fn freq(args: &ArgMatches) {
+pub fn freq(args: &ArgMatches) {
let env = Environment(args);
- let (context, mut decoder, mut input, mut output) = (env.context(), env.decoder(), env.input(), env.output());
+ let (context, mut decoder, mut input, mut output) = (env.context(),
+ env.decoder(),
+ env.input(),
+ env.output());
let mut stats: HashMap<String, Person> = HashMap::new();
for e in decoder.decode(&context, &mut input) {
let m = match e {
Ok(m) => m,
- Err(err) => error(Box::new(err))
+ Err(err) => error(Box::new(err)),
};
match m {
@@ -51,18 +58,21 @@ pub fn freq(args: &ArgMatches) {
let p: &mut Person = stats.get_mut(nick).unwrap();
let (words, alpha) = words_alpha(content);
p.lines += 1;
- if alpha { p.alpha_lines += 1 }
+ if alpha {
+ p.alpha_lines += 1
+ }
p.words += words;
} else {
let (words, alpha) = words_alpha(content);
- stats.insert(nick.to_owned(), Person {
- lines: 1,
- alpha_lines: if alpha { 1 } else { 0 },
- words: words
- });
+ stats.insert(nick.to_owned(),
+ Person {
+ lines: 1,
+ alpha_lines: if alpha { 1 } else { 0 },
+ words: words,
+ });
}
- },
- _ => ()
+ }
+ _ => (),
}
}
@@ -72,7 +82,12 @@ pub fn freq(args: &ArgMatches) {
let count = value_t!(args, "count", usize).unwrap_or(stats.len());
for &(ref name, ref stat) in stats.iter().take(count) {
let _ = write!(&mut output,
- "{}:\n\tTotal lines: {}\n\tLines without alphabetic characters: {}\n\tTotal words: {}\n\tWords per line: {}\n",
- name, stat.lines, stat.lines - stat.alpha_lines, stat.words, stat.words as f32 / stat.lines as f32);
+ "{}:\n\tTotal lines: {}\n\tLines without alphabetic characters: \
+ {}\n\tTotal words: {}\n\tWords per line: {}\n",
+ name,
+ stat.lines,
+ stat.lines - stat.alpha_lines,
+ stat.words,
+ stat.words as f32 / stat.lines as f32);
}
}
diff --git a/src/app/mod.rs b/src/app/mod.rs
index 3221ec9..9b02b1e 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -7,16 +7,16 @@ use glob::glob;
use std::process;
use std::str::FromStr;
-use std::path::{ Path, PathBuf };
-use std::io::{ self, Write, BufWriter, BufRead, BufReader };
+use std::path::{Path, PathBuf};
+use std::io::{self, BufRead, BufReader, BufWriter, Write};
use std::fs::File;
use std::error::Error;
use std::ffi::OsStr;
use ilc::context::Context;
-use ilc::format::{ self, Encode, Decode };
+use ilc::format::{self, Decode, Encode};
-use ::chain;
+use chain;
pub mod freq;
@@ -38,43 +38,47 @@ pub fn die(s: &str) -> ! {
pub fn force_decoder(s: Option<&str>) -> Box<Decode> {
let inf = match s {
Some(s) => s,
- None => die("You didn't specify the input format")
+ None => die("You didn't specify the input format"),
};
match format::decoder(&inf) {
Some(d) => d,
- None => die(&format!("The format `{}` is unknown to me", inf))
+ None => die(&format!("The format `{}` is unknown to me", inf)),
}
}
pub fn force_encoder<'a>(s: Option<&str>) -> Box<Encode> {
let outf = match s {
Some(s) => s,
- None => die("You didn't specify the output format")
+ None => die("You didn't specify the output format"),
};
match format::encoder(&outf) {
Some(e) => e,
- None => die(&format!("The format `{}` is unknown to me", outf))
+ None => die(&format!("The format `{}` is unknown to me", outf)),
}
}
pub fn build_context(args: &ArgMatches) -> Context {
let mut context = Context {
- timezone: FixedOffset::west(args.value_of("timezone").and_then(|s| s.parse().ok()).unwrap_or(0)),
+ timezone: FixedOffset::west(args.value_of("timezone")
+ .and_then(|s| s.parse().ok())
+ .unwrap_or(0)),
override_date: args.value_of("date").and_then(|d| NaiveDate::from_str(&d).ok()),
- channel: args.value_of("channel").map(str::to_owned).clone()
+ channel: args.value_of("channel").map(str::to_owned).clone(),
};
if args.is_present("infer_date") {
let input_files = gather_input(args);
match input_files.len() {
0 => die("No input files given, can't infer date"),
- 1 => if let Some(date) = input_files.get(0)
- .map(PathBuf::as_path)
- .and_then(Path::file_stem)
- .and_then(OsStr::to_str)
- .and_then(|s: &str| NaiveDate::from_str(s).ok()) {
- context.override_date = Some(date);
- },
- _n => die("Too many input files, can't infer date")
+ 1 => {
+ if let Some(date) = input_files.get(0)
+ .map(PathBuf::as_path)
+ .and_then(Path::file_stem)
+ .and_then(OsStr::to_str)
+ .and_then(|s: &str| NaiveDate::from_str(s).ok()) {
+ context.override_date = Some(date);
+ }
+ }
+ _n => die("Too many input files, can't infer date"),
}
}
context
@@ -83,17 +87,23 @@ pub fn build_context(args: &ArgMatches) -> Context {
pub fn gather_input(args: &ArgMatches) -> Vec<PathBuf> {
if let Some(iter) = args.values_of("input_files") {
iter.flat_map(|p| {
- match glob(p) {
- Ok(paths) => paths,
- Err(e) => die(&format!("{}", e.msg))
- }
- }).filter_map(Result::ok).collect()
- } else { Vec::new() }
+ match glob(p) {
+ Ok(paths) => paths,
+ Err(e) => die(&format!("{}", e.msg)),
+ }
+ })
+ .filter_map(Result::ok)
+ .collect()
+ } else {
+ Vec::new()
+ }
}
pub fn open_files(files: Vec<PathBuf>) -> Box<BufRead> {
if files.len() > 0 {
- Box::new(BufReader::new(chain::Chain::new(files.iter().map(|p| File::open(p).unwrap()).collect())))
+ Box::new(BufReader::new(chain::Chain::new(files.iter()
+ .map(|p| File::open(p).unwrap())
+ .collect())))
} else {
Box::new(BufReader::new(io::stdin()))
}
@@ -103,7 +113,7 @@ pub fn open_output(args: &ArgMatches) -> Box<Write> {
if let Some(out) = args.value_of("output_file") {
match File::create(out) {
Ok(f) => Box::new(BufWriter::new(f)),
- Err(e) => error(Box::new(e))
+ Err(e) => error(Box::new(e)),
}
} else {
Box::new(BufWriter::new(io::stdout()))
@@ -113,11 +123,21 @@ pub fn open_output(args: &ArgMatches) -> Box<Write> {
pub struct Environment<'a>(pub &'a ArgMatches<'a>);
impl<'a> Environment<'a> {
- pub fn context(&self) -> Context { build_context(self.0) }
- pub fn input(&self) -> Box<BufRead> { open_files(gather_input(self.0)) }
- pub fn output(&self) -> Box<Write> { open_output(self.0) }
- pub fn decoder(&self) -> Box<Decode> { force_decoder(self.0.value_of("input_format")) }
- pub fn encoder(&self) -> Box<Encode> { force_encoder(self.0.value_of("output_format")) }
+ pub fn context(&self) -> Context {
+ build_context(self.0)
+ }
+ pub fn input(&self) -> Box<BufRead> {
+ open_files(gather_input(self.0))
+ }
+ pub fn output(&self) -> Box<Write> {
+ open_output(self.0)
+ }
+ pub fn decoder(&self) -> Box<Decode> {
+ force_decoder(self.0.value_of("format").or(self.0.value_of("input_format")))
+ }
+ pub fn encoder(&self) -> Box<Encode> {
+ force_encoder(self.0.value_of("format").or(self.0.value_of("output_format")))
+ }
}
pub mod parse {
@@ -128,8 +148,11 @@ pub mod parse {
let (context, mut decoder, mut input) = (env.context(), env.decoder(), env.input());
for e in decoder.decode(&context, &mut input) {
match e {
- Err(e) => { println!("Foo!"); error(Box::new(e)) },
- _ => ()
+ Err(e) => {
+ println!("Foo!");
+ error(Box::new(e))
+ }
+ _ => (),
}
}
}
@@ -140,13 +163,18 @@ pub mod convert {
use super::*;
pub fn convert(args: &ArgMatches) {
let env = Environment(args);
- let (context, mut decoder, mut input, encoder, mut output) =
- (env.context(), env.decoder(), env.input(), env.encoder(), env.output());
+ let (context, mut decoder, mut input, encoder, mut output) = (env.context(),
+ env.decoder(),
+ env.input(),
+ env.encoder(),
+ env.output());
for e in decoder.decode(&context, &mut input) {
match e {
- Ok(e) => { let _ = encoder.encode(&context, &mut output, &e); },
- Err(e) => error(Box::new(e))
+ Ok(e) => {
+ let _ = encoder.encode(&context, &mut output, &e);
+ }
+ Err(e) => error(Box::new(e)),
}
}
}
@@ -155,11 +183,14 @@ pub mod convert {
pub mod seen {
use clap::ArgMatches;
use ilc::event::Event;
- use ilc::format::{ self, Encode };
+ use ilc::format::{self, Encode};
use super::*;
pub fn seen(args: &ArgMatches) {
let env = Environment(args);
- let (context, mut decoder, mut input, mut output) = (env.context(), env.decoder(), env.input(), env.output());
+ let (context, mut decoder, mut input, mut output) = (env.context(),
+ env.decoder(),
+ env.input(),
+ env.output());
let nick = args.value_of("nick").expect("Required argument <nick> not present");
@@ -167,11 +198,14 @@ pub mod seen {
for e in decoder.decode(&context, &mut input) {
let m = match e {
Ok(m) => m,
- Err(err) => error(Box::new(err))
+ Err(err) => error(Box::new(err)),
};
- if m.ty.involves(nick)
- && last.as_ref().map_or(true, |last| m.time.as_timestamp() > last.time.as_timestamp()) { last = Some(m) }
+ if m.ty.involves(nick) &&
+ last.as_ref().map_or(true,
+ |last| m.time.as_timestamp() > last.time.as_timestamp()) {
+ last = Some(m)
+ }
}
let encoder = format::Weechat;
if let Some(ref m) = last {
@@ -186,12 +220,15 @@ pub mod sort {
use super::*;
pub fn sort(args: &ArgMatches) {
let env = Environment(args);
- let (context, mut decoder, mut input, encoder, mut output) =
- (env.context(), env.decoder(), env.input(), env.encoder(), env.output());
+ let (context, mut decoder, mut input, encoder, mut output) = (env.context(),
+ env.decoder(),
+ env.input(),
+ env.encoder(),
+ env.output());
let mut events: Vec<Event> = decoder.decode(&context, &mut input)
- .flat_map(Result::ok)
- .collect();
+ .flat_map(Result::ok)
+ .collect();
events.sort_by(|a, b| a.time.cmp(&b.time));
for e in events {
@@ -203,12 +240,15 @@ pub mod sort {
pub mod dedup {
use clap::ArgMatches;
use ilc::event::NoTimeHash;
- use ::ageset::AgeSet;
+ use ageset::AgeSet;
use super::*;
pub fn dedup(args: &ArgMatches) {
let env = Environment(args);
- let (context, mut decoder, mut input, encoder, mut output) =
- (env.context(), env.decoder(), env.input(), env.encoder(), env.output());
+ let (context, mut decoder, mut input, encoder, mut output) = (env.context(),
+ env.decoder(),
+ env.input(),
+ env.encoder(),
+ env.output());
let mut backlog = AgeSet::new();
diff --git a/src/chain.rs b/src/chain.rs
index 9b9848a..a8014b8 100644
--- a/src/chain.rs
+++ b/src/chain.rs
@@ -1,19 +1,21 @@
-use std::io::{ Result, Read, Write };
+use std::io::{Read, Result, Write};
pub struct Chain<T> {
elem: Vec<T>,
- index: usize
+ index: usize,
}
impl<T: Read> Read for Chain<T> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
loop {
match self.elem.get_mut(self.index) {
- Some(ref mut r) => match try!(r.read(buf)) {
- 0 => self.index += 1,
- n => return Ok(n)
- },
- None => return Ok(0)
+ Some(ref mut r) => {
+ match try!(r.read(buf)) {
+ 0 => self.index += 1,
+ n => return Ok(n),
+ }
+ }
+ None => return Ok(0),
}
}
}
@@ -23,11 +25,13 @@ impl<T: Write> Write for Chain<T> {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
loop {
match self.elem.get_mut(self.index) {
- Some(ref mut r) => match try!(r.write(buf)) {
- 0 => self.index += 1,
- n => return Ok(n)
- },
- None => return Ok(0)
+ Some(ref mut r) => {
+ match try!(r.write(buf)) {
+ 0 => self.index += 1,
+ n => return Ok(n),
+ }
+ }
+ None => return Ok(0),
}
}
}
@@ -35,14 +39,16 @@ impl<T: Write> Write for Chain<T> {
fn flush(&mut self) -> Result<()> {
match self.elem.get_mut(self.index) {
Some(ref mut r) => r.flush(),
- None => Ok(())
+ None => Ok(()),
}
}
-
}
impl<T> Chain<T> {
pub fn new(elem: Vec<T>) -> Chain<T> {
- Chain { index: 0, elem: elem }
+ Chain {
+ index: 0,
+ elem: elem,
+ }
}
}
diff --git a/src/context.rs b/src/context.rs
index 1793361..4393457 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -5,5 +5,5 @@ use chrono::offset::fixed::FixedOffset;
pub struct Context {
pub timezone: FixedOffset,
pub override_date: Option<NaiveDate>,
- pub channel: Option<String>
+ pub channel: Option<String>,
}
diff --git a/src/event.rs b/src/event.rs
index d2ce053..0a6fb2b 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -17,7 +17,7 @@
use std::borrow::Cow;
use std::cmp::Ordering;
-use std::hash::{ Hash, Hasher };
+use std::hash::{Hash, Hasher};
use chrono::naive::time::NaiveTime;
use chrono::offset::fixed::FixedOffset;
@@ -27,7 +27,7 @@ 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<Event<'a>>
+ pub entries: Vec<Event<'a>>,
}
/// Different log formats carry different amounts of information. Some might
@@ -37,23 +37,25 @@ pub struct Log<'a> {
pub enum Time {
Unknown,
Hms(u8, u8, u8),
- Timestamp(i64)
+ 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)
+ .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))
+ &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)),
}
}
@@ -61,14 +63,18 @@ impl Time {
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
+ &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()) }
+ pub fn to_timestamp(&self) -> Time {
+ Time::Timestamp(self.as_timestamp())
+ }
}
impl PartialOrd for Time {
@@ -77,13 +83,16 @@ impl PartialOrd for 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) }
- },
+ 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!()
+ _ => unimplemented!(),
}
}
}
@@ -92,12 +101,12 @@ impl PartialOrd for Time {
pub struct Event<'a> {
pub ty: Type<'a>,
pub time: Time,
- pub channel: Option<Cow<'a, str>>
+ pub channel: Option<Cow<'a, str>>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, RustcEncodable, RustcDecodable)]
pub struct User<'a> {
- nick: Cow<'a, str>
+ nicks: Cow<'a, str>,
}
/// All representable events, such as messages, quits, joins
@@ -151,8 +160,8 @@ pub enum Type<'a> {
Mode {
nick: Option<Cow<'a, str>>,
mode: Cow<'a, str>,
- masks: Cow<'a, str>
- }
+ masks: Cow<'a, str>,
+ },
}
impl<'a> Type<'a> {
@@ -166,11 +175,15 @@ impl<'a> Type<'a> {
&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)),
+ &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
+ &Mode { ref nick, .. } => {
+ nick.as_ref().map_or(false, |k| k.as_ref() == Cow::Borrowed(needle))
+ }
+ _ => false,
}
}
}
@@ -179,7 +192,9 @@ impl<'a> Type<'a> {
pub struct NoTimeHash<'a>(pub Event<'a>);
impl<'a> Hash for NoTimeHash<'a> {
- fn hash<H>(&self, state: &mut H) where H: Hasher {
+ fn hash<H>(&self, state: &mut H)
+ where H: Hasher
+ {
self.0.ty.hash(state);
self.0.channel.hash(state);
}
diff --git a/src/format/binary.rs b/src/format/binary.rs
index a7ae7ca..7cc4281 100644
--- a/src/format/binary.rs
+++ b/src/format/binary.rs
@@ -12,39 +12,46 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use std::io::{ BufRead, Write };
+use std::io::{BufRead, Write};
use std::iter::Iterator;
use event::Event;
use context::Context;
-use format::{ Encode, Decode };
+use format::{Decode, Encode};
-use bincode::{ self, SizeLimit };
+use bincode::{self, SizeLimit};
pub struct Binary;
pub struct Iter<'a> {
- input: &'a mut BufRead
+ input: &'a mut BufRead,
}
impl<'a> Iterator for Iter<'a> {
type Item = ::Result<Event<'a>>;
fn next(&mut self) -> Option<::Result<Event<'a>>> {
- Some(bincode::rustc_serialize::decode_from::<_, Event>(&mut self.input, SizeLimit::Infinite)
- .map_err(|_| ::IlcError::BincodeDecode))
+ Some(bincode::rustc_serialize::decode_from::<_, Event>(&mut self.input,
+ SizeLimit::Infinite)
+ .map_err(|_| ::IlcError::BincodeDecode))
}
}
impl Encode for Binary {
- fn encode<'a>(&'a self, _context: &'a Context, mut output: &'a mut Write, event: &'a Event) -> ::Result<()> {
+ fn encode<'a>(&'a self,
+ _context: &'a Context,
+ mut output: &'a mut Write,
+ event: &'a Event)
+ -> ::Result<()> {
bincode::rustc_serialize::encode_into(event, &mut output, SizeLimit::Infinite)
.map_err(|_| ::IlcError::BincodeEncode)
}
}
impl Decode for Binary {
- fn decode<'a>(&'a mut self, _context: &'a Context, input: &'a mut BufRead)
- -> Box<Iterator<Item = ::Result<Event<'a>>> + 'a> {
+ fn decode<'a>(&'a mut self,
+ _context: &'a Context,
+ input: &'a mut BufRead)
+ -> Box<Iterator<Item = ::Result<Event<'a>>> + 'a> {
Box::new(Iter { input: input })
}
}
diff --git a/src/format/energymech.rs b/src/format/energymech.rs
index ba82458..e8dded6 100644
--- a/src/format/energymech.rs
+++ b/src/format/energymech.rs
@@ -12,15 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use std::io::{ BufRead, Write };
-use std::borrow::{ ToOwned, Cow };
-use std::iter::{ Iterator };
+use std::io::{BufRead, Write};
+use std::borrow::{Cow, ToOwned};
+use std::iter::Iterator;
-use event::{ Event, Type, Time };
+use event::{Event, Time, Type};
use context::Context;
-use format::{ Encode, Decode, rejoin, strip_one };
+use format::{Decode, Encode, rejoin, strip_one};
-use l::LogLevel::Info;
+use log::LogLevel::Info;
use chrono::*;
@@ -31,7 +31,7 @@ static TIME_FORMAT: &'static str = "%H:%M:%S";
pub struct Iter<'a> {
context: &'a Context,
input: &'a mut BufRead,
- buffer: Vec<u8>
+ buffer: Vec<u8>,
}
impl<'a> Iterator for Iter<'a> {
@@ -42,11 +42,13 @@ impl<'a> Iterator for Iter<'a> {
let m = time[4..6].parse::<u32>().unwrap();
let s = time[7..9].parse::<u32>().unwrap();
if let Some(date) = context.override_date {
- Time::Timestamp(context.timezone.from_local_date(&date)
- .and_time(NaiveTime::from_hms(h, m, s))
- .single()
- .expect("Transformed log times can't be represented, due to timezone transitions")
- .timestamp())
+ Time::Timestamp(context.timezone
+ .from_local_date(&date)
+ .and_time(NaiveTime::from_hms(h, m, s))
+ .single()
+ .expect("Transformed log times can't be represented, due \
+ to timezone transitions")
+ .timestamp())
} else {
Time::Hms(h as u8, m as u8, s as u8)
}
@@ -56,15 +58,21 @@ impl<'a> Iterator for Iter<'a> {
self.buffer.clear();
match self.input.read_until(b'\n', &mut self.buffer) {
Ok(0) | Err(_) => return None,
- Ok(_) => ()
+ Ok(_) => (),
}
let buffer = String::from_utf8_lossy(&self.buffer);
let mut split_tokens: Vec<char> = Vec::new();
- let tokens = buffer.split( |c: char| {
- if c.is_whitespace() { split_tokens.push(c); true } else { false }
- }).collect::<Vec<_>>();
+ let tokens = buffer.split(|c: char| {
+ if c.is_whitespace() {
+ split_tokens.push(c);
+ true
+ } else {
+ false
+ }
+ })
+ .collect::<Vec<_>>();
if log_enabled!(Info) {
info!("Original: `{}`", buffer);
@@ -72,142 +80,175 @@ impl<'a> Iterator for Iter<'a> {
}
match &tokens[..tokens.len() - 1] {
- [time, "*", nick, content..] => return Some(Ok(Event {
- ty: Type::Action {
- from: nick.to_owned().into(),
- content: rejoin(content, &split_tokens[3..])
- },
- time: parse_time(&self.context, time),
- channel: self.context.channel.clone().map(Into::into)
- })),
- [time, "***", old, "is", "now", "known", "as", new] => return Some(Ok(Event {
- ty: Type::Nick {
- old_nick: old.to_owned().into(),
- new_nick: new.to_owned().into()
- },
- time: parse_time(&self.context, time),
- channel: self.context.channel.clone().map(Into::into)
-
- })),
- [time, "***", nick, "sets", "mode:", mode, masks..] => return Some(Ok(Event {
- ty: Type::Mode {
- nick: Some(nick.to_owned().into()),
- mode: mode.to_owned().into(),
- masks: rejoin(&masks, &split_tokens[6..]).to_owned().into()
- },
- time: parse_time(&self.context, time),
- channel: self.context.channel.clone().map(Into::into)
-
- })),
- [time, "***", "Joins:", nick, host] => return Some(Ok(Event {
- ty: Type::Join {
- nick: nick.to_owned().into(),
- mask: Some(strip_one(host).into())
- },
- time: parse_time(&self.context, time),
- channel: self.context.channel.clone().map(Into::into)
-
- })),
- [time, "***", "Parts:", nick, host, reason..] => return Some(Ok(Event {
- ty: Type::Part {
- nick: nick.to_owned().into(),
- mask: Some(strip_one(host).into()),
- reason: Some(strip_one(&rejoin(reason, &split_tokens[5..])).into())
- },
- time: parse_time(&self.context, time),
- channel: self.context.channel.clone().map(Into::into)
-
- })),
- [time, "***", "Quits:", nick, host, reason..] => return Some(Ok(Event {
- ty: Type::Quit {
- nick: nick.to_owned().into(),
- mask: Some(strip_one(host).into()),
- reason: Some(strip_one(&rejoin(reason, &split_tokens[5..])).into())
- },
- time: parse_time(&self.context, time),
- channel: self.context.channel.clone().map(Into::into)
-
- })),
- [time, "***", nick, "changes", "topic", "to", topic..] => return Some(Ok(Event {
- ty: Type::TopicChange {
- nick: Some(nick.to_owned().into()),
- new_topic: strip_one(&rejoin(topic, &split_tokens[6..])).into()
- },
- time: parse_time(&self.context, time),
- channel: self.context.channel.clone().map(Into::into)
-
- })),
- [time, nick, content..]
- if nick.starts_with('<') && nick.ends_with('>')
- => return Some(Ok(Event {
- ty: Type::Msg {
- from: strip_one(nick).into(),
- content: rejoin(content, &split_tokens[2..])
- },
- time: parse_time(&self.context, time),
- channel: self.context.channel.clone().map(Into::into)
- })),
- _ => ()
+ [time, "*", nick, content..] => {
+ return Some(Ok(Event {
+ ty: Type::Action {
+ from: nick.to_owned().into(),
+ content: rejoin(content, &split_tokens[3..]),
+ },
+ time: parse_time(&self.context, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ [time, "***", old, "is", "now", "known", "as", new] => {
+ return Some(Ok(Event {
+ ty: Type::Nick {
+ old_nick: old.to_owned().into(),
+ new_nick: new.to_owned().into(),
+ },
+ time: parse_time(&self.context, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ [time, "***", nick, "sets", "mode:", mode, masks..] => {
+ return Some(Ok(Event {
+ ty: Type::Mode {
+ nick: Some(nick.to_owned().into()),
+ mode: mode.to_owned().into(),
+ masks: rejoin(&masks, &split_tokens[6..]).to_owned().into(),
+ },
+ time: parse_time(&self.context, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ [time, "***", "Joins:", nick, host] => {
+ return Some(Ok(Event {
+ ty: Type::Join {
+ nick: nick.to_owned().into(),
+ mask: Some(strip_one(host).into()),
+ },
+ time: parse_time(&self.context, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ [time, "***", "Parts:", nick, host, reason..] => {
+ return Some(Ok(Event {
+ ty: Type::Part {
+ nick: nick.to_owned().into(),
+ mask: Some(strip_one(host).into()),
+ reason: Some(strip_one(&rejoin(reason, &split_tokens[5..])).into()),
+ },
+ time: parse_time(&self.context, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ [time, "***", "Quits:", nick, host, reason..] => {
+ return Some(Ok(Event {
+ ty: Type::Quit {
+ nick: nick.to_owned().into(),
+ mask: Some(strip_one(host).into()),
+ reason: Some(strip_one(&rejoin(reason, &split_tokens[5..])).into()),
+ },
+ time: parse_time(&self.context, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ [time, "***", nick, "changes", "topic", "to", topic..] => {
+ return Some(Ok(Event {
+ ty: Type::TopicChange {
+ nick: Some(nick.to_owned().into()),
+ new_topic: strip_one(&rejoin(topic, &split_tokens[6..])).into(),
+ },
+ time: parse_time(&self.context, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ [time, nick, content..] if nick.starts_with('<') && nick.ends_with('>') => {
+ return Some(Ok(Event {
+ ty: Type::Msg {
+ from: strip_one(nick).into(),
+ content: rejoin(content, &split_tokens[2..]),
+ },
+ time: parse_time(&self.context, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ _ => (),
}
}
}
}
impl Decode for Energymech {
- fn decode<'a>(&'a mut self, context: &'a Context, input: &'a mut BufRead) -> Box<Iterator<Item = ::Result<Event<'a>>> + 'a> {
+ fn decode<'a>(&'a mut self,
+ context: &'a Context,
+ input: &'a mut BufRead)
+ -> Box<Iterator<Item = ::Result<Event<'a>>> + 'a> {
Box::new(Iter {
context: context,
input: input,
- buffer: Vec::new()
+ buffer: Vec::new(),
})
}
}
impl Encode for Energymech {
- fn encode<'a>(&'a self, context: &'a Context, mut output: &'a mut Write, event: &'a Event) -> ::Result<()> {
+ fn encode<'a>(&'a self,
+ context: &'a Context,
+ mut output: &'a mut Write,
+ event: &'a Event)
+ -> ::Result<()> {
match event {
&Event { ty: Type::Msg { ref from, ref content }, ref time, .. } => {
- try!(writeln!(&mut output, "[{}] <{}> {}",
- time.with_format(&context.timezone, TIME_FORMAT), from, content))
- },
+ try!(writeln!(&mut output,
+ "[{}] <{}> {}",
+ time.with_format(&context.timezone, TIME_FORMAT),
+ from,
+ content))
+ }
&Event { ty: Type::Action { ref from, ref content }, ref time, .. } => {
- try!(writeln!(&mut output, "[{}] * {} {}",
- time.with_format(&context.timezone, TIME_FORMAT), from, content))
- },
+ try!(writeln!(&mut output,
+ "[{}] * {} {}",
+ time.with_format(&context.timezone, TIME_FORMAT),
+ from,
+ content))
+ }
&Event { ty: Type::Nick { ref old_nick, ref new_nick }, ref time, .. } => {
- try!(writeln!(&mut output, "[{}] *** {} is now known as {}",
- time.with_format(&context.timezone, TIME_FORMAT), old_nick, new_nick))
- },
+ try!(writeln!(&mut output,
+ "[{}] *** {} is now known as {}",
+ time.with_format(&context.timezone, TIME_FORMAT),
+ old_nick,
+ new_nick))
+ }
&Event { ty: Type::Mode { ref nick, ref mode, ref masks }, ref time, .. } => {
- try!(writeln!(&mut output, "[{}] *** {} sets mode: {} {}",
- time.with_format(&context.timezone, TIME_FORMAT),
- nick.as_ref().expect("Nickname not present, but required."),
- mode, masks))
- },
+ try!(writeln!(&mut output,
+ "[{}] *** {} sets mode: {} {}",
+ time.with_format(&context.timezone, TIME_FORMAT),
+ nick.as_ref().expect("Nickname not present, but required."),
+ mode,
+ masks))
+ }
&Event { ty: Type::Join { ref nick, ref mask }, ref time, .. } => {
- try!(writeln!(&mut output, "[{}] *** Joins: {} ({})",
- time.with_format(&context.timezone, TIME_FORMAT), nick,
- mask.as_ref().expect("Mask not present, but required.")))
- },
+ try!(writeln!(&mut output,
+ "[{}] *** Joins: {} ({})",
+ time.with_format(&context.timezone, TIME_FORMAT),
+ nick,
+ mask.as_ref().expect("Mask not present, but required.")))
+ }
&Event { ty: Type::Part { ref nick, ref mask, ref reason }, ref time, .. } => {
- try!(writeln!(&mut output, "[{}] *** Parts: {} ({}) ({})",
- time.with_format(&context.timezone, TIME_FORMAT), nick,
- mask.as_ref().expect("Mask not present, but required."),
- reason.as_ref().unwrap_or(&Cow::Borrowed(""))))
- },
+ try!(writeln!(&mut output,
+ "[{}] *** Parts: {} ({}) ({})",
+ time.with_format(&context.timezone, TIME_FORMAT),
+ nick,
+ mask.as_ref().expect("Mask not present, but required."),
+ reason.as_ref().unwrap_or(&Cow::Borrowed(""))))
+ }
&Event { ty: Type::Quit { ref nick, ref mask, ref reason }, ref time, .. } => {
- try!(writeln!(&mut output, "[{}] *** Quits: {} ({}) ({})",
- time.with_format(&context.timezone, TIME_FORMAT), nick,
- mask.as_ref().expect("Mask not present, but required."),
- reason.as_ref().expect("Reason not present, but required.")))
- },
+ try!(writeln!(&mut output,
+ "[{}] *** Quits: {} ({}) ({})",
+ time.with_format(&context.timezone, TIME_FORMAT),
+ nick,
+ mask.as_ref().expect("Mask not present, but required."),
+ reason.as_ref().expect("Reason not present, but required.")))
+ }
&Event { ty: Type::TopicChange { ref nick, ref new_topic }, ref time, .. } => {
- try!(writeln!(&mut output, "[{}] *** {} changes topic to '{}'",
- time.with_format(&context.timezone, TIME_FORMAT),
- nick.as_ref().expect("Nick not present, but required."),
- new_topic))
- },
- _ => ()
+ try!(writeln!(&mut output,
+ "[{}] *** {} changes topic to '{}'",
+ time.with_format(&context.timezone, TIME_FORMAT),
+ nick.as_ref().expect("Nick not present, but required."),
+ new_topic))
+ }
+ _ => (),
}
Ok(())
}
diff --git a/src/format/irssi.rs b/src/format/irssi.rs
new file mode 100644
index 0000000..6afcd61
--- /dev/null
+++ b/src/format/irssi.rs
@@ -0,0 +1,205 @@
+// 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.
+
+use std::io::{ BufRead, Write };
+use std::borrow::{ ToOwned };
+use std::iter::{ Iterator };
+
+use event::{ Event, Type, Time };
+use context::Context;
+use format::{ Encode, Decode, rejoin, strip_one };
+
+use l::LogLevel::Info;
+
+pub struct Irssi;
+
+static LOG_OPEN_FORMAT: &'static str = "%a %b %e %T %Y";
+static LINE_FORMAT: &'static str = "%H:%M";
+
+pub struct Iter<'a> {
+ context: &'a Context,
+ input: &'a mut BufRead,
+ buffer: Vec<u8>
+}
+
+impl<'a> Iterator for Iter<'a> {
+ type Item = ::Result<Event<'a>>;
+ fn next(&mut self) -> Option<::Result<Event<'a>>> {
+ fn parse_time(c: &Context, date: &str, time: &str) -> Time {
+ Time::from_format(&c.timezone, &format!("{} {}", date, time), TIME_DATE_FORMAT)
+ }
+
+ loop {
+ self.buffer.clear();
+ match self.input.read_until(b'\n', &mut self.buffer) {
+ Ok(0) | Err(_) => return None,
+ Ok(_) => ()
+ }
+
+ let buffer = String::from_utf8_lossy(&self.buffer);
+
+ let mut split_tokens: Vec<char> = Vec::new();
+ let tokens = buffer.split(|c: char| {
+ if c.is_whitespace() { split_tokens.push(c); true } else { false }
+ }).collect::<Vec<_>>();
+
+ if log_enabled!(Info) {
+ info!("Original: `{}`", buffer);
+ info!("Parsing: {:?}", tokens);
+ }
+
+ match &tokens[..tokens.len() - 1] {
+ ["---", "Log", "opened", day_of_week, month, day, time, year] => {
+ year
+ },
+ ["---", "Log", "closed", day_of_week, month, day, time, year]
+ => return Some(Ok(Event {
+ ty: Type::Disconnect,
+ time: parse_time(&self.context, date, time),
+ channel: self.context.channel.clone().map(Into::into)
+ })),
+ [time, "-!-", nick, host, "has", "joined", channel]
+ => return Some(Ok(Event {
+ ty: Type::Join {
+ nick: nick.to_owned().into(),
+ mask: Some(strip_one(host).into()),
+ },
+ channel: Some(channel.to_owned().into()),
+ time: parse_time(&self.context, date, time)
+ })),
+ [time, "-!-", nick, host, "has", "left", channel, reason..]
+ => return Some(Ok(Event {
+ ty: Type::Part {
+ nick: nick.to_owned().into(),
+ mask: Some(strip_one(host).into()),
+ reason: Some(strip_one(&rejoin(reason, &split_tokens[8..])).into()),
+ },
+ channel: Some(channel.to_owned().into()),
+ time: parse_time(&self.context, date, time)
+ })),
+ [time, "-!-", nick, host, "has", "quit", reason..]
+ => return Some(Ok(Event {
+ ty: Type::Quit {
+ nick: nick.to_owned().into(),
+ mask: Some(strip_one(host).into()),
+ reason: Some(strip_one(&rejoin(reason, &split_tokens[7..])).into()),
+ },
+ time: parse_time(&self.context, date, time),
+ channel: self.context.channel.clone().map(Into::into)
+ })),
+ // TODO: reorder
+ [date, time, "--", notice, content..]
+ if notice.starts_with("Notice(")
+ => return Some(Ok(Event {
+ ty: Type::Notice {
+ from: notice["Notice(".len()..notice.len() - 2].to_owned().into(),
+ content: rejoin(content, &split_tokens[4..]),
+ },
+ time: parse_time(&self.context, date, time),
+ channel: self.context.channel.clone().map(Into::into)
+ })),
+ [date, time, "--", nick, verb, "now", "known", "as", new_nick]
+ if verb == "is" || verb == "are"
+ => return Some(Ok(Event {
+ ty: Type::Nick {
+ old_nick: nick.to_owned().into(),
+ new_nick: new_nick.to_owned().into()
+ },
+ time: parse_time(&self.context, date, time),
+ channel: self.context.channel.clone().map(Into::into)
+ })),
+ [date, time, sp, "*", nick, msg..]
+ if sp.clone().is_empty()
+ => return Some(Ok(Event {
+ ty: Type::Action {
+ from: nick.to_owned().into(),
+ content: rejoin(msg, &split_tokens[5..]),
+ },
+ time: parse_time(&self.context, date, time),
+ channel: self.context.channel.clone().map(Into::into)
+ })),
+ [date, time, nick, msg..]
+ => return Some(Ok(Event {
+ ty: Type::Msg {
+ from: nick.to_owned().into(),
+ content: rejoin(msg, &split_tokens[3..]),
+ },
+ time: parse_time(&self.context, date, time),
+ channel: self.context.channel.clone().map(Into::into)
+ })),
+ _ => ()
+ }
+ }
+ }
+}
+
+impl Decode for Irssi {
+ fn decode<'a>(&'a mut self, context: &'a Context, input: &'a mut BufRead) -> Box<Iterator<Item = ::Result<Event<'a>>> + 'a> {
+ Box::new(Iter {
+ context: context,
+ input: input,
+ buffer: Vec::new()
+ })
+ }
+}
+
+impl Encode for Irssi {
+ fn encode<'a>(&'a self, context: &'a Context, mut output: &'a mut Write, event: &'a Event) -> ::Result<()> {
+ match event {
+ &Event { ty: Type::Msg { ref from, ref content, .. }, ref time, .. } => {
+ try!(writeln!(&mut output, "{}\t{}\t{}",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT), from, content))
+ },
+ &Event { ty: Type::Action { ref from, ref content, .. }, ref time, .. } => {
+ try!(writeln!(&mut output, "{}\t *\t{} {}",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT), from, content))
+ },
+ &Event { ty: Type::Join { ref nick, ref mask, .. }, ref channel, ref time } => {
+ try!(writeln!(&mut output, "{}\t-->\t{} ({}) has joined {}",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT), nick,
+ mask.as_ref().expect("Hostmask not present, but required."),
+ channel.as_ref().expect("Channel not present, but required.")))
+ },
+ &Event { ty: Type::Part { ref nick, ref mask, ref reason }, ref channel, ref time } => {
+ try!(write!(&mut output, "{}\t<--\t{} ({}) has left {}",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT), nick,
+ mask.as_ref().expect("Hostmask not present, but required."),
+ channel.as_ref().expect("Channel not present, but required.")));
+ if reason.is_some() && reason.as_ref().unwrap().len() > 0 {
+ try!(write!(&mut output, " ({})", reason.as_ref().unwrap()));
+ }
+ try!(write!(&mut output, "\n"))
+ },
+ &Event { ty: Type::Quit { ref nick, ref mask, ref reason }, ref time, .. } => {
+ try!(write!(&mut output, "{}\t<--\t{} ({}) has quit",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT), nick,
+ mask.as_ref().expect("Hostmask not present, but required.")));
+ if reason.is_some() && reason.as_ref().unwrap().len() > 0 {
+ try!(write!(&mut output, " ({})", reason.as_ref().unwrap()));
+ }
+ try!(write!(&mut output, "\n"))
+ },
+ &Event { ty: Type::Disconnect, ref time, .. } => {
+ try!(writeln!(&mut output, "{}\t--\tirc: disconnected from server",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT)))
+ },
+ &Event { ty: Type::Notice { ref from, ref content }, ref time, .. } => {
+ try!(writeln!(&mut output, "{}\t--\tNotice({}): {}",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT), from, content))
+ },
+ _ => ()
+ }
+ Ok(())
+ }
+}
diff --git a/src/format/mod.rs b/src/format/mod.rs
index cea6855..8873db8 100644
--- a/src/format/mod.rs
+++ b/src/format/mod.rs
@@ -17,7 +17,7 @@
//! target format, all formats must allow for omittable information.
use std::iter;
-use std::io::{ BufRead, Write };
+use std::io::{BufRead, Write};
use std::borrow::Cow;
use event::Event;
@@ -35,17 +35,27 @@ mod binary;
mod msgpack;
pub trait Encode {
- fn encode<'a>(&'a self, context: &'a Context, output: &'a mut Write, event: &'a Event) -> ::Result<()>;
+ fn encode<'a>(&'a self,
+ context: &'a Context,
+ output: &'a mut Write,
+ event: &'a Event)
+ -> ::Result<()>;
}
pub trait Decode {
- fn decode<'a>(&'a mut self, context: &'a Context, input: &'a mut BufRead) -> Box<Iterator<Item = ::Result<Event<'a>>> + 'a>;
+ fn decode<'a>(&'a mut self,
+ context: &'a Context,
+ input: &'a mut BufRead)
+ -> Box<Iterator<Item = ::Result<Event<'a>>> + 'a>;
}
pub struct Dummy;
impl Decode for Dummy {
- fn decode<'a>(&'a mut self, _context: &'a Context, _input: &'a mut BufRead) -> Box<Iterator<Item = ::Result<Event<'a>>> + 'a> {
+ fn decode<'a>(&'a mut self,
+ _context: &'a Context,
+ _input: &'a mut BufRead)
+ -> Box<Iterator<Item = ::Result<Event<'a>>> + 'a> {
Box::new(iter::empty())
}
}
@@ -54,10 +64,10 @@ pub fn decoder(format: &str) -> Option<Box<Decode>> {
match format {
"energymech" | "em" => Some(Box::new(Energymech)),
"weechat" | "w" => Some(Box::new(Weechat)),
-// "irssi" => Some(Box::new(irssi::Irssi)),
+ // "irssi" => Some(Box::new(irssi::Irssi)),
"binary" => Some(Box::new(Binary)),
"msgpack" => Some(Box::new(Msgpack)),
- _ => None
+ _ => None,
}
}
@@ -65,18 +75,24 @@ pub fn encoder(format: &str) -> Option<Box<Encode>> {
match format {
"energymech" | "em" => Some(Box::new(Energymech)),
"weechat" | "w" => Some(Box::new(Weechat)),
-// "irssi" => Some(Box::new(irssi::Irssi)),
+ // "irssi" => Some(Box::new(irssi::Irssi)),
"binary" => Some(Box::new(Binary)),
"msgpack" => Some(Box::new(Msgpack)),
- _ => None
+ _ => None,
}
}
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)
+ 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)
}
fn strip_one(s: &str) -> String {
diff --git a/src/format/msgpack.rs b/src/format/msgpack.rs
index 022e373..36af1aa 100644
--- a/src/format/msgpack.rs
+++ b/src/format/msgpack.rs
@@ -12,21 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use std::io::{ BufRead, Write };
+use std::io::{BufRead, Write};
use std::iter::Iterator;
use event::Event;
use context::Context;
-use format::{ Encode, Decode };
+use format::{Decode, Encode};
-use rustc_serialize::{ Encodable, Decodable };
-use msgpack::{ Encoder, Decoder };
+use rustc_serialize::{Decodable, Encodable};
+use msgpack::{Decoder, Encoder};
use rmp::decode::ReadError;
pub struct Msgpack;
pub struct Iter<'a> {
- input: &'a mut BufRead
+ input: &'a mut BufRead,
}
impl<'a> Iterator for Iter<'a> {
@@ -36,21 +36,27 @@ impl<'a> Iterator for Iter<'a> {
match Event::decode(&mut Decoder::new(&mut self.input)) {
Ok(e) => Some(Ok(e)),
Err(decode::Error::InvalidMarkerRead(ReadError::UnexpectedEOF)) => None,
- Err(e) => Some(Err(::IlcError::MsgpackDecode(e)))
+ Err(e) => Some(Err(::IlcError::MsgpackDecode(e))),
}
}
}
impl Encode for Msgpack {
- fn encode<'a>(&'a self, _context: &'a Context, output: &'a mut Write, event: &'a Event) -> ::Result<()> {
+ fn encode<'a>(&'a self,
+ _context: &'a Context,
+ output: &'a mut Write,
+ event: &'a Event)
+ -> ::Result<()> {
event.encode(&mut Encoder::new(output))
- .map_err(|e| ::IlcError::MsgpackEncode(e))
+ .map_err(|e| ::IlcError::MsgpackEncode(e))
}
}
impl Decode for Msgpack {
- fn decode<'a>(&'a mut self, _context: &'a Context, input: &'a mut BufRead)
- -> Box<Iterator<Item = ::Result<Event<'a>>> + 'a> {
+ fn decode<'a>(&'a mut self,
+ _context: &'a Context,
+ input: &'a mut BufRead)
+ -> Box<Iterator<Item = ::Result<Event<'a>>> + 'a> {
Box::new(Iter { input: input })
}
}
diff --git a/src/format/weechat.rs b/src/format/weechat.rs
index 30fdc24..ccb0726 100644
--- a/src/format/weechat.rs
+++ b/src/format/weechat.rs
@@ -12,15 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use std::io::{ BufRead, Write };
-use std::borrow::{ ToOwned };
-use std::iter::{ Iterator };
+use std::io::{BufRead, Write};
+use std::borrow::ToOwned;
+use std::iter::Iterator;
-use event::{ Event, Type, Time };
+use event::{Event, Time, Type};
use context::Context;
-use format::{ Encode, Decode, rejoin, strip_one };
+use format::{Decode, Encode, rejoin, strip_one};
-use l::LogLevel::Info;
+use log::LogLevel::Info;
pub struct Weechat;
@@ -29,7 +29,7 @@ static TIME_DATE_FORMAT: &'static str = "%Y-%m-%d %H:%M:%S";
pub struct Iter<'a> {
context: &'a Context,
input: &'a mut BufRead,
- buffer: Vec<u8>
+ buffer: Vec<u8>,
}
impl<'a> Iterator for Iter<'a> {
@@ -43,15 +43,21 @@ impl<'a> Iterator for Iter<'a> {
self.buffer.clear();
match self.input.read_until(b'\n', &mut self.buffer) {
Ok(0) | Err(_) => return None,
- Ok(_) => ()
+ Ok(_) => (),
}
let buffer = String::from_utf8_lossy(&self.buffer);
let mut split_tokens: Vec<char> = Vec::new();
let tokens = buffer.split(|c: char| {
- if c.is_whitespace() { split_tokens.push(c); true } else { false }
- }).collect::<Vec<_>>();
+ if c.is_whitespace() {
+ split_tokens.push(c);
+ true
+ } else {
+ false
+ }
+ })
+ .collect::<Vec<_>>();
if log_enabled!(Info) {
info!("Original: `{}`", buffer);
@@ -59,141 +65,170 @@ impl<'a> Iterator for Iter<'a> {
}
match &tokens[..tokens.len() - 1] {
- [date, time, "-->", nick, host, "has", "joined", channel, _..]
- => return Some(Ok(Event {
- ty: Type::Join {
- nick: nick.to_owned().into(),
- mask: Some(strip_one(host).into()),
- },
- channel: Some(channel.to_owned().into()),
- time: parse_time(&self.context, date, time)
- })),
- [date, time, "<--", nick, host, "has", "left", channel, reason..]
- => return Some(Ok(Event {
- ty: Type::Part {
- nick: nick.to_owned().into(),
- mask: Some(strip_one(host).into()),
- reason: Some(strip_one(&rejoin(reason, &split_tokens[8..])).into()),
- },
- channel: Some(channel.to_owned().into()),
- time: parse_time(&self.context, date, time)
- })),
- [date, time, "<--", nick, host, "has", "quit", reason..]
- => return Some(Ok(Event {
- ty: Type::Quit {
- nick: nick.to_owned().into(),
- mask: Some(strip_one(host).into()),
- reason: Some(strip_one(&rejoin(reason, &split_tokens[7..])).into()),
- },
- time: parse_time(&self.context, date, time),
- channel: self.context.channel.clone().map(Into::into)
- })),
- [date, time, "--", notice, content..]
- if notice.starts_with("Notice(")
- => return Some(Ok(Event {
- ty: Type::Notice {
- from: notice["Notice(".len()..notice.len() - 2].to_owned().into(),
- content: rejoin(content, &split_tokens[4..]),
- },
- time: parse_time(&self.context, date, time),
- channel: self.context.channel.clone().map(Into::into)
- })),
- [date, time, "--", "irc:", "disconnected", "from", "server", _..]
- => return Some(Ok(Event {
- ty: Type::Disconnect,
- time: parse_time(&self.context, date, time),
- channel: self.context.channel.clone().map(Into::into)
- })),
- [date, time, "--", nick, verb, "now", "known", "as", new_nick]
- if verb == "is" || verb == "are"
- => return Some(Ok(Event {
- ty: Type::Nick {
- old_nick: nick.to_owned().into(),
- new_nick: new_nick.to_owned().into()
- },
- time: parse_time(&self.context, date, time),
- channel: self.context.channel.clone().map(Into::into)
- })),
- [date, time, sp, "*", nick, msg..]
- if sp.clone().is_empty()
- => return Some(Ok(Event {
- ty: Type::Action {
- from: nick.to_owned().into(),
- content: rejoin(msg, &split_tokens[5..]),
- },
- time: parse_time(&self.context, date, time),
- channel: self.context.channel.clone().map(Into::into)
- })),
- [date, time, nick, msg..]
- => return Some(Ok(Event {
- ty: Type::Msg {
- from: nick.to_owned().into(),
- content: rejoin(msg, &split_tokens[3..]),
- },
- time: parse_time(&self.context, date, time),
- channel: self.context.channel.clone().map(Into::into)
- })),
- _ => ()
+ [date, time, "-->", nick, host, "has", "joined", channel, _..] => {
+ return Some(Ok(Event {
+ ty: Type::Join {
+ nick: nick.to_owned().into(),
+ mask: Some(strip_one(host).into()),
+ },
+ channel: Some(channel.to_owned().into()),
+ time: parse_time(&self.context, date, time),
+ }))
+ }
+ [date, time, "<--", nick, host, "has", "left", channel, reason..] => {
+ return Some(Ok(Event {
+ ty: Type::Part {
+ nick: nick.to_owned().into(),
+ mask: Some(strip_one(host).into()),
+ reason: Some(strip_one(&rejoin(reason, &split_tokens[8..])).into()),
+ },
+ channel: Some(channel.to_owned().into()),
+ time: parse_time(&self.context, date, time),
+ }))
+ }
+ [date, time, "<--", nick, host, "has", "quit", reason..] => {
+ return Some(Ok(Event {
+ ty: Type::Quit {
+ nick: nick.to_owned().into(),
+ mask: Some(strip_one(host).into()),
+ reason: Some(strip_one(&rejoin(reason, &split_tokens[7..])).into()),
+ },
+ time: parse_time(&self.context, date, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ [date, time, "--", notice, content..] if notice.starts_with("Notice(") => {
+ return Some(Ok(Event {
+ ty: Type::Notice {
+ from: notice["Notice(".len()..notice.len() - 2].to_owned().into(),
+ content: rejoin(content, &split_tokens[4..]),
+ },
+ time: parse_time(&self.context, date, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ [date, time, "--", "irc:", "disconnected", "from", "server", _..] => {
+ return Some(Ok(Event {
+ ty: Type::Disconnect,
+ time: parse_time(&self.context, date, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ [date, time, "--", nick, verb, "now", "known", "as", new_nick] if verb == "is" ||
+ verb == "are" => {
+ return Some(Ok(Event {
+ ty: Type::Nick {
+ old_nick: nick.to_owned().into(),
+ new_nick: new_nick.to_owned().into(),
+ },
+ time: parse_time(&self.context, date, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ [date, time, sp, "*", nick, msg..] if sp.clone().is_empty() => {
+ return Some(Ok(Event {
+ ty: Type::Action {
+ from: nick.to_owned().into(),
+ content: rejoin(msg, &split_tokens[5..]),
+ },
+ time: parse_time(&self.context, date, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ [date, time, nick, msg..] => {
+ return Some(Ok(Event {
+ ty: Type::Msg {
+ from: nick.to_owned().into(),
+ content: rejoin(msg, &split_tokens[3..]),
+ },
+ time: parse_time(&self.context, date, time),
+ channel: self.context.channel.clone().map(Into::into),
+ }))
+ }
+ _ => (),
}
}
}
}
impl Decode for Weechat {
- fn decode<'a>(&'a mut self, context: &'a Context, input: &'a mut BufRead) -> Box<Iterator<Item = ::Result<Event<'a>>> + 'a> {
+ fn decode<'a>(&'a mut self,
+ context: &'a Context,
+ input: &'a mut BufRead)
+ -> Box<Iterator<Item = ::Result<Event<'a>>> + 'a> {
Box::new(Iter {
context: context,
input: input,
- buffer: Vec::new()
+ buffer: Vec::new(),
})
}
}
impl Encode for Weechat {
- fn encode<'a>(&'a self, context: &'a Context, mut output: &'a mut Write, event: &'a Event) -> ::Result<()> {
+ fn encode<'a>(&'a self,
+ context: &'a Context,
+ mut output: &'a mut Write,
+ event: &'a Event)
+ -> ::Result<()> {
match event {
&Event { ty: Type::Msg { ref from, ref content, .. }, ref time, .. } => {
- try!(writeln!(&mut output, "{}\t{}\t{}",
- time.with_format(&context.timezone, TIME_DATE_FORMAT), from, content))
- },
+ try!(writeln!(&mut output,
+ "{}\t{}\t{}",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT),
+ from,
+ content))
+ }
&Event { ty: Type::Action { ref from, ref content, .. }, ref time, .. } => {
- try!(writeln!(&mut output, "{}\t *\t{} {}",
- time.with_format(&context.timezone, TIME_DATE_FORMAT), from, content))
- },
+ try!(writeln!(&mut output,
+ "{}\t *\t{} {}",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT),
+ from,
+ content))
+ }
&Event { ty: Type::Join { ref nick, ref mask, .. }, ref channel, ref time } => {
- try!(writeln!(&mut output, "{}\t-->\t{} ({}) has joined {}",
- time.with_format(&context.timezone, TIME_DATE_FORMAT), nick,
- mask.as_ref().expect("Hostmask not present, but required."),
- channel.as_ref().expect("Channel not present, but required.")))
- },
+ try!(writeln!(&mut output,
+ "{}\t-->\t{} ({}) has joined {}",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT),
+ nick,
+ mask.as_ref().expect("Hostmask not present, but required."),
+ channel.as_ref().expect("Channel not present, but required.")))
+ }
&Event { ty: Type::Part { ref nick, ref mask, ref reason }, ref channel, ref time } => {
- try!(write!(&mut output, "{}\t<--\t{} ({}) has left {}",
- time.with_format(&context.timezone, TIME_DATE_FORMAT), nick,
- mask.as_ref().expect("Hostmask not present, but required."),
- channel.as_ref().expect("Channel not present, but required.")));
+ try!(write!(&mut output,
+ "{}\t<--\t{} ({}) has left {}",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT),
+ nick,
+ mask.as_ref().expect("Hostmask not present, but required."),
+ channel.as_ref().expect("Channel not present, but required.")));
if reason.is_some() && reason.as_ref().unwrap().len() > 0 {
try!(write!(&mut output, " ({})", reason.as_ref().unwrap()));
}
try!(write!(&mut output, "\n"))
- },
+ }
&Event { ty: Type::Quit { ref nick, ref mask, ref reason }, ref time, .. } => {
- try!(write!(&mut output, "{}\t<--\t{} ({}) has quit",
- time.with_format(&context.timezone, TIME_DATE_FORMAT), nick,
- mask.as_ref().expect("Hostmask not present, but required.")));
+ try!(write!(&mut output,
+ "{}\t<--\t{} ({}) has quit",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT),
+ nick,
+ mask.as_ref().expect("Hostmask not present, but required.")));
if reason.is_some() && reason.as_ref().unwrap().len() > 0 {
try!(write!(&mut output, " ({})", reason.as_ref().unwrap()));
}
try!(write!(&mut output, "\n"))
- },
+ }
&Event { ty: Type::Disconnect, ref time, .. } => {
- try!(writeln!(&mut output, "{}\t--\tirc: disconnected from server",
- time.with_format(&context.timezone, TIME_DATE_FORMAT)))
- },
+ try!(writeln!(&mut output,
+ "{}\t--\tirc: disconnected from server",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT)))
+ }
&Event { ty: Type::Notice { ref from, ref content }, ref time, .. } => {
- try!(writeln!(&mut output, "{}\t--\tNotice({}): {}",
- time.with_format(&context.timezone, TIME_DATE_FORMAT), from, content))
- },
- _ => ()
+ try!(writeln!(&mut output,
+ "{}\t--\tNotice({}): {}",
+ time.with_format(&context.timezone, TIME_DATE_FORMAT),
+ from,
+ content))
+ }
+ _ => (),
}
Ok(())
}
diff --git a/src/lambda.rs b/src/lambda.rs
new file mode 100644
index 0000000..cf7b1ec
--- /dev/null
+++ b/src/lambda.rs
@@ -0,0 +1,18 @@
+// I'm only a little sorry for this.
+
+// Inline definition of anonymous functions. Examples:
+// l!(42;)
+// l!(i32 := 42)
+// l!(i: i32 := i + 42)
+// l!(i: i32, j: i32 -> i32 := i + j)
+#[macro_export]
+macro_rules! l {
+ ($body: expr) => ({ fn f() { $body } f });
+ ($res: ty := $body: expr) => ({ fn f() -> $res { $body } f });
+ ($($n: ident: $t: ty),+ := $body: expr) => ({
+ fn f($($n: $t),+) { $body } f
+ });
+ ($($n: ident: $t: ty),+ -> $res: ty := $body: expr) => ({
+ fn f($($n: $t),+) -> $res { $body } f
+ })
+}
diff --git a/src/lazy.rs b/src/lazy.rs
new file mode 100644
index 0000000..b4f801b
--- /dev/null
+++ b/src/lazy.rs
@@ -0,0 +1,74 @@
+#![macro_escape]
+
+use std::ptr;
+use std::cell::UnsafeCell;
+use std::ops::Deref;
+use std::boxed::FnBox;
+
+pub enum State<V> {
+ Evaluated(V),
+ Evaluating,
+ Unevaluated(Box<FnBox() -> V>)
+}
+
+pub struct Lazy<V> {
+ state: UnsafeCell<State<V>>,
+}
+
+impl<V> Lazy<V> {
+ pub fn new(f: Box<FnBox() -> V>) -> Lazy<V> {
+ Lazy { state: UnsafeCell::new(State::Unevaluated(f)) }
+ }
+
+ pub fn ready(v: V) -> Lazy<V> {
+ Lazy { state: UnsafeCell::new(State::Evaluated(v)) }
+ }
+
+ pub fn force(&mut self) {
+ self.value();
+ }
+
+ pub fn unwrap(self) -> V {
+ unsafe {
+ match self.state.into_inner() {
+ State::Unevaluated(f) => f(),
+ State::Evaluating => panic!("Illegal state, can't call unwrap during evaluation"),
+ State::Evaluated(v) => v
+ }
+ }
+ }
+
+ pub fn value(&self) -> &V {
+ unsafe {
+ let state = self.state.get();
+ match *state {
+ State::Evaluated(ref v) => v,
+ State::Evaluating => panic!("Illegal state, can't call value during evaluation"),
+ State::Unevaluated(_) => {
+ if let State::Unevaluated(f) = ptr::replace(state, State::Evaluating) {
+ ptr::replace(state, State::Evaluated(f()));
+ }
+ if let State::Evaluated(ref v) = *state { return v }
+ unreachable!()
+ }
+ }
+ }
+ }
+}
+
+impl<V> Deref for Lazy<V> {
+ type Target = V;
+ fn deref(&self) -> &V { self.value() }
+}
+
+#[macro_export]
+macro_rules! lazy {
+ (@as_expr $val: expr) => { $val };
+ ($($val: tt)*) => { Lazy::new(Box::new(move || lazy![@as_expr { $($val)* }])) };
+}
+
+#[macro_export]
+macro_rules! eager {
+ (@as_expr $val: expr) => { $val };
+ ($($val: tt)*) => { Lazy::<_>::ready(eager![@as_expr { $($val)* }]) };
+}
diff --git a/src/lib.rs b/src/lib.rs
index e1b2db5..4772428 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -15,7 +15,7 @@
#![feature(slice_patterns)]
extern crate chrono;
#[macro_use]
-extern crate log as l;
+extern crate log;
extern crate rustc_serialize;
extern crate bincode;
extern crate rmp;
@@ -26,9 +26,9 @@ pub mod format;
pub mod context;
use std::convert::From;
-use std::{ io, result };
+use std::{io, result};
use std::error::Error;
-use std::fmt::{ self, Display, Formatter };
+use std::fmt::{self, Display, Formatter};
use chrono::format::ParseError;
@@ -42,7 +42,7 @@ pub enum IlcError {
BincodeEncode,
MsgpackEncode(msgpack::encode::Error),
MsgpackDecode(msgpack::decode::Error),
- Io(io::Error)
+ Io(io::Error),
}
impl Display for IlcError {
@@ -61,7 +61,7 @@ impl Error for IlcError {
&BincodeEncode => "error while encoding to binary",
&MsgpackDecode(_) => "error while decoding from msgpack",
&MsgpackEncode(_) => "error while encoding to msgpack",
- &Io(_) => "error during input/output"
+ &Io(_) => "error during input/output",
}
}
@@ -74,15 +74,19 @@ impl Error for IlcError {
&BincodeEncode => None,
&MsgpackDecode(ref e) => Some(e),
&MsgpackEncode(ref e) => Some(e),
- &Io(ref e) => Some(e)
+ &Io(ref e) => Some(e),
}
}
}
impl From<ParseError> for IlcError {
- fn from(err: ParseError) -> IlcError { IlcError::Chrono(err) }
+ fn from(err: ParseError) -> IlcError {
+ IlcError::Chrono(err)
+ }
}
impl From<io::Error> for IlcError {
- fn from(err: io::Error) -> IlcError { IlcError::Io(err) }
+ fn from(err: io::Error) -> IlcError {
+ IlcError::Io(err)
+ }
}
diff --git a/src/main.rs b/src/main.rs
index df4d790..0f4215f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -23,7 +23,7 @@ extern crate env_logger;
extern crate glob;
extern crate blist;
-use clap::{ Arg, App, AppSettings, SubCommand };
+use clap::{App, AppSettings, Arg, SubCommand};
mod chain;
mod ageset;
@@ -32,77 +32,86 @@ mod app;
fn main() {
env_logger::init().unwrap();
let args = App::new("ilc")
- .version(crate_version!())
- .setting(AppSettings::GlobalVersion)
- .setting(AppSettings::VersionlessSubcommands)
- .setting(AppSettings::ArgRequiredElseHelp)
- .author("Till Höppner <till@hoeppner.ws>")
- .about("A converter and statistics utility for IRC log files")
- .arg(Arg::with_name("timezone")
- .help("UTC offset in the direction of the western hemisphere")
- .global(true)
- .takes_value(true)
- .long("timezone")
- .short("t"))
- .arg(Arg::with_name("date")
- .help("Override the date for this log, ISO 8601, YYYY-MM-DD")
- .global(true)
- .takes_value(true)
- .long("date")
- .short("d"))
- .arg(Arg::with_name("infer_date")
- .help("Try to use the filename as date for the current log")
- .global(true)
- .long("infer-date"))
- .arg(Arg::with_name("channel")
- .help("Set a channel for the current log")
- .global(true)
- .takes_value(true)
- .long("channel")
- .short("c"))
- .arg(Arg::with_name("input_format")
- .help("Set the input format for the current log")
- .global(true)
- .takes_value(true)
- .long("inf"))
- .arg(Arg::with_name("output_format")
- .help("Set the output format for the current log")
- .global(true)
- .takes_value(true)
- .long("outf"))
- .arg(Arg::with_name("input_files")
- .help("Specify an input file, instead of stdin")
- .global(true)
- .takes_value(true).multiple(true)
- .long("input")
- .short("i"))
- .arg(Arg::with_name("output_file")
- .help("Specify an output file, instead of stdout")
- .global(true)
- .takes_value(true)
- .long("output")
- .short("o"))
- .subcommand(SubCommand::with_name("parse")
- .about("Parse the input, checking the format"))
- .subcommand(SubCommand::with_name("convert")
- .about("Convert from a source to a target format"))
- .subcommand(SubCommand::with_name("freq")
- .about("Analyse the activity of users by certain metrics")
- .arg(Arg::with_name("count")
- .help("The number of items to be displayed")
- .takes_value(true)
- .long("count")))
- .subcommand(SubCommand::with_name("seen")
- .about("Print the last line a nick was active")
- .arg(Arg::with_name("nick")
- .help("The nick you're looking for")
- .takes_value(true).required(true)
- .index(1)))
- .subcommand(SubCommand::with_name("sort")
- .about("Sorts a log by time"))
- .subcommand(SubCommand::with_name("dedup")
- .about("Removes duplicate log entries in close proximity"))
- .get_matches();
+ .version(crate_version!())
+ .setting(AppSettings::GlobalVersion)
+ .setting(AppSettings::VersionlessSubcommands)
+ .setting(AppSettings::ArgRequiredElseHelp)
+ .author("Till Höppner <till@hoeppner.ws>")
+ .about("A converter and statistics utility for IRC log files")
+ .arg(Arg::with_name("timezone")
+ .help("UTC offset in the direction of the western hemisphere")
+ .global(true)
+ .takes_value(true)
+ .long("timezone")
+ .short("t"))
+ .arg(Arg::with_name("date")
+ .help("Override the date for this log, ISO 8601, YYYY-MM-DD")
+ .global(true)
+ .takes_value(true)
+ .long("date")
+ .short("d"))
+ .arg(Arg::with_name("infer_date")
+ .help("Try to use the filename as date for the current log")
+ .global(true)
+ .long("infer-date"))
+ .arg(Arg::with_name("channel")
+ .help("Set a channel for the current log")
+ .global(true)
+ .takes_value(true)
+ .long("channel")
+ .short("c"))
+ .arg(Arg::with_name("format")
+ .help("Set the input and output format for the current log")
+ .global(true)
+ .takes_value(true)
+ .long("format")
+ .short("f"))
+ .arg(Arg::with_name("input_format")
+ .help("Set the input format for the current log")
+ .global(true)
+ .conflicts_with("format")
+ .takes_value(true)
+ .long("inf"))
+ .arg(Arg::with_name("output_format")
+ .help("Set the output format for the current log")
+ .global(true)
+ .conflicts_with("format")
+ .takes_value(true)
+ .long("outf"))
+ .arg(Arg::with_name("input_files")
+ .help("Specify an input file, instead of stdin")
+ .global(true)
+ .takes_value(true)
+ .multiple(true)
+ .long("input")
+ .short("i"))
+ .arg(Arg::with_name("output_file")
+ .help("Specify an output file, instead of stdout")
+ .global(true)
+ .takes_value(true)
+ .long("output")
+ .short("o"))
+ .subcommand(SubCommand::with_name("parse")
+ .about("Parse the input, checking the format"))
+ .subcommand(SubCommand::with_name("convert")
+ .about("Convert from a source to a target format"))
+ .subcommand(SubCommand::with_name("freq")
+ .about("Analyse the activity of users by certain metrics")
+ .arg(Arg::with_name("count")
+ .help("The number of items to be displayed")
+ .takes_value(true)
+ .long("count")))
+ .subcommand(SubCommand::with_name("seen")
+ .about("Print the last line a nick was active")
+ .arg(Arg::with_name("nick")
+ .help("The nick you're looking for")
+ .takes_value(true)
+ .required(true)
+ .index(1)))
+ .subcommand(SubCommand::with_name("sort").about("Sorts a log by time"))
+ .subcommand(SubCommand::with_name("dedup")
+ .about("Removes duplicate log entries in close proximity"))
+ .get_matches();
match args.subcommand() {
("parse", Some(args)) => app::parse::parse(args),
@@ -112,6 +121,6 @@ fn main() {
("sort", Some(args)) => app::sort::sort(args),
("dedup", Some(args)) => app::dedup::dedup(args),
(sc, _) if !sc.is_empty() => panic!("Unimplemented subcommand `{}`, this is a bug", sc),
- _ => ()
+ _ => (),
}
}