diff --git a/Cargo.lock b/Cargo.lock index 994e83d..b3e0d80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,11 @@ dependencies = [ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bitflags" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitflags" version = "1.0.1" @@ -100,17 +105,30 @@ dependencies = [ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nix" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pipecolor" version = "0.2.4-pre" dependencies = [ "atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "timeout-readwrite 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -254,6 +272,14 @@ dependencies = [ "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "timeout-readwrite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "toml" version = "0.4.5" @@ -325,6 +351,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum atty 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "af80143d6f7608d746df1520709e5d141c96f240b0e62b0aa41bdfb53374d9d4" "checksum backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbbf59b1c43eefa8c3ede390fcc36820b4999f7914104015be25025e0d62af2" "checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661" +"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" "checksum cc 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "2b4911e4bdcb4100c7680e7e854ff38e23f1b34d4d9e079efae3da2801341ffc" "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" @@ -333,6 +360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" "checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" +"checksum nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32" "checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0" "checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408" "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" @@ -350,6 +378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" +"checksum timeout-readwrite 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "20b6a7dabffd4febd7b0b95f3c1ba00fb58dd523db07837e93c5c90e34636629" "checksum toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a7540f4ffc193e0d3c94121edb19b055670d369f77d5804db11ae053a45b6e7e" "checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" diff --git a/Cargo.toml b/Cargo.toml index a484545..ec3ff51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,14 +13,16 @@ travis-ci = { repository = "dalance/pipecolor" } codecov = { repository = "dalance/pipecolor", branch = "master", service = "github" } [dependencies] -atty = "0.2.8" -error-chain = "0.11.0" -regex = "0.2.10" -serde = "1.0.33" -serde_derive = "1.0.33" -structopt = "0.2.5" -termion = "1.5.1" -toml = "0.4.5" +atty = "0.2.8" +error-chain = "0.11.0" +memchr = "2.0.1" +regex = "0.2.10" +serde = "1.0.33" +serde_derive = "1.0.33" +structopt = "0.2.5" +termion = "1.5.1" +timeout-readwrite = "0.1.0" +toml = "0.4.5" [package.metadata.release] dev-version-ext = "pre" diff --git a/src/colorize.rs b/src/colorize.rs index bc4fb0c..6431d64 100644 --- a/src/colorize.rs +++ b/src/colorize.rs @@ -1,7 +1,6 @@ use regex::Regex; use termion::color; use termion::color::Color; -use toml; // ------------------------------------------------------------------------------------------------- // Config @@ -67,7 +66,7 @@ pub fn colorize(mut s: String, config: &Config) -> Result<(String, Option let mut pos = Vec::new(); let mut line_idx = None; - for (i,line) in config.lines.iter().enumerate() { + for (i, line) in config.lines.iter().enumerate() { let cap = line.pat.captures(&s); if let Some(cap) = cap { line_idx = Some(i); @@ -143,7 +142,7 @@ fn conv_color(s: &Option<&String>) -> Result> { "Yellow" => Box::new(color::Yellow), _ => { bail!(format!("failed to parse color name '{}'", s)); - }, + } } } else { Box::new(color::Reset) @@ -158,6 +157,7 @@ fn conv_color(s: &Option<&String>) -> Result> { #[cfg(test)] mod tests { use super::*; + use toml; pub static TEST_CONFIG: &'static str = r#" [[lines]] @@ -215,6 +215,9 @@ mod tests { fn test_colorize_fail() { let config: Config = toml::from_str(TEST_CONFIG2).unwrap(); let ret = colorize(String::from("A123 456 789 xyz"), &config); - assert_eq!(&format!("{:?}", ret)[0..50], "Err(Error(Msg(\"failed to parse color name \\\'xxx\\\'\""); + assert_eq!( + &format!("{:?}", ret)[0..50], + "Err(Error(Msg(\"failed to parse color name \\\'xxx\\\'\"" + ); } } diff --git a/src/main.rs b/src/main.rs index 66424ac..3f7c2cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ extern crate atty; #[macro_use] extern crate error_chain; +extern crate memchr; extern crate regex; extern crate serde; #[macro_use] @@ -8,17 +9,22 @@ extern crate serde_derive; #[macro_use] extern crate structopt; extern crate termion; +extern crate timeout_readwrite; extern crate toml; mod colorize; +mod read_timeout; use colorize::{colorize, Config}; use atty::Stream; +use read_timeout::read_line_timeout; use std::env::home_dir; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use std::path::{Path, PathBuf}; +use std::time::Duration; use structopt::{clap, StructOpt}; +use timeout_readwrite::TimeoutReader; // ------------------------------------------------------------------------------------------------- // Option @@ -42,6 +48,10 @@ pub struct Opt { #[structopt(short = "c", long = "config", parse(from_os_str))] pub config: Option, + /// Timeout of stdin by milliseconds + #[structopt(short = "t", long = "timeout", default_value = "500")] + pub timeout: u64, + /// Show verbose message #[structopt(short = "v", long = "verbose")] pub verbose: bool, @@ -89,8 +99,11 @@ fn get_reader_file(path: &Path) -> Result> { Ok(Box::new(BufReader::new(f))) } -fn get_reader_stdin() -> Result> { - Ok(Box::new(BufReader::new(stdin()))) +fn get_reader_stdin(timeout_millis: u64) -> Result> { + Ok(Box::new(BufReader::new(TimeoutReader::new( + stdin(), + Duration::from_millis(timeout_millis), + )))) } fn get_config_path(opt: &Opt) -> Option { @@ -105,10 +118,16 @@ fn get_config_path(opt: &Opt) -> Option { None } -fn output(reader: &mut BufRead, writer: &mut Write, use_color: bool, config: &Config, opt: &Opt) -> Result<()> { +fn output( + reader: &mut BufRead, + writer: &mut Write, + use_color: bool, + config: &Config, + opt: &Opt, +) -> Result<()> { let mut s = String::new(); loop { - match reader.read_line(&mut s) { + match read_line_timeout(reader, &mut s) { Ok(0) => break, Ok(_) => { if use_color { @@ -121,7 +140,7 @@ fn output(reader: &mut BufRead, writer: &mut Write, use_color: bool, config: &Co } } let _ = writer.write(s.as_bytes()); - //let _ = writer.flush(); + let _ = writer.flush(); s.clear(); } Err(_) => break, @@ -169,7 +188,7 @@ fn run_opt(opt: &Opt) -> Result<()> { let mut writer = BufWriter::new(stdout()); if opt.files.is_empty() { - let mut reader = get_reader_stdin()?; + let mut reader = get_reader_stdin(opt.timeout)?; let _ = output(&mut *reader, writer.get_mut(), use_color, &config, &opt)?; } else { for f in &opt.files { diff --git a/src/read_timeout.rs b/src/read_timeout.rs new file mode 100644 index 0000000..63ae3b7 --- /dev/null +++ b/src/read_timeout.rs @@ -0,0 +1,81 @@ +use memchr; +use std::io::{BufRead, Error, ErrorKind, Result}; + +// ------------------------------------------------------------------------------------------------- +// Functions +// ------------------------------------------------------------------------------------------------- + +struct Guard<'a> { + buf: &'a mut Vec, + len: usize, +} + +// ------------------------------------------------------------------------------------------------- +// Functions +// ------------------------------------------------------------------------------------------------- + +pub fn read_until_timeout( + r: &mut R, + delim: u8, + buf: &mut Vec, +) -> Result { + let mut read = 0; + let empty = vec![]; + loop { + let (done, used) = { + let available = match r.fill_buf() { + Ok(n) => n, + Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(ref e) if e.kind() == ErrorKind::TimedOut => &empty, + Err(e) => return Err(e), + }; + match memchr::memchr(delim, available) { + Some(i) => { + buf.extend_from_slice(&available[..i + 1]); + (true, i + 1) + } + None => { + buf.extend_from_slice(available); + (false, available.len()) + } + } + }; + r.consume(used); + read += used; + if done || used == 0 { + return Ok(read); + } + } +} + +pub fn read_line_timeout(r: &mut R, buf: &mut String) -> Result { + append_to_string(buf, |b| read_until_timeout(r, b'\n', b)) +} + +fn append_to_string(buf: &mut String, f: F) -> Result +where + F: FnOnce(&mut Vec) -> Result, +{ + unsafe { + let mut g = Guard { + len: buf.len(), + buf: buf.as_mut_vec(), + }; + let ret = f(g.buf); + if String::from_utf8(g.buf[g.len..].to_vec()).is_err() { + ret.and_then(|_| { + Err(Error::new( + ErrorKind::InvalidData, + "stream did not contain valid UTF-8", + )) + }) + } else { + g.len = g.buf.len(); + ret + } + } +} + +// ------------------------------------------------------------------------------------------------- +// Test +// -------------------------------------------------------------------------------------------------