| // We have some long regex literals, so: |
| // ignore-tidy-linelength |
| |
| use std::io::BufRead; |
| use std::{fs, io, path}; |
| |
| use docopt::Docopt; |
| use serde::Deserialize; |
| |
| fn main() { |
| let args: Args = Docopt::new(USAGE) |
| .and_then(|d| d.deserialize()) |
| .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) => { |
| eprintln!("{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) => { |
| eprintln!( |
| "{}:{}\t{}", |
| path.display(), |
| line_num, |
| line |
| ) |
| } |
| LintingError::UnableToOpenFile => { |
| eprintln!("Unable to open {}.", path.display()) |
| } |
| } |
| } |
| !err_vec.is_empty() |
| } else { |
| false |
| } |
| }) |
| .collect::<Vec<_>>() |
| .iter() |
| .any(|result| *result); |
| |
| if found_errs { |
| std::process::exit(1) |
| } else { |
| std::process::exit(0) |
| } |
| } |
| |
| const USAGE: &str = " |
| counter |
| Usage: |
| lfp <src-dir> |
| lfp (-h | --help) |
| Options: |
| -h --help Show this screen. |
| "; |
| |
| #[derive(Debug, Deserialize)] |
| struct Args { |
| arg_src_dir: String, |
| } |
| |
| fn lint_file(path: &path::Path) -> Vec<LintingError> { |
| match fs::File::open(path) { |
| Ok(file) => lint_lines(io::BufReader::new(&file).lines()), |
| Err(_) => vec![LintingError::UnableToOpenFile], |
| } |
| } |
| |
| fn lint_lines<I>(lines: I) -> Vec<LintingError> |
| where |
| I: Iterator<Item = io::Result<String>>, |
| { |
| 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().any(|sub_string| { |
| sub_string.contains("file://") |
| && !sub_string.contains("file:///projects/") |
| && !sub_string.contains("file:///home/.cargo") |
| && !sub_string.contains("file:///home/.rustup") |
| }) |
| } |
| |
| #[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)); |
| } |
| } |