From d265980e8e06101c07dd3265dd2d66d834b09c58 Mon Sep 17 00:00:00 2001 From: Kevin K Date: Mon, 24 Aug 2015 17:14:51 -0400 Subject: [PATCH] fix(Symlinks): adds ability to optionally follow symlinks while counting Closes #6 Closes #7 --- Cargo.lock | 2 +- src/config.rs | 29 ++++++++++++++++---- src/count/counts.rs | 67 ++++++++++++++++++++++----------------------- src/fsutil.rs | 66 ++++++++++++++++++++++---------------------- src/main.rs | 12 ++++++-- 5 files changed, 100 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c76534..a1bf521 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ [root] name = "cargo-count" -version = "0.1.0" +version = "0.1.1" dependencies = [ "ansi_term 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "clap 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/src/config.rs b/src/config.rs index a3ba36a..45c213c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,5 @@ use std::env; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use clap::ArgMatches; @@ -21,9 +21,10 @@ pub struct Config<'a> { pub thousands: Option, pub utf8_rule: Utf8Rule, pub usafe: bool, - pub exclude: Vec<&'a str>, + pub exclude: Vec, pub exts: Option>, - pub to_count: Vec + pub to_count: Vec, + pub follow_links: bool } impl<'a> Config<'a> { @@ -40,7 +41,24 @@ impl<'a> Config<'a> { thousands: m.value_of("sep").map(|s| s.chars().nth(0).unwrap() ), usafe: m.is_present("unsafe-statistics"), utf8_rule: value_t!(m.value_of("rule"), Utf8Rule).unwrap_or(Utf8Rule::Strict), - exclude: m.values_of("paths").unwrap_or(vec![".git"]), + exclude: if let Some(v) = m.values_of("paths") { + debugln!("There are some"); + let mut ret = vec![]; + for p in v { + let pb = Path::new(p); + if pb.is_relative() { + ret.push(cli_try!(env::current_dir()).join(p)); + } else { + ret.push(pb.to_path_buf()); + } + } + debugln!("found files or dirs: {:?}", ret); + ret.push(cli_try!(env::current_dir()).join(".git")); + ret + } else { + debugln!("There aren't any, adding .git"); + vec!(cli_try!(env::current_dir()).join(".git")) + }, to_count: if let Some(v) = m.values_of("to_count") { debugln!("There are some"); let mut ret = vec![]; @@ -53,7 +71,8 @@ impl<'a> Config<'a> { debugln!("There aren't any, using cwd"); vec![cli_try!(env::current_dir())] }, - exts: m.values_of("exts") + exts: m.values_of("exts"), + follow_links: m.is_present("follow-symlinks") }) } } \ No newline at end of file diff --git a/src/count/counts.rs b/src/count/counts.rs index 46e53d5..91e2987 100644 --- a/src/count/counts.rs +++ b/src/count/counts.rs @@ -41,46 +41,43 @@ impl<'c> Counts<'c> { debugln!("executing; fill_from; cfg={:?}", self.cfg); for path in &self.cfg.to_count { debugln!("iter; path={:?};", path); - if let Some(f) = path.to_str() { - let files = fsutil::get_all_files(f, &self.cfg.exclude); + let mut files = vec![]; + fsutil::get_all_files(&mut files, &path, &self.cfg.exclude, self.cfg.follow_links); - for file in files { - debugln!("iter; file={:?};", file); - let extension = match Path::new(&file).extension() { - Some(result) => { - if let Some(ref exts) = self.cfg.exts { - if !exts.contains(&result.to_str().unwrap_or("")) { continue } - } - result.to_str().unwrap() - }, - None => continue, - }; - - debugln!("found extension: {:?}", extension); - if let Some(pos_lang) = Language::from_ext(extension) { - debugln!("Extension is valid"); - let mut found = false; - debugln!("Searching for previous entries of that type"); - for l in self.counts.iter_mut() { - if l.lang.extension() == extension { - debugln!("Found"); - found = true; - l.add_file(PathBuf::from(&file)); - break; - } + for file in files { + debugln!("iter; file={:?};", file); + let extension = match Path::new(&file).extension() { + Some(result) => { + if let Some(ref exts) = self.cfg.exts { + if !exts.contains(&result.to_str().unwrap_or("")) { continue } } - if !found { - debugln!("Not found, creating new"); - let mut c = Count::new(pos_lang, self.cfg.thousands); - c.add_file(PathBuf::from(&file)); - self.counts.push(c); + result.to_str().unwrap() + }, + None => continue, + }; + + debugln!("found extension: {:?}", extension); + if let Some(pos_lang) = Language::from_ext(extension) { + debugln!("Extension is valid"); + let mut found = false; + debugln!("Searching for previous entries of that type"); + for l in self.counts.iter_mut() { + if l.lang.extension() == extension { + debugln!("Found"); + found = true; + l.add_file(PathBuf::from(&file)); + break; } - } else { - debugln!("extension wasn't valid"); } + if !found { + debugln!("Not found, creating new"); + let mut c = Count::new(pos_lang, self.cfg.thousands); + c.add_file(PathBuf::from(&file)); + self.counts.push(c); + } + } else { + debugln!("extension wasn't valid"); } - } else { - debugln!("path couldn't be converted to a str"); } } } diff --git a/src/fsutil.rs b/src/fsutil.rs index b45d43d..f03184b 100644 --- a/src/fsutil.rs +++ b/src/fsutil.rs @@ -1,56 +1,56 @@ use std::fs; -use std::fs::metadata; +use std::io::Result; use std::path::PathBuf; use glob; -pub fn get_all_files<'a>(path: &'a str, exclude: &Vec<&'a str>) -> Vec { +pub fn get_all_files<'a>(v: &mut Vec, path: &PathBuf, exclude: &Vec, follow_links: bool) { debugln!("executing; get_all_files; path={:?}; exclude={:?};", path, exclude); - let mut files = vec![]; + if exclude.contains(path) { + return + } debugln!("Getting metadata"); - if let Ok(result) = metadata(&path) { + if let Ok(result) = get_metadata(&path, follow_links) { debugln!("Found"); if result.is_dir() { debugln!("It's a dir"); let dir = fs::read_dir(&path).unwrap(); - 'file: for entry in dir { + for entry in dir { let entry = entry.unwrap(); let file_path = entry.path(); - let file_str = file_path.to_str().expect("file_path isn't a valid str"); - let file_string = file_str.to_owned(); - let path_metadata = metadata(&file_string).unwrap(); - - for ignored in exclude { - debugln!("iter; ignored={:?}", ignored); - if file_str.contains(ignored) { - debugln!("iter; ignored={:?}", ignored); - continue 'file; - } - } - if path_metadata.is_dir() { - for file in get_all_files(&*file_string, &exclude) { - files.push(file); - } - } else if path_metadata.is_file() { - files.push(PathBuf::from(file_str)); - } + get_all_files(v, &file_path.to_path_buf(), &exclude, follow_links); } } else { debugln!("It's a file"); - if !exclude.contains(&path) { - debugln!("It's not excluded"); - files.push(PathBuf::from(path)); - } else { - debugln!("It's excluded"); - } + v.push(path.clone()); } } else { - for path_buf in glob::glob(&path).ok().expect("failed to get files from glob") { - let file_path = path_buf.unwrap(); - files.push(file_path); + for path_buf in glob::glob(path.to_str().unwrap_or("")).ok().expect("failed to get files from glob") { + if let Ok(file_path) = path_buf { + if let Ok(result) = get_metadata(&file_path, follow_links) { + if result.is_dir() { + debugln!("It's a dir"); + let dir = fs::read_dir(&path).unwrap(); + for entry in dir { + let entry = entry.unwrap(); + let file_path = entry.path(); + get_all_files(v, &file_path.to_path_buf(), &exclude, follow_links); + } + } else { + debugln!("It's a file"); + v.push(path.clone()); + } + } + } } } +} - files +fn get_metadata(path: &PathBuf, follow_links: bool) -> Result { + if follow_links { + fs::metadata(path) + } else { + fs::symlink_metadata(path) + } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 435c968..20bf7a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,12 +49,20 @@ fn main() { -l, --language [exts]... 'Only count these languges (by source code extension){n}\ (i.e. \'-l js py cpp\')' -v, --verbose 'Print verbose output' + -S, --follow-symlinks 'Follows symlinks and counts source files it finds{n}(Defaults to false when omitted)' [to_count]... 'The files or directories (including children) to count{n}\ (defaults to current working directory when omitted)'") .arg(Arg::from_usage("-s, --separator [sep] 'Set the thousands separator for pretty printing'") .validator(single_char)) .arg(Arg::from_usage("--utf8-rule [rule] 'Sets the UTF-8 parsing rule (Defaults to \'strict\'){n}'") - .possible_values(&UTF8_RULES))) + .possible_values(&UTF8_RULES)) + .after_help("When using '--exclude ' the path given can either be relative to the current \n\ + directory, or absolute. When '' is a file, it must be relative to the current \n\ + directory or it will not be found. Example, if the current directory has a child \n\ + directory named 'target' with a child fild 'test.rs' and you use `--exclude target/test.rs' \n\ + \n\ + Globs are also supported. For example, to eclude 'test.rs' files from all child directories \n\ + of the current directory you could do '--exclude */test.rs'.")) .get_matches(); if let Some(m) = m.subcommand_matches("count") { @@ -68,7 +76,7 @@ fn main() { fn execute(cfg: Config) -> CliResult<()> { debugln!("executing; cmd=execute;"); - verboseln!(cfg, "{}: {}", Format::Warning("Excluding"), cfg.exclude.connect(", ")); + verboseln!(cfg, "{}: {:?}", Format::Warning("Excluding"), cfg.exclude); verbose!(cfg, "{}", if cfg.exts.is_some() { format!("{} including files with extension: {}\n", Format::Warning("Only"), cfg.exts.as_ref().unwrap().connect(", "))