diff --git a/Cargo.lock b/Cargo.lock index 62c61ce..15397e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ [root] name = "tokei" -version = "1.5.1" +version = "1.6.0" dependencies = [ "clap 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/src/fsutil.rs b/src/fsutil.rs index 5b6d8ac..1019255 100644 --- a/src/fsutil.rs +++ b/src/fsutil.rs @@ -2,6 +2,21 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::{self, BufRead, BufReader, Read, Write}; +use std::fs::File; +use std::path::Path; +use std::thread; +use std::time::Duration; +use std::sync::mpsc::channel; +use clap::App; +use glob::glob; +use walkdir::{WalkDir, WalkDirIterator}; + +use language::{Language, LanguageName}; +use language::LanguageName::*; + pub fn contains_comments(file: &str, comment: &str, comment_end: &str) -> bool { let mut in_comments: usize = 0; 'window: for chars in file.chars().collect::>().windows(comment.len()) { @@ -26,6 +41,197 @@ pub fn contains_comments(file: &str, comment: &str, comment_end: &str) -> bool { in_comments != 0 } +pub fn get_all_files<'a, I: Iterator>(paths: I, + languages: &mut BTreeMap, + ignored_directories: Vec<&str>) { + for path in paths { + if let Err(_) = Path::new(path).metadata() { + if let Ok(paths) = glob(path) { + for path in paths { + let path = unwrap_rs_cont!(path); + let mut language = if unwrap_opt_cont!(path.to_str()).contains("Makefile") { + languages.get_mut(&Makefile).unwrap() + } else { + unwrap_opt_cont!(languages.get_mut(&unwrap_opt_cont!(get_language(&path)))) + }; + + language.files.push(path.to_owned()); + } + } else { + + } + } else { + let walker = WalkDir::new(path).into_iter().filter_entry(|entry| { + for ig in ignored_directories.to_owned() { + if entry.path().to_str().unwrap().contains(&*ig) { + return false; + } + } + true + }); + + for entry in walker { + let entry = unwrap_rs_cont!(entry); + + let mut language = if unwrap_opt_cont!(entry.path().to_str()) + .contains("Makefile") { + languages.get_mut(&Makefile).unwrap() + } else { + unwrap_opt_cont!(languages.get_mut(&unwrap_opt_cont!(get_language(entry.path())))) + }; + + language.files.push(entry.path().to_owned()); + } + } + } +} + +pub fn get_extension>(path: P) -> Option { + let path = path.as_ref(); + let extension = match path.extension() { + Some(extension_os) => { + match extension_os.to_str() { + Some(ext) => ext, + None => return None, + } + } + None => { + match get_filetype_from_shebang(path) { + Some(ext) => ext, + None => return None, + } + } + }; + Some(extension.to_lowercase()) +} + +pub fn get_filetype_from_shebang>(file: P) -> Option<&'static str> { + let file = match File::open(file) { + Ok(file) => file, + _ => return None, + }; + let mut buf = BufReader::new(file); + let mut line = String::new(); + let _ = buf.read_line(&mut line); + + let mut words = line.split_whitespace(); + match words.next() { + Some("#!/bin/sh") => Some("sh"), + Some("#!/bin/csh") => Some("csh"), + Some("#!/usr/bin/perl") => Some("pl"), + Some("#!/usr/bin/env") => { + match words.next() { + Some("python") | Some("python2") | Some("python3") => Some("py"), + Some("sh") => Some("sh"), + _ => None, + } + } + _ => None, + } +} + +pub fn get_language>(entry: P) -> Option { + if let Some(extension) = get_extension(entry) { + match &*extension { + "as" => Some(ActionScript), + "s" => Some(Assembly), + "bat" => Some(Batch), + "btm" => Some(Batch), + "cmd" => Some(Batch), + "bash" => Some(Bash), + "sh" => Some(Bash), + "c" => Some(C), + "csh" => Some(CShell), + "ec" => Some(C), + "pgc" => Some(C), + "cs" => Some(CSharp), + "clj" => Some(Clojure), + "coffee" => Some(CoffeeScript), + "cfm" => Some(ColdFusion), + "cfc" => Some(ColdFusionScript), + "cc" => Some(Cpp), + "cpp" => Some(Cpp), + "cxx" => Some(Cpp), + "pcc" => Some(Cpp), + "c++" => Some(Cpp), + "css" => Some(Css), + "d" => Some(D), + "dart" => Some(Dart), + "dts" => Some(DeviceTree), + "dtsi" => Some(DeviceTree), + "el" => Some(Lisp), + "lisp" => Some(Lisp), + "lsp" => Some(Lisp), + "lua" => Some(Lua), + "sc" => Some(Lisp), + "f" => Some(FortranLegacy), + "f77" => Some(FortranLegacy), + "for" => Some(FortranLegacy), + "ftn" => Some(FortranLegacy), + "pfo" => Some(FortranLegacy), + "f90" => Some(FortranModern), + "f95" => Some(FortranModern), + "f03" => Some(FortranModern), + "f08" => Some(FortranModern), + "go" => Some(Go), + "h" => Some(CHeader), + "hs" => Some(Haskell), + "hpp" => Some(CppHeader), + "hh" => Some(CppHeader), + "html" => Some(Html), + "hxx" => Some(CppHeader), + "jai" => Some(Jai), + "java" => Some(Java), + "js" => Some(JavaScript), + "jl" => Some(Julia), + "json" => Some(Json), + "jsx" => Some(Jsx), + "lds" => Some(LinkerScript), + "less" => Some(Less), + "m" => Some(ObjectiveC), + "md" => Some(Markdown), + "markdown" => Some(Markdown), + "ml" => Some(OCaml), + "mli" => Some(OCaml), + "mm" => Some(ObjectiveCpp), + "makefile" => Some(Makefile), + "mustache" => Some(Mustache), + "php" => Some(Php), + "pas" => Some(Pascal), + "pl" => Some(Perl), + "text" => Some(Text), + "txt" => Some(Text), + "polly" => Some(Polly), + "proto" => Some(Protobuf), + "py" => Some(Python), + "r" => Some(R), + "rake" => Some(Ruby), + "rb" => Some(Ruby), + "rhtml" => Some(RubyHtml), + "rs" => Some(Rust), + "sass" => Some(Sass), + "scss" => Some(Sass), + "scala" => Some(Scala), + "sml" => Some(Sml), + "sql" => Some(Sql), + "swift" => Some(Swift), + "tex" => Some(Tex), + "sty" => Some(Tex), + "toml" => Some(Toml), + "ts" => Some(TypeScript), + "vim" => Some(VimScript), + "xml" => Some(Xml), + "yaml" => Some(Yaml), + "yml" => Some(Yaml), + "zsh" => Some(Zsh), + _ => None, + } + } else { + None + } +} + #[allow(dead_code, unused_imports)] mod tests { use super::*; @@ -48,4 +254,24 @@ mod tests { fn comment_start_in_line() { assert!(contains_comments("Hello /* World", "/*", "*/")); } + + #[test] + fn comment_start_in_quotes_ocaml() { + assert!(contains_comments("Hello \"(*\" World", "(*", "*)")); + } + + #[test] + fn both_comments_in_quotes_ocaml() { + assert!(!contains_comments("Hello \"(**)\" World", "(*", "*)")); + } + + #[test] + fn both_comments_in_line_ocaml() { + assert!(!contains_comments("Hello (**) World", "(*", "*)")); + } + + #[test] + fn comment_start_in_line_ocaml() { + assert!(contains_comments("Hello (* World", "(*", "*)")); + } } diff --git a/src/language.rs b/src/language.rs index 10eb469..9b8c49b 100644 --- a/src/language.rs +++ b/src/language.rs @@ -7,90 +7,80 @@ use std::fmt; use std::path::PathBuf; use std::ops::AddAssign; use stats::Stats; +use std::fs::File; +use std::path::Path; -#[derive(Debug, Default)] -pub struct Language<'a> { - pub name: &'a str, - pub line_comment: &'a str, - pub multi_line: &'a str, - pub multi_line_end: &'a str, +#[derive(Debug, Default, Clone, Eq, Ord, PartialEq, PartialOrd)] +pub struct Language { + pub line_comment: &'static str, + pub multi_line: &'static str, + pub multi_line_end: &'static str, pub files: Vec, pub code: usize, pub comments: usize, pub blanks: usize, pub lines: usize, pub total: usize, - pub printed: bool, } +impl Language { + pub fn new(line_comment: &'static str, + multi_line: &'static str, + multi_line_end: &'static str) + -> Self { -impl<'a> Language<'a> { - pub fn new(name: &'a str, - line_comment: &'a str, - multi_line: &'a str, - multi_line_end: &'a str) - -> RefCell { - - RefCell::new(Language { - name: name, + Language { line_comment: line_comment, multi_line: multi_line, multi_line_end: multi_line_end, ..Self::default() - }) + } } - pub fn new_raw(name: &'a str) -> Self { - Language { name: name, ..Self::default() } - } - - pub fn new_c(name: &'a str) -> RefCell { - RefCell::new(Language { - name: name, + pub fn new_c() -> Self { + Language { line_comment: "//", multi_line: "/*", multi_line_end: "*/", ..Self::default() - }) + } } - pub fn new_html(name: &'a str) -> RefCell { - RefCell::new(Language { - name: name, + pub fn new_html() -> Self { + Language { line_comment: "", ..Self::default() - }) + } } - pub fn new_blank(name: &'a str) -> RefCell { - RefCell::new(Language { name: name, ..Self::default() }) + pub fn new_blank() -> Self { + Language { ..Self::default() } } - pub fn new_single(name: &'a str, line_comment: &'a str) -> RefCell { - RefCell::new(Language { - name: name, - line_comment: line_comment, - ..Self::default() - }) + pub fn new_single(line_comment: &'static str) -> Self { + Language { line_comment: line_comment, ..Self::default() } } - pub fn new_multi(name: &'a str, multi_line: &'a str, multi_line_end: &'a str) -> RefCell { - RefCell::new(Language { - name: name, + pub fn new_multi(multi_line: &'static str, multi_line_end: &'static str) -> Self { + Language { multi_line: multi_line, multi_line_end: multi_line_end, ..Self::default() - }) + } } pub fn is_empty(&self) -> bool { self.code == 0 && self.comments == 0 && self.blanks == 0 && self.lines == 0 } + + pub fn is_blank(&self) -> bool { + self.line_comment == "" && self.multi_line == "" + } } -impl<'a> fmt::Display for Language<'a> { +impl fmt::Display for Language { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let total = if self.total == 0 { self.files.len() @@ -99,7 +89,7 @@ impl<'a> fmt::Display for Language<'a> { }; write!(f, " {: <18} {: >6} {:>12} {:>12} {:>12} {:>12}", - self.name, + "CHANGE", total, self.lines, self.blanks, @@ -107,8 +97,9 @@ impl<'a> fmt::Display for Language<'a> { self.code) } } + // Adding languages to the raw total. -impl<'a, 'b> AddAssign<&'b Language<'a>> for Language<'a> { +impl<'a> AddAssign<&'a Language> for Language { fn add_assign(&mut self, rhs: &Self) { self.total += rhs.files.len(); self.lines += rhs.lines; @@ -118,8 +109,19 @@ impl<'a, 'b> AddAssign<&'b Language<'a>> for Language<'a> { } } +// Adding languages to the raw total. +impl<'a> AddAssign<&'a mut Language> for Language { + fn add_assign(&mut self, rhs: &mut Self) { + self.total += rhs.files.len(); + self.lines += rhs.lines; + self.comments += rhs.comments; + self.blanks += rhs.blanks; + self.code += rhs.code; + } +} + // Adding a file to the language. -impl<'a> AddAssign for Language<'a> { +impl AddAssign for Language { fn add_assign(&mut self, rhs: Stats) { self.lines += rhs.lines; self.code += rhs.code; @@ -127,3 +129,147 @@ impl<'a> AddAssign for Language<'a> { self.blanks += rhs.blanks; } } + + +#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] +pub enum LanguageName { + ActionScript, + Assembly, + Bash, + Batch, + C, + CHeader, + CSharp, + CShell, + Clojure, + CoffeeScript, + ColdFusion, + ColdFusionScript, + Cpp, + CppHeader, + Css, + D, + Dart, + DeviceTree, + Lisp, + FortranLegacy, + FortranModern, + Go, + Haskell, + Html, + Jai, + Java, + JavaScript, + Julia, + Json, + Jsx, + Less, + LinkerScript, + Lua, + Makefile, + Markdown, + Mustache, + ObjectiveC, + ObjectiveCpp, + OCaml, + Php, + Pascal, + Polly, + Perl, + Protobuf, + Python, + R, + Ruby, + RubyHtml, + Rust, + Sass, + Scala, + Sml, + Sql, + Swift, + Tex, + Text, + Toml, + TypeScript, + VimScript, + Xml, + Yaml, + Zsh, +} + +impl LanguageName { + fn name(&self) -> &'static str { + use self::LanguageName::*; + + match *self { + ActionScript => "ActionScript", + Assembly => "Assembly", + Bash => "BASH", + Batch => "Batch", + C => "C", + CHeader => "C Header", + CSharp => "C#", + CShell => "C Shell", + Clojure => "Clojure", + CoffeeScript => "CoffeeScript", + ColdFusion => "ColdFusion", + ColdFusionScript => "ColdFusion CFScript", + Cpp => "C++", + CppHeader => "C++ Header", + Css => "CSS", + D => "D", + Dart => "Dart", + DeviceTree => "Device Tree", + Lisp => "LISP", + FortranLegacy => "FORTRAN Legacy", + FortranModern => "FORTRAN Modern", + Go => "Go", + Haskell => "Haskell", + Html => "HTML", + Jai => "JAI", + Java => "Java", + JavaScript => "JavaScript", + Julia => "Julia", + Json => "JSON", + Jsx => "JSX", + Less => "LESS", + LinkerScript => "LD Script", + Lua => "Lua", + Makefile => "Makefile", + Markdown => "Markdown", + Mustache => "Mustache", + ObjectiveC => "Objective C", + ObjectiveCpp => "Objective C++", + OCaml => "OCaml", + Php => "PHP", + Pascal => "Pascal", + Polly => "Polly", + Perl => "Perl", + Protobuf => "Protocol Buffers", + Python => "Python", + R => "R", + Ruby => "Ruby", + RubyHtml => "Ruby HTML", + Rust => "Rust", + Sass => "Sass", + Scala => "Scala", + Sml => "Standard ML", + Sql => "SQL", + Swift => "Swift", + Tex => "TeX", + Text => "Plain Text", + Toml => "TOML", + TypeScript => "TypeScript", + VimScript => "Vim Script", + Xml => "XML", + Yaml => "YAML", + Zsh => "Zsh", + } + } +} + +impl fmt::Display for LanguageName { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name()) + } +} diff --git a/src/main.rs b/src/main.rs index 90af90b..f44e6e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,17 +16,21 @@ pub mod stats; use std::cell::RefCell; use std::collections::BTreeMap; -use std::io::{BufRead, BufReader, Read}; +use std::io::{self, BufRead, BufReader, Read, Write}; use std::fs::File; use std::path::Path; +use std::thread; +use std::time::Duration; +use std::sync::mpsc::channel; use clap::App; use glob::glob; use walkdir::{WalkDir, WalkDirIterator}; -use language::Language; +use fsutil::*; +use language::{Language, LanguageName}; +use language::LanguageName::*; -use fsutil::contains_comments; const ROW: &'static str = "-----------------------------------------------------------------------\ --------"; const BLANKS: &'static str = "blanks"; @@ -39,170 +43,78 @@ fn main() { let yaml = load_yaml!("../cli.yml"); let matches = App::from_yaml(yaml).get_matches(); - let action_script = Language::new_c("ActionScript"); - let asm = Language::new_single("Assembly", ";"); - let bash = Language::new_single("BASH", "#"); - let batch = Language::new_single("Batch", "REM"); - let c = Language::new_c("C"); - let c_header = Language::new_c("C Header"); - let c_sharp = Language::new_c("C#"); - let c_shell = Language::new_single("C Shell", "#"); - let clojure = Language::new_single("Clojure", ";,#,#_"); - let coffee_script = Language::new("CoffeeScript", "#", "###", "###"); - let cold_fusion = Language::new_multi("ColdFusion", ""); - let cf_script = Language::new_c("ColdFusion CFScript"); - let cpp = Language::new_c("C++"); - let cpp_header = Language::new_c("C++ Header"); - let css = Language::new_c("CSS"); - let d = Language::new_c("D"); - let dart = Language::new_c("Dart"); - let device_tree = Language::new_c("Device Tree"); - let lisp = Language::new("LISP", ";", "#|", "|#"); - let fortran_legacy = Language::new_single("FORTRAN Legacy", "c,C,!,*"); - let fortran_modern = Language::new_single("FORTRAN Modern", "!"); - let go = Language::new_c("Go"); - let haskell = Language::new_single("Haskell", "--"); - let html = Language::new_html("HTML"); - let jai = Language::new_c("JAI"); - let java = Language::new_c("Java"); - let java_script = Language::new_c("JavaScript"); - let julia = Language::new("Julia", "#", "#=", "=#"); - let json = Language::new_blank("JSON"); - let jsx = Language::new_c("JSX"); - let less = Language::new_c("LESS"); - let linker_script = Language::new_c("LD Script"); - let lua = Language::new("Lua", "--", "--[[", "]]"); - let makefile = Language::new_single("Makefile", "#"); - let markdown = Language::new_blank("Markdown"); - let mustache = Language::new_multi("Mustache", "{{!", "}}"); - let objective_c = Language::new_c("Objective C"); - let objective_cpp = Language::new_c("Objective C++"); - let ocaml = Language::new_multi("OCaml", "(*", "*)"); - let php = Language::new("PHP", "#,//", "/*", "*/"); - let pascal = Language::new("Pascal", "//,(*", "{", "}"); - let polly = Language::new_html("Polly"); - let perl = Language::new("Perl", "#", "=", "=cut"); - let protobuf = Language::new_single("Protocol Buffers", "//"); - let python = Language::new("Python", "#", "'''", "'''"); - let r = Language::new_single("R", "#"); - let ruby = Language::new("Ruby", "#", "=begin", "=end"); - let ruby_html = Language::new_html("Ruby HTML"); - let rust = Language::new("Rust", "//,///,//!", "/*", "*/"); - let sass = Language::new_c("Sass"); - let sml = Language::new_multi("Standard ML", "(*", "*)"); - let sql = Language::new("SQL", "--", "/*", "*/"); - let swift = Language::new_c("Swift"); - let tex = Language::new_single("TeX", "%"); - let text = Language::new_blank("Plain Text"); - let toml = Language::new_single("TOML", "#"); - let type_script = Language::new_c("TypeScript"); - let vim_script = Language::new_single("Vim script", "\""); - let xml = Language::new_html("XML"); - let yaml = Language::new_single("YAML", "#"); - let zsh = Language::new_single("Zsh", "#"); + let files_option = matches.is_present(FILES); + let language_option = matches.is_present("languages"); // Languages are placed inside a BTreeMap, in order to print alphabetically by default - let languages = btreemap! { - "as" => &action_script, - "s" => &asm, - "bat" => &batch, - "btm" => &batch, - "cmd" => &batch, - "bash" => &bash, - "sh" => &bash, - "c" => &c, - "csh" => &c_shell, - "ec" => &c, - "pgc" => &c, - "cs" => &c_sharp, - "clj" => &clojure, - "coffee" => &coffee_script, - "cfm" => &cold_fusion, - "cfc" => &cf_script, - "cc" => &cpp, - "cpp" => &cpp, - "cxx" => &cpp, - "pcc" => &cpp, - "c++" => &cpp, - "css" => &css, - "d" => &d, - "dart" => &dart, - "dts" => &device_tree, - "dtsi" => &device_tree, - "el" => &lisp, - "lisp" => &lisp, - "lsp" => &lisp, - "lua" => &lua, - "sc" => &lisp, - "f" => &fortran_legacy, - "f77" => &fortran_legacy, - "for" => &fortran_legacy, - "ftn" => &fortran_legacy, - "pfo" => &fortran_legacy, - "f90" => &fortran_modern, - "f95" => &fortran_modern, - "f03" => &fortran_modern, - "f08" => &fortran_modern, - "go" => &go, - "h" => &c_header, - "hs" => &haskell, - "hpp" => &cpp_header, - "hh" => &cpp_header, - "html" => &html, - "hxx" => &cpp_header, - "jai" => &jai, - "java" => &java, - "js" => &java_script, - "jl" => &julia, - "json" => &json, - "jsx" => &jsx, - "lds" => &linker_script, - "less" => &less, - "m" => &objective_c, - "md" => &markdown, - "markdown" => &markdown, - "ml" => &ocaml, - "mli" => &ocaml, - "mm" => &objective_cpp, - "makefile" => &makefile, - "mustache" => &mustache, - "php" => &php, - "pas" => &pascal, - "pl" => &perl, - "text" => &text, - "txt" => &text, - "polly" => &polly, - "proto" => &protobuf, - "py" => &python, - "r" => &r, - "rake" => &ruby, - "rb" => &ruby, - "rhtml" => &ruby_html, - "rs" => &rust, - "sass" => &sass, - "scss" => &sass, - "sml" => &sml, - "sql" => &sql, - "swift" => &swift, - "tex" => &tex, - "sty" => &tex, - "toml" => &toml, - "ts" => &type_script, - "vim" => &vim_script, - "xml" => &xml, - "yaml" => &yaml, - "yml" => &yaml, - "zsh" => &zsh, + let mut languages = btreemap! { + ActionScript => Language::new_c(), + Assembly => Language::new_single(";"), + Bash => Language::new_single("#"), + Batch => Language::new_single("REM"), + C => Language::new_c(), + CHeader => Language::new_c(), + CSharp => Language::new_c(), + CShell => Language::new_single("#"), + Clojure => Language::new_single(";,#,#_"), + CoffeeScript => Language::new("#", "###", "###"), + ColdFusion => Language::new_multi(""), + ColdFusionScript => Language::new_c(), + Cpp => Language::new_c(), + CppHeader => Language::new_c(), + Css => Language::new_c(), + D => Language::new_c(), + Dart => Language::new_c(), + DeviceTree => Language::new_c(), + Lisp => Language::new(";", "#|", "|#"), + FortranLegacy => Language::new_single("c,C,!,*"), + FortranModern => Language::new_single("!"), + Go => Language::new_c(), + Haskell => Language::new_single("--"), + Html => Language::new_html(), + Jai => Language::new_c(), + Java => Language::new_c(), + JavaScript => Language::new_c(), + Julia => Language::new("#", "#=", "=#"), + Json => Language::new_blank(), + Jsx => Language::new_c(), + Less => Language::new_c(), + LinkerScript => Language::new_c(), + Lua => Language::new("--", "--[[", "]]"), + Makefile => Language::new_single("#"), + Markdown => Language::new_blank(), + Mustache => Language::new_multi("{{!", "}}"), + ObjectiveC => Language::new_c(), + ObjectiveCpp => Language::new_c(), + OCaml => Language::new_multi("(*", "*)"), + Php => Language::new("#,//", "/*", "*/"), + Pascal => Language::new("//,(*", "{", "}"), + Polly => Language::new_html(), + Perl => Language::new("#", "=", "=cut"), + Protobuf => Language::new_single("//"), + Python => Language::new("#", "'''", "'''"), + R => Language::new_single("#"), + Ruby => Language::new("#", "=begin", "=end"), + RubyHtml => Language::new_html(), + Rust => Language::new("//,///,//!", "/*", "*/"), + Sass => Language::new_c(), + Sml => Language::new_multi("(*", "*)"), + Sql => Language::new("--", "/*", "*/"), + Swift => Language::new_c(), + Tex => Language::new_single("%"), + Text => Language::new_blank(), + Toml => Language::new_single("#"), + TypeScript => Language::new_c(), + VimScript => Language::new_single("\""), + Xml => Language::new_html(), + Yaml => Language::new_single("#"), + Zsh => Language::new_single("#"), }; // Print every supported language. - if matches.is_present("languages") { - for language in languages.values() { - let mut language = language.borrow_mut(); - if !language.printed { - println!("{:<25}", language.name); - language.printed = true; - } + if language_option { + for key in languages.keys() { + println!("{:<25}", key); } return; } @@ -210,10 +122,10 @@ fn main() { let paths = matches.values_of("input").unwrap(); let ignored_directories = { - let mut ignored_directories = vec![String::from(".git")]; + let mut ignored_directories = vec![".git"]; if let Some(user_ignored) = matches.values_of("exclude") { for ignored in user_ignored { - ignored_directories.push(ignored.to_owned()); + ignored_directories.push(ignored); } } ignored_directories @@ -238,25 +150,35 @@ fn main() { "Code"); println!("{}", ROW); - get_all_files(paths, &languages, ignored_directories); + get_all_files(paths, &mut languages, ignored_directories); - let mut total = Language::new_raw("Total"); - for language_ref in languages.values() { - let mut language = language_ref.borrow_mut(); + let mut total = Language::new_blank(); + for (name, language) in &mut languages { - if language.printed { + if language.files.len() == 0 { continue; } - let is_blank_lang = if language.line_comment == "" && language.multi_line == "" { - true - } else { - false - }; + + let (tx, rx) = channel(); + let child = thread::spawn(move || { + loop { + if let Ok(_) = rx.try_recv() { + break; + } + // print!("\x1B[?25l;"); + print!(" Counting {} files. \r", name); + thread::sleep(Duration::from_millis(4)); + print!(" Counting {} files..\r", name); + thread::sleep(Duration::from_millis(4)); + print!(" Counting {} files...\r", name); + thread::sleep(Duration::from_millis(4)); + } + }); let files = language.files.clone(); for file in files { let mut contents = String::new(); - let is_fortran = language.name.contains("FORTRAN"); + let is_fortran = *name == FortranModern || *name == FortranLegacy; let mut stats = stats::Stats::new(unwrap_opt_cont!(file.to_str())); let _ = unwrap_rs_cont!(unwrap_rs_cont!(File::open(file)) .read_to_string(&mut contents)); @@ -265,7 +187,7 @@ fn main() { let lines = contents.lines(); - if is_blank_lang { + if language.is_blank() { stats.code += lines.count(); continue; } @@ -314,149 +236,54 @@ fn main() { stats.code += 1; } - if matches.is_present(FILES) { + if files_option { println!("{}", stats); } *language += stats; } + let _ = tx.send(()); + let _ = child.join(); + print!(" \r"); if !language.is_empty() { - language.printed = true; if let None = sort { - if matches.is_present(FILES) { + if files_option { println!("{}", ROW); - println!("{}", *language); + println!("{}", language); println!("{}", ROW); } else { - println!("{}", *language); + println!("{}", language); } } } - total += &*language; + + total += language; } if let Some(sort_category) = sort { - let mut unsorted_vec: Vec<(&&str, &&RefCell)> = languages.iter().collect(); + let mut sorted: Vec<&Language> = languages.values().collect(); match &*sort_category { - BLANKS => unsorted_vec.sort_by(|a, b| b.1.borrow().blanks.cmp(&a.1.borrow().blanks)), - COMMENTS => { - unsorted_vec.sort_by(|a, b| b.1.borrow().comments.cmp(&a.1.borrow().comments)) - } - CODE => unsorted_vec.sort_by(|a, b| b.1.borrow().code.cmp(&a.1.borrow().code)), - FILES => { - unsorted_vec.sort_by(|a, b| b.1.borrow().files.len().cmp(&a.1.borrow().files.len())) - } - TOTAL => unsorted_vec.sort_by(|a, b| b.1.borrow().lines.cmp(&a.1.borrow().lines)), + BLANKS => sorted.sort_by(|a, b| b.blanks.cmp(&a.blanks)), + COMMENTS => sorted.sort_by(|a, b| b.comments.cmp(&a.comments)), + CODE => sorted.sort_by(|a, b| b.code.cmp(&a.code)), + FILES => sorted.sort_by(|a, b| b.files.len().cmp(&a.files.len())), + TOTAL => sorted.sort_by(|a, b| b.lines.cmp(&a.lines)), _ => unreachable!(), } - for (_, language) in unsorted_vec { - - if !language.borrow().is_empty() && language.borrow().printed { - language.borrow_mut().printed = false; - println!("{}", *language.borrow()); + for language in sorted { + if !language.is_empty() { + println!("{}", *language); } } } - if !matches.is_present(FILES) { + if !files_option { println!("{}", ROW); } println!("{}", total); println!("{}", ROW); -} - - -fn get_all_files<'a, I: Iterator>(paths: I, - languages: &BTreeMap<&str, &RefCell>, - ignored_directories: Vec) { - for path in paths { - if let Err(_) = Path::new(path).metadata() { - if let Ok(paths) = glob(path) { - for path in paths { - let path = unwrap_rs_cont!(path); - let language = if unwrap_opt_cont!(path.to_str()).contains("Makefile") { - languages.get("makefile").unwrap() - } else { - let extension = unwrap_opt_cont!(get_extension(&path)); - unwrap_opt_cont!(languages.get(&*extension)) - }; - - language.borrow_mut().files.push(path.to_owned()); - } - } else { - - } - } else { - let walker = WalkDir::new(path).into_iter().filter_entry(|entry| { - for ig in ignored_directories.to_owned() { - if entry.path().to_str().unwrap().contains(&*ig) { - return false; - } - } - true - }); - - for entry in walker { - let entry = unwrap_rs_cont!(entry); - - let language = if unwrap_opt_cont!(entry.path().to_str()).contains("Makefile") { - languages.get("makefile").unwrap() - } else { - let extension = unwrap_opt_cont!(get_extension(entry.path())); - unwrap_opt_cont!(languages.get(&*extension)) - }; - - language.borrow_mut().files.push(entry.path().to_owned()); - } - } - } -} - - -fn get_filetype_from_shebang>(file: P) -> Option<&'static str> { - let file = match File::open(file) { - Ok(file) => file, - _ => return None, - }; - let mut buf = BufReader::new(file); - let mut line = String::new(); - let _ = buf.read_line(&mut line); - - let mut words = line.split_whitespace(); - match words.next() { - Some("#!/bin/sh") => Some("sh"), - Some("#!/bin/csh") => Some("csh"), - Some("#!/usr/bin/perl") => Some("pl"), - Some("#!/usr/bin/env") => { - match words.next() { - Some("python") | Some("python2") | Some("python3") => Some("py"), - Some("sh") => Some("sh"), - _ => None, - } - } - _ => None, - } -} - -fn get_extension>(path: P) -> Option { - let path = path.as_ref(); - let extension = match path.extension() { - Some(extension_os) => { - let extension = match extension_os.to_str() { - Some(ext) => ext, - None => return None, - }; - extension.to_lowercase() - } - None => { - match get_filetype_from_shebang(path) { - Some(ext) => String::from(ext).to_lowercase(), - None => return None, - } - } - }; - Some(extension) + println!("\x1B[?25h"); }