diff options
author | Till Höppner | 2016-02-23 17:00:53 +0100 |
---|---|---|
committer | Till Höppner | 2016-02-24 18:36:04 +0100 |
commit | f01c278a26e0f248d188c10bdb852b0859b98b3b (patch) | |
tree | 3e729bf243011111a20b84f24aea3fbcb9f595f5 | |
parent | 815f31f5cef61709c50087c9f7601ea330929bb7 (diff) | |
download | ilc-f01c278a26e0f248d188c10bdb852b0859b98b3b.tar.gz ilc-f01c278a26e0f248d188c10bdb852b0859b98b3b.tar.xz ilc-f01c278a26e0f248d188c10bdb852b0859b98b3b.zip |
Test CI
-rw-r--r-- | .travis.yml | 93 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | appveyor.yml | 65 | ||||
-rw-r--r-- | ci/before_deploy.sh | 15 | ||||
-rw-r--r-- | ci/install.sh | 43 | ||||
-rw-r--r-- | ci/script.sh | 36 | ||||
-rw-r--r-- | rustfmt.toml | 3 | ||||
-rw-r--r-- | src/ageset.rs | 16 | ||||
-rw-r--r-- | src/app/freq.rs | 51 | ||||
-rw-r--r-- | src/app/mod.rs | 138 | ||||
-rw-r--r-- | src/chain.rs | 36 | ||||
-rw-r--r-- | src/context.rs | 2 | ||||
-rw-r--r-- | src/event.rs | 73 | ||||
-rw-r--r-- | src/format/binary.rs | 25 | ||||
-rw-r--r-- | src/format/energymech.rs | 299 | ||||
-rw-r--r-- | src/format/irssi.rs | 205 | ||||
-rw-r--r-- | src/format/mod.rs | 38 | ||||
-rw-r--r-- | src/format/msgpack.rs | 26 | ||||
-rw-r--r-- | src/format/weechat.rs | 265 | ||||
-rw-r--r-- | src/lambda.rs | 18 | ||||
-rw-r--r-- | src/lazy.rs | 74 | ||||
-rw-r--r-- | src/lib.rs | 20 | ||||
-rw-r--r-- | src/main.rs | 155 |
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 @@ -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..3dd9a1b --- /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 -V + ;; + *) + 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)* }]) }; +} @@ -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), - _ => () + _ => (), } } |