moved functions to fsutil, and switched from RefCell to an Enum based system

This commit is contained in:
Aaronepower 2016-05-12 15:58:03 +01:00
parent db2060e0f2
commit ebee004a8f
4 changed files with 536 additions and 337 deletions

2
Cargo.lock generated
View file

@ -1,6 +1,6 @@
[root] [root]
name = "tokei" name = "tokei"
version = "1.5.1" version = "1.6.0"
dependencies = [ dependencies = [
"clap 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "glob 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -2,6 +2,21 @@
// Use of this source code is governed by the MIT license that can be // Use of this source code is governed by the MIT license that can be
// found in the LICENSE file. // 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 { pub fn contains_comments(file: &str, comment: &str, comment_end: &str) -> bool {
let mut in_comments: usize = 0; let mut in_comments: usize = 0;
'window: for chars in file.chars().collect::<Vec<char>>().windows(comment.len()) { 'window: for chars in file.chars().collect::<Vec<char>>().windows(comment.len()) {
@ -26,6 +41,197 @@ pub fn contains_comments(file: &str, comment: &str, comment_end: &str) -> bool {
in_comments != 0 in_comments != 0
} }
pub fn get_all_files<'a, I: Iterator<Item = &'a str>>(paths: I,
languages: &mut BTreeMap<LanguageName,
Language>,
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<P: AsRef<Path>>(path: P) -> Option<String> {
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<P: AsRef<Path>>(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<P: AsRef<Path>>(entry: P) -> Option<LanguageName> {
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)] #[allow(dead_code, unused_imports)]
mod tests { mod tests {
use super::*; use super::*;
@ -48,4 +254,24 @@ mod tests {
fn comment_start_in_line() { fn comment_start_in_line() {
assert!(contains_comments("Hello /* World", "/*", "*/")); 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", "(*", "*)"));
}
} }

View file

@ -7,90 +7,80 @@ use std::fmt;
use std::path::PathBuf; use std::path::PathBuf;
use std::ops::AddAssign; use std::ops::AddAssign;
use stats::Stats; use stats::Stats;
use std::fs::File;
use std::path::Path;
#[derive(Debug, Default)] #[derive(Debug, Default, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub struct Language<'a> { pub struct Language {
pub name: &'a str, pub line_comment: &'static str,
pub line_comment: &'a str, pub multi_line: &'static str,
pub multi_line: &'a str, pub multi_line_end: &'static str,
pub multi_line_end: &'a str,
pub files: Vec<PathBuf>, pub files: Vec<PathBuf>,
pub code: usize, pub code: usize,
pub comments: usize, pub comments: usize,
pub blanks: usize, pub blanks: usize,
pub lines: usize, pub lines: usize,
pub total: 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> { Language {
pub fn new(name: &'a str,
line_comment: &'a str,
multi_line: &'a str,
multi_line_end: &'a str)
-> RefCell<Self> {
RefCell::new(Language {
name: name,
line_comment: line_comment, line_comment: line_comment,
multi_line: multi_line, multi_line: multi_line,
multi_line_end: multi_line_end, multi_line_end: multi_line_end,
..Self::default() ..Self::default()
}) }
} }
pub fn new_raw(name: &'a str) -> Self { pub fn new_c() -> Self {
Language { name: name, ..Self::default() } Language {
}
pub fn new_c(name: &'a str) -> RefCell<Self> {
RefCell::new(Language {
name: name,
line_comment: "//", line_comment: "//",
multi_line: "/*", multi_line: "/*",
multi_line_end: "*/", multi_line_end: "*/",
..Self::default() ..Self::default()
}) }
} }
pub fn new_html(name: &'a str) -> RefCell<Self> { pub fn new_html() -> Self {
RefCell::new(Language { Language {
name: name,
line_comment: "<!--", line_comment: "<!--",
multi_line: "<!--", multi_line: "<!--",
multi_line_end: "-->", multi_line_end: "-->",
..Self::default() ..Self::default()
}) }
} }
pub fn new_blank(name: &'a str) -> RefCell<Self> { pub fn new_blank() -> Self {
RefCell::new(Language { name: name, ..Self::default() }) Language { ..Self::default() }
} }
pub fn new_single(name: &'a str, line_comment: &'a str) -> RefCell<Self> { pub fn new_single(line_comment: &'static str) -> Self {
RefCell::new(Language { Language { line_comment: line_comment, ..Self::default() }
name: name,
line_comment: line_comment,
..Self::default()
})
} }
pub fn new_multi(name: &'a str, multi_line: &'a str, multi_line_end: &'a str) -> RefCell<Self> { pub fn new_multi(multi_line: &'static str, multi_line_end: &'static str) -> Self {
RefCell::new(Language { Language {
name: name,
multi_line: multi_line, multi_line: multi_line,
multi_line_end: multi_line_end, multi_line_end: multi_line_end,
..Self::default() ..Self::default()
}) }
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.code == 0 && self.comments == 0 && self.blanks == 0 && self.lines == 0 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 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let total = if self.total == 0 { let total = if self.total == 0 {
self.files.len() self.files.len()
@ -99,7 +89,7 @@ impl<'a> fmt::Display for Language<'a> {
}; };
write!(f, write!(f,
" {: <18} {: >6} {:>12} {:>12} {:>12} {:>12}", " {: <18} {: >6} {:>12} {:>12} {:>12} {:>12}",
self.name, "CHANGE",
total, total,
self.lines, self.lines,
self.blanks, self.blanks,
@ -107,8 +97,9 @@ impl<'a> fmt::Display for Language<'a> {
self.code) self.code)
} }
} }
// Adding languages to the raw total. // 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) { fn add_assign(&mut self, rhs: &Self) {
self.total += rhs.files.len(); self.total += rhs.files.len();
self.lines += rhs.lines; 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. // Adding a file to the language.
impl<'a> AddAssign<Stats> for Language<'a> { impl AddAssign<Stats> for Language {
fn add_assign(&mut self, rhs: Stats) { fn add_assign(&mut self, rhs: Stats) {
self.lines += rhs.lines; self.lines += rhs.lines;
self.code += rhs.code; self.code += rhs.code;
@ -127,3 +129,147 @@ impl<'a> AddAssign<Stats> for Language<'a> {
self.blanks += rhs.blanks; 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())
}
}

View file

@ -16,17 +16,21 @@ pub mod stats;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::io::{BufRead, BufReader, Read}; use std::io::{self, BufRead, BufReader, Read, Write};
use std::fs::File; use std::fs::File;
use std::path::Path; use std::path::Path;
use std::thread;
use std::time::Duration;
use std::sync::mpsc::channel;
use clap::App; use clap::App;
use glob::glob; use glob::glob;
use walkdir::{WalkDir, WalkDirIterator}; 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 ROW: &'static str = "-----------------------------------------------------------------------\
--------"; --------";
const BLANKS: &'static str = "blanks"; const BLANKS: &'static str = "blanks";
@ -39,170 +43,78 @@ fn main() {
let yaml = load_yaml!("../cli.yml"); let yaml = load_yaml!("../cli.yml");
let matches = App::from_yaml(yaml).get_matches(); let matches = App::from_yaml(yaml).get_matches();
let action_script = Language::new_c("ActionScript"); let files_option = matches.is_present(FILES);
let asm = Language::new_single("Assembly", ";"); let language_option = matches.is_present("languages");
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", "#");
// Languages are placed inside a BTreeMap, in order to print alphabetically by default // Languages are placed inside a BTreeMap, in order to print alphabetically by default
let languages = btreemap! { let mut languages = btreemap! {
"as" => &action_script, ActionScript => Language::new_c(),
"s" => &asm, Assembly => Language::new_single(";"),
"bat" => &batch, Bash => Language::new_single("#"),
"btm" => &batch, Batch => Language::new_single("REM"),
"cmd" => &batch, C => Language::new_c(),
"bash" => &bash, CHeader => Language::new_c(),
"sh" => &bash, CSharp => Language::new_c(),
"c" => &c, CShell => Language::new_single("#"),
"csh" => &c_shell, Clojure => Language::new_single(";,#,#_"),
"ec" => &c, CoffeeScript => Language::new("#", "###", "###"),
"pgc" => &c, ColdFusion => Language::new_multi("<!---", "--->"),
"cs" => &c_sharp, ColdFusionScript => Language::new_c(),
"clj" => &clojure, Cpp => Language::new_c(),
"coffee" => &coffee_script, CppHeader => Language::new_c(),
"cfm" => &cold_fusion, Css => Language::new_c(),
"cfc" => &cf_script, D => Language::new_c(),
"cc" => &cpp, Dart => Language::new_c(),
"cpp" => &cpp, DeviceTree => Language::new_c(),
"cxx" => &cpp, Lisp => Language::new(";", "#|", "|#"),
"pcc" => &cpp, FortranLegacy => Language::new_single("c,C,!,*"),
"c++" => &cpp, FortranModern => Language::new_single("!"),
"css" => &css, Go => Language::new_c(),
"d" => &d, Haskell => Language::new_single("--"),
"dart" => &dart, Html => Language::new_html(),
"dts" => &device_tree, Jai => Language::new_c(),
"dtsi" => &device_tree, Java => Language::new_c(),
"el" => &lisp, JavaScript => Language::new_c(),
"lisp" => &lisp, Julia => Language::new("#", "#=", "=#"),
"lsp" => &lisp, Json => Language::new_blank(),
"lua" => &lua, Jsx => Language::new_c(),
"sc" => &lisp, Less => Language::new_c(),
"f" => &fortran_legacy, LinkerScript => Language::new_c(),
"f77" => &fortran_legacy, Lua => Language::new("--", "--[[", "]]"),
"for" => &fortran_legacy, Makefile => Language::new_single("#"),
"ftn" => &fortran_legacy, Markdown => Language::new_blank(),
"pfo" => &fortran_legacy, Mustache => Language::new_multi("{{!", "}}"),
"f90" => &fortran_modern, ObjectiveC => Language::new_c(),
"f95" => &fortran_modern, ObjectiveCpp => Language::new_c(),
"f03" => &fortran_modern, OCaml => Language::new_multi("(*", "*)"),
"f08" => &fortran_modern, Php => Language::new("#,//", "/*", "*/"),
"go" => &go, Pascal => Language::new("//,(*", "{", "}"),
"h" => &c_header, Polly => Language::new_html(),
"hs" => &haskell, Perl => Language::new("#", "=", "=cut"),
"hpp" => &cpp_header, Protobuf => Language::new_single("//"),
"hh" => &cpp_header, Python => Language::new("#", "'''", "'''"),
"html" => &html, R => Language::new_single("#"),
"hxx" => &cpp_header, Ruby => Language::new("#", "=begin", "=end"),
"jai" => &jai, RubyHtml => Language::new_html(),
"java" => &java, Rust => Language::new("//,///,//!", "/*", "*/"),
"js" => &java_script, Sass => Language::new_c(),
"jl" => &julia, Sml => Language::new_multi("(*", "*)"),
"json" => &json, Sql => Language::new("--", "/*", "*/"),
"jsx" => &jsx, Swift => Language::new_c(),
"lds" => &linker_script, Tex => Language::new_single("%"),
"less" => &less, Text => Language::new_blank(),
"m" => &objective_c, Toml => Language::new_single("#"),
"md" => &markdown, TypeScript => Language::new_c(),
"markdown" => &markdown, VimScript => Language::new_single("\""),
"ml" => &ocaml, Xml => Language::new_html(),
"mli" => &ocaml, Yaml => Language::new_single("#"),
"mm" => &objective_cpp, Zsh => Language::new_single("#"),
"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,
}; };
// Print every supported language. // Print every supported language.
if matches.is_present("languages") { if language_option {
for language in languages.values() { for key in languages.keys() {
let mut language = language.borrow_mut(); println!("{:<25}", key);
if !language.printed {
println!("{:<25}", language.name);
language.printed = true;
}
} }
return; return;
} }
@ -210,10 +122,10 @@ fn main() {
let paths = matches.values_of("input").unwrap(); let paths = matches.values_of("input").unwrap();
let ignored_directories = { 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") { if let Some(user_ignored) = matches.values_of("exclude") {
for ignored in user_ignored { for ignored in user_ignored {
ignored_directories.push(ignored.to_owned()); ignored_directories.push(ignored);
} }
} }
ignored_directories ignored_directories
@ -238,25 +150,35 @@ fn main() {
"Code"); "Code");
println!("{}", ROW); println!("{}", ROW);
get_all_files(paths, &languages, ignored_directories); get_all_files(paths, &mut languages, ignored_directories);
let mut total = Language::new_raw("Total"); let mut total = Language::new_blank();
for language_ref in languages.values() { for (name, language) in &mut languages {
let mut language = language_ref.borrow_mut();
if language.printed { if language.files.len() == 0 {
continue; continue;
} }
let is_blank_lang = if language.line_comment == "" && language.multi_line == "" {
true let (tx, rx) = channel();
} else { let child = thread::spawn(move || {
false 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(); let files = language.files.clone();
for file in files { for file in files {
let mut contents = String::new(); 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 mut stats = stats::Stats::new(unwrap_opt_cont!(file.to_str()));
let _ = unwrap_rs_cont!(unwrap_rs_cont!(File::open(file)) let _ = unwrap_rs_cont!(unwrap_rs_cont!(File::open(file))
.read_to_string(&mut contents)); .read_to_string(&mut contents));
@ -265,7 +187,7 @@ fn main() {
let lines = contents.lines(); let lines = contents.lines();
if is_blank_lang { if language.is_blank() {
stats.code += lines.count(); stats.code += lines.count();
continue; continue;
} }
@ -314,149 +236,54 @@ fn main() {
stats.code += 1; stats.code += 1;
} }
if matches.is_present(FILES) { if files_option {
println!("{}", stats); println!("{}", stats);
} }
*language += stats; *language += stats;
} }
let _ = tx.send(());
let _ = child.join();
print!(" \r");
if !language.is_empty() { if !language.is_empty() {
language.printed = true;
if let None = sort { if let None = sort {
if matches.is_present(FILES) { if files_option {
println!("{}", ROW); println!("{}", ROW);
println!("{}", *language); println!("{}", language);
println!("{}", ROW); println!("{}", ROW);
} else { } else {
println!("{}", *language); println!("{}", language);
} }
} }
} }
total += &*language;
total += language;
} }
if let Some(sort_category) = sort { if let Some(sort_category) = sort {
let mut unsorted_vec: Vec<(&&str, &&RefCell<Language>)> = languages.iter().collect(); let mut sorted: Vec<&Language> = languages.values().collect();
match &*sort_category { match &*sort_category {
BLANKS => unsorted_vec.sort_by(|a, b| b.1.borrow().blanks.cmp(&a.1.borrow().blanks)), BLANKS => sorted.sort_by(|a, b| b.blanks.cmp(&a.blanks)),
COMMENTS => { COMMENTS => sorted.sort_by(|a, b| b.comments.cmp(&a.comments)),
unsorted_vec.sort_by(|a, b| b.1.borrow().comments.cmp(&a.1.borrow().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())),
CODE => unsorted_vec.sort_by(|a, b| b.1.borrow().code.cmp(&a.1.borrow().code)), TOTAL => sorted.sort_by(|a, b| b.lines.cmp(&a.lines)),
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)),
_ => unreachable!(), _ => unreachable!(),
} }
for (_, language) in unsorted_vec { for language in sorted {
if !language.is_empty() {
if !language.borrow().is_empty() && language.borrow().printed { println!("{}", *language);
language.borrow_mut().printed = false;
println!("{}", *language.borrow());
} }
} }
} }
if !matches.is_present(FILES) { if !files_option {
println!("{}", ROW); println!("{}", ROW);
} }
println!("{}", total); println!("{}", total);
println!("{}", ROW); println!("{}", ROW);
} println!("\x1B[?25h");
fn get_all_files<'a, I: Iterator<Item = &'a str>>(paths: I,
languages: &BTreeMap<&str, &RefCell<Language>>,
ignored_directories: Vec<String>) {
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<P: AsRef<Path>>(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<P: AsRef<Path>>(path: P) -> Option<String> {
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)
} }