diff --git a/.gitignore b/.gitignore index bac8a05..1b3250e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ book/ *~ +.idea +.DS_Store +target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4dc998b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,140 @@ +[root] +name = "rust-book" +version = "0.0.1" +dependencies = [ + "docopt 0.6.82 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "aho-corasick" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "docopt" +version = "0.6.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.73 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-serialize" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "strsim" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "thread-id" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "utf8-ranges" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "walkdir" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2b3fb52b09c1710b961acb35390d514be82e4ac96a9969a8e38565a29b878dc9" +"checksum docopt 0.6.82 (registry+https://github.com/rust-lang/crates.io-index)" = "8f20016093b4e545dccf6ad4a01099de0b695f9bc99b08210e68f6425db2d37d" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f" +"checksum libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "23e3757828fa702a20072c37ff47938e9dd331b92fac6e223d26d4b7a55f7ee2" +"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" +"checksum regex 0.1.73 (registry+https://github.com/rust-lang/crates.io-index)" = "56b7ee9f764ecf412c6e2fff779bca4b22980517ae335a21aeaf4e32625a5df2" +"checksum regex-syntax 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "31040aad7470ad9d8c46302dcffba337bb4289ca5da2e3cd6e37b64109a85199" +"checksum rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "6159e4e6e559c81bd706afe9c8fd68f547d3e851ce12e76b1de7914bab61691b" +"checksum strsim 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4d73a2c36a4d095ed1a6df5cbeac159863173447f7a82b3f4757426844ab825" +"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" +"checksum thread_local 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "55dd963dbaeadc08aa7266bf7f91c3154a7805e32bb94b820b769d2ef3b4744d" +"checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum walkdir 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7ad450634b9022aeb0e8e7f1c79c1ded92d0fc5bee831033d148479771bd218d" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7c0ab66 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "rust-book" +version = "0.0.1" +authors = ["Steve Klabnik "] +description = "The Rust Book" + +[dependencies] +walkdir = "0.1.5" +docopt = "0.6.82" +rustc-serialize = "0.3.19" \ No newline at end of file diff --git a/src/bin/lfp.rs b/src/bin/lfp.rs new file mode 100644 index 0000000..d7aed1c --- /dev/null +++ b/src/bin/lfp.rs @@ -0,0 +1,246 @@ +extern crate rustc_serialize; +extern crate docopt; +use docopt::Docopt; +extern crate walkdir; +use std::{path, fs, io}; +use std::io::{BufRead, Write}; + +macro_rules! println_stderr( + ($($arg:tt)*) => ( + match writeln!(&mut ::std::io::stderr(), $($arg)* ) { + Ok(_) => {}, + Err(x) => panic!("Unable to write to stderr: {}", x), + } + ) +); + +fn main () { + let args: Args = Docopt::new(USAGE) + .and_then(|d| d.decode()) + .unwrap_or_else(|e| e.exit()); + + let src_dir = &path::Path::new(&args.arg_src_dir); + let found_errs = walkdir::WalkDir::new(src_dir) + .min_depth(1) + .into_iter() + .map(|entry| { + match entry { + Ok(entry) => entry, + Err(err) => { + println_stderr!("{:?}", err); + std::process::exit(911) + }, + } + }) + .map(|entry| { + let path = entry.path(); + if is_file_of_interest(path) { + let err_vec = lint_file(path); + for err in &err_vec { + match *err { + LintingError::LineOfInterest(line_num, ref line) => + println_stderr!("{}:{}\t{}", path.display(), line_num, line), + LintingError::UnableToOpenFile => + println_stderr!("Unable to open {}.", path.display()), + } + } + !err_vec.is_empty() + } else { + false + } + }) + .collect::>() + .iter() + .any(|result| *result); + + if found_errs { + std::process::exit(1) + } else { + std::process::exit(0) + } +} + +const USAGE: &'static str = " +counter +Usage: + lfp + lfp (-h | --help) +Options: + -h --help Show this screen. +"; + +#[derive(Debug, RustcDecodable)] +struct Args { + arg_src_dir: String, +} + +fn lint_file(path: &path::Path) -> Vec { + match fs::File::open(path) { + Ok(file) => lint_lines(io::BufReader::new(&file).lines()), + Err(_) => vec![LintingError::UnableToOpenFile], + } +} + +fn lint_lines(lines: I) -> Vec + where I: Iterator> { + lines + .enumerate() + .map(|(line_num, line)| { + let raw_line = line.unwrap(); + if is_line_of_interest(&raw_line) { + Err(LintingError::LineOfInterest(line_num, raw_line)) + } else { + Ok(()) + } + }) + .filter(|result| result.is_err()) + .map(|result| result.unwrap_err()) + .collect() +} + +fn is_file_of_interest(path: &path::Path) -> bool { + path.extension() + .map_or(false, |ext| ext == "md") +} + +fn is_line_of_interest(line: &str) -> bool { + !line.split_whitespace() + .filter(|sub_string| sub_string.contains("file://")) + .filter(|file_url| !file_url.contains("file:///projects/")) + .collect::>() + .is_empty() +} + +#[derive(Debug)] +enum LintingError { + UnableToOpenFile, + LineOfInterest(usize, String) +} + +#[cfg(test)] +mod tests { + + use std::path; + + #[test] + fn lint_file_returns_a_vec_with_errs_when_lines_of_interest_are_found() { + let string = r#" + $ cargo run + Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game) + Running `target/guessing_game` + Guess the number! + The secret number is: 61 + Please input your guess. + 10 + You guessed: 10 + Too small! + Please input your guess. + 99 + You guessed: 99 + Too big! + Please input your guess. + foo + Please input your guess. + 61 + You guessed: 61 + You win! + $ cargo run + Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game) + Running `target/debug/guessing_game` + Guess the number! + The secret number is: 7 + Please input your guess. + 4 + You guessed: 4 + $ cargo run + Running `target/debug/guessing_game` + Guess the number! + The secret number is: 83 + Please input your guess. + 5 + $ cargo run + Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game) + Running `target/debug/guessing_game` + Hello, world! + "#; + + let raw_lines = string.to_string(); + let lines = raw_lines.lines().map(|line| { + Ok(line.to_string()) + }); + + let result_vec = super::lint_lines(lines); + + assert!(!result_vec.is_empty()); + assert_eq!(3, result_vec.len()); + } + + #[test] + fn lint_file_returns_an_empty_vec_when_no_lines_of_interest_are_found() { + let string = r#" + $ cargo run + Compiling guessing_game v0.1.0 (file:///projects/guessing_game) + Running `target/guessing_game` + Guess the number! + The secret number is: 61 + Please input your guess. + 10 + You guessed: 10 + Too small! + Please input your guess. + 99 + You guessed: 99 + Too big! + Please input your guess. + foo + Please input your guess. + 61 + You guessed: 61 + You win! + "#; + + let raw_lines = string.to_string(); + let lines = raw_lines.lines().map(|line| { + Ok(line.to_string()) + }); + + let result_vec = super::lint_lines(lines); + + assert!(result_vec.is_empty()); + } + + #[test] + fn is_file_of_interest_returns_false_when_the_path_is_a_directory() { + let uninteresting_fn = "src/img"; + + assert!(!super::is_file_of_interest(path::Path::new(uninteresting_fn))); + } + + #[test] + fn is_file_of_interest_returns_false_when_the_filename_does_not_have_the_md_extension() { + let uninteresting_fn = "src/img/foo1.png"; + + assert!(!super::is_file_of_interest(path::Path::new(uninteresting_fn))); + } + + #[test] + fn is_file_of_interest_returns_true_when_the_filename_has_the_md_extension() { + let interesting_fn = "src/ch01-00-introduction.md"; + + assert!(super::is_file_of_interest(path::Path::new(interesting_fn))); + } + + #[test] + fn is_line_of_interest_does_not_report_a_line_if_the_line_contains_a_file_url_which_is_directly_followed_by_the_project_path() { + let sample_line = "Compiling guessing_game v0.1.0 (file:///projects/guessing_game)"; + + assert!(!super::is_line_of_interest(sample_line)); + } + + #[test] + fn is_line_of_interest_reports_a_line_if_the_line_contains_a_file_url_which_is_not_directly_followed_by_the_project_path() { + let sample_line = "Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)"; + + assert!(super::is_line_of_interest(sample_line)); + } +} \ No newline at end of file