Merge pull request #17 from Aaronepower/RefCell

Added support for multiple file extensions
This commit is contained in:
Aaron Power 2015-10-11 21:50:55 +01:00
commit c06871b56f
2 changed files with 371 additions and 274 deletions

View file

@ -4,118 +4,119 @@
use std::fmt;
#[derive(Debug)]
pub struct Language<'a> {
pub name: &'a str,
pub line_comment: &'a str,
pub multi_line: &'a str,
pub multi_line_end: &'a str,
pub files: Vec<String>,
pub code: usize,
pub comments: usize,
pub blanks: usize,
pub lines: usize,
pub total: usize,
pub size: usize,
pub name: &'a str,
pub line_comment: &'a str,
pub multi_line: &'a str,
pub multi_line_end: &'a str,
pub files: Vec<String>,
pub code: usize,
pub comments: usize,
pub blanks: usize,
pub lines: usize,
pub total: usize,
pub printed: bool,
}
impl<'a> Language<'a> {
pub fn new<'b>(name: &'a str,
line_comment: &'a str,
multi_line: &'a str,
multi_line_end: &'a str) -> Language<'a> {
pub fn new<'b>(name: &'a str,
line_comment: &'a str,
multi_line: &'a str,
multi_line_end: &'a str) -> Language<'a> {
Language {
name: name,
line_comment: line_comment,
multi_line: multi_line,
multi_line_end: multi_line_end,
files: Vec::new(),
code: 0,
comments: 0,
blanks: 0,
lines: 0,
total: 0,
size: 0,
}
}
Language {
name: name,
line_comment: line_comment,
multi_line: multi_line,
multi_line_end: multi_line_end,
files: Vec::new(),
code: 0,
comments: 0,
blanks: 0,
lines: 0,
total: 0,
printed: false,
}
}
pub fn new_c(name: &'a str) -> Language<'a> {
Language {
name: name,
line_comment: "//",
multi_line: "/*",
multi_line_end: "*/",
files: Vec::new(),
code: 0,
comments: 0,
blanks: 0,
lines: 0,
total: 0,
size: 0,
}
}
pub fn new_c(name: &'a str) -> Language<'a> {
Language {
name: name,
line_comment: "//",
multi_line: "/*",
multi_line_end: "*/",
files: Vec::new(),
code: 0,
comments: 0,
blanks: 0,
lines: 0,
total: 0,
printed: false,
}
}
pub fn new_html(name: &'a str) -> Language<'a> {
Language {
name: name,
line_comment: "<!--",
multi_line: "<!--",
multi_line_end: "-->",
files: Vec::new(),
code: 0,
comments: 0,
blanks: 0,
lines: 0,
total: 0,
size: 0,
}
}
pub fn new_html(name: &'a str) -> Language<'a> {
Language {
name: name,
line_comment: "<!--",
multi_line: "<!--",
multi_line_end: "-->",
files: Vec::new(),
code: 0,
comments: 0,
blanks: 0,
lines: 0,
total: 0,
printed: false,
}
}
pub fn new_blank(name: &'a str) -> Language<'a> {
Language {
name: name,
line_comment: "",
multi_line: "",
multi_line_end: "",
files: Vec::new(),
code: 0,
comments: 0,
blanks: 0,
lines: 0,
total: 0,
size: 0,
}
}
pub fn new_blank(name: &'a str) -> Language<'a> {
Language {
name: name,
line_comment: "",
multi_line: "",
multi_line_end: "",
files: Vec::new(),
code: 0,
comments: 0,
blanks: 0,
lines: 0,
total: 0,
printed: false,
}
}
pub fn new_single(name: &'a str, line_comment: &'a str) -> Language<'a> {
Language {
name: name,
line_comment: line_comment,
multi_line: "",
multi_line_end: "",
files: Vec::new(),
code: 0,
comments: 0,
blanks: 0,
lines: 0,
total: 0,
size: 0,
}
}
pub fn new_single(name: &'a str, line_comment: &'a str) -> Language<'a> {
Language {
name: name,
line_comment: line_comment,
multi_line: "",
multi_line_end: "",
files: Vec::new(),
code: 0,
comments: 0,
blanks: 0,
lines: 0,
total: 0,
printed: false,
}
}
pub fn is_empty(&self) -> bool {
self.code == 0 && self.comments == 0 && self.blanks == 0 && self.lines == 0
}
pub fn is_empty(&self) -> bool {
self.code == 0 && self.comments == 0 && self.blanks == 0 && self.lines == 0
}
}
impl<'a> fmt::Display for Language<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let total = if self.total == 0 {
self.files.len()
} else {
self.total
};
write!(f," {: <15} {: >15} {:>15} {:>15} {:>15} {:>15}", self.name, total, self.lines, self.blanks, self.comments, self.code)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let total = if self.total == 0 {
self.files.len()
} else {
self.total
};
write!(f," {: <15} {: >15} {:>15} {:>15} {:>15} {:>15}", self.name, total, self.lines, self.blanks, self.comments, self.code)
}
}

View file

@ -10,6 +10,8 @@ pub mod macros;
pub mod language;
pub mod fsutil;
use std::rc::Rc;
use std::cell::RefCell;
use std::io::Read;
use std::path::Path;
use std::fs::File;
@ -23,188 +25,282 @@ use fsutil::{get_all_files, contains_comments};
static ROW: &'static str = "--------------------------------------------------------------------------------------------------";
fn main() {
let yaml = load_yaml!("../cli.yml");
let matches = App::from_yaml(yaml).get_matches();
let yaml = load_yaml!("../cli.yml");
let matches = App::from_yaml(yaml).get_matches();
let mut languages: BTreeMap<&str, Language> = BTreeMap::new();
languages.insert("as" , Language::new_c("ActionScript"));
languages.insert("c" , Language::new_c("C"));
languages.insert("cs" , Language::new_c("C#"));
languages.insert("clj" , Language::new_single("Clojure", ";,#,#_"));
languages.insert("coffee" , Language::new("CoffeeScript", "#", "###", "###"));
languages.insert("cfm" , Language::new("ColdFusion", "<!---", "<!---", "--->"));
languages.insert("cfc" , Language::new_c("ColdFusion CFScript"));
languages.insert("cpp" , Language::new_c("C++"));
languages.insert("css" , Language::new_c("CSS"));
languages.insert("d" , Language::new_c("D"));
languages.insert("dart" , Language::new_c("Dart"));
languages.insert("el" , Language::new("LISP", ";", "#|", "|#"));
languages.insert("f" , Language::new_single("FORTRAN Legacy", "c,C,!,*"));
languages.insert("f90" , Language::new_single("FORTRAN Modern", "!"));
languages.insert("go" , Language::new_c("Go"));
languages.insert("h" , Language::new_c("C Header"));
languages.insert("hs" , Language::new_single("Haskell", "--"));
languages.insert("hpp" , Language::new_c("C++ Header"));
languages.insert("html" , Language::new_html("HTML"));
languages.insert("java" , Language::new_c("Java"));
languages.insert("js" , Language::new_c("JavaScript"));
languages.insert("jl" , Language::new("Julia", "#", "#=", "=#"));
languages.insert("json" , Language::new_blank("JSON"));
languages.insert("jsx" , Language::new_c("JSX"));
languages.insert("less" , Language::new_c("LESS"));
languages.insert("m" , Language::new_c("Objective-C"));
languages.insert("md" , Language::new_blank("Markdown"));
languages.insert("mm" , Language::new_c("Objective-C++"));
languages.insert("php" , Language::new("PHP", "#,//","/*","*/"));
languages.insert("pas" , Language::new("Pascal", "//,(*","{","}"));
languages.insert("pl" , Language::new("Perl", "#","=","=cut"));
languages.insert("py" , Language::new("Python", "#","'''","'''"));
languages.insert("rs" , Language::new("Rust", "//,///,//!", "/*", "*/"));
languages.insert("r" , Language::new("R", "#","",""));
languages.insert("rb" , Language::new("Ruby", "#","=begin","=end"));
languages.insert("rhtml" , Language::new_html("Ruby HTML"));
languages.insert("sass" , Language::new_c("Sass"));
languages.insert("sh" , Language::new_single("BASH", "#"));
languages.insert("sql" , Language::new("SQL", "--", "/*", "*/"));
languages.insert("swift" , Language::new_c("Swift"));
languages.insert("toml" , Language::new_single("TOML", "#"));
languages.insert("ts" , Language::new_c("TypeScript"));
languages.insert("xml" , Language::new_html("XML"));
languages.insert("yml" , Language::new_single("YAML", "#"));
let action_script = Rc::new(RefCell::new(Language::new_c("ActionScript")));
let bash = Rc::new(RefCell::new(Language::new_single("BASH", "#")));
let batch = Rc::new(RefCell::new(Language::new_single("Batch", "REM")));
let c = Rc::new(RefCell::new(Language::new_c("C")));
let c_header = Rc::new(RefCell::new(Language::new_c("C Header")));
let c_sharp = Rc::new(RefCell::new(Language::new_c("C#")));
let clojure = Rc::new(RefCell::new(Language::new_single("Clojure", ";,#,#_")));
let coffee_script = Rc::new(RefCell::new(Language::new("CoffeeScript", "#", "###", "###")));
let cold_fusion = Rc::new(RefCell::new(Language::new("ColdFusion", "<!---", "<!---", "--->")));
let cf_script = Rc::new(RefCell::new(Language::new_c("ColdFusion CFScript")));
let cpp = Rc::new(RefCell::new(Language::new_c("C++")));
let cpp_header = Rc::new(RefCell::new(Language::new_c("C++ Header")));
let css = Rc::new(RefCell::new(Language::new_c("CSS")));
let d = Rc::new(RefCell::new(Language::new_c("D")));
let dart = Rc::new(RefCell::new(Language::new_c("Dart")));
let lisp = Rc::new(RefCell::new(Language::new("LISP", ";", "#|", "|#")));
let fortran_legacy = Rc::new(RefCell::new(Language::new_single("FORTRAN Legacy", "c,C,!,*")));
let fortran_modern = Rc::new(RefCell::new(Language::new_single("FORTRAN Modern", "!")));
let go = Rc::new(RefCell::new(Language::new_c("Go")));
let haskell = Rc::new(RefCell::new(Language::new_single("Haskell", "--")));
let html = Rc::new(RefCell::new(Language::new_html("HTML")));
let java = Rc::new(RefCell::new(Language::new_c("Java")));
let java_script = Rc::new(RefCell::new(Language::new_c("JavaScript")));
let julia = Rc::new(RefCell::new(Language::new("Julia", "#", "#=", "=#")));
let json = Rc::new(RefCell::new(Language::new_blank("JSON")));
let jsx = Rc::new(RefCell::new(Language::new_c("JSX")));
let less = Rc::new(RefCell::new(Language::new_c("LESS")));
let markdown = Rc::new(RefCell::new(Language::new_blank("Markdown")));
let objective_c = Rc::new(RefCell::new(Language::new_c("Objective-C")));
let objective_cpp = Rc::new(RefCell::new(Language::new_c("Objective-C++")));
let php = Rc::new(RefCell::new(Language::new("PHP", "#,//","/*","*/")));
let pascal = Rc::new(RefCell::new(Language::new("Pascal", "//,(*","{","}")));
let perl = Rc::new(RefCell::new(Language::new("Perl", "#","=","=cut")));
let python = Rc::new(RefCell::new(Language::new("Python", "#","'''","'''")));
let r = Rc::new(RefCell::new(Language::new("R", "#","","")));
let ruby = Rc::new(RefCell::new(Language::new("Ruby", "#","=begin","=end")));
let ruby_html = Rc::new(RefCell::new(Language::new_html("Ruby HTML")));
let rust = Rc::new(RefCell::new(Language::new("Rust", "//,///,//!", "/*", "*/")));
let sass = Rc::new(RefCell::new(Language::new_c("Sass")));
let sql = Rc::new(RefCell::new(Language::new("SQL", "--", "/*", "*/")));
let swift = Rc::new(RefCell::new(Language::new_c("Swift")));
let toml = Rc::new(RefCell::new(Language::new_single("TOML", "#")));
let type_script = Rc::new(RefCell::new(Language::new_c("TypeScript")));
let xml = Rc::new(RefCell::new(Language::new_html("XML")));
let yaml = Rc::new(RefCell::new(Language::new_single("YAML", "#")));
if matches.is_present("languages") {
for (ext, language) in languages {
println!("{:<25} ({})", language.name, ext);
}
return;
}
let mut languages: BTreeMap<&str, &Rc<RefCell<Language>>> = BTreeMap::new();
languages.insert("as" , &action_script);
languages.insert("bat" , &batch);
languages.insert("btm" , &batch);
languages.insert("cmd" , &batch);
languages.insert("bash" , &bash);
languages.insert("sh" , &bash);
languages.insert("c" , &c);
languages.insert("ec" , &c);
languages.insert("pgc" , &c);
languages.insert("cs" , &c_sharp);
languages.insert("clj" , &clojure);
languages.insert("coffee" , &coffee_script);
languages.insert("cfm" , &cold_fusion);
languages.insert("cfc" , &cf_script);
languages.insert("cc" , &cpp);
languages.insert("cpp" , &cpp);
languages.insert("cxx" , &cpp);
languages.insert("pcc" , &cpp);
languages.insert("c++" , &cpp);
languages.insert("css" , &css);
languages.insert("d" , &d);
languages.insert("dart" , &dart);
languages.insert("el" , &lisp);
languages.insert("lisp" , &lisp);
languages.insert("lsp" , &lisp);
languages.insert("sc" , &lisp);
languages.insert("f" , &fortran_legacy);
languages.insert("f77" , &fortran_legacy);
languages.insert("for" , &fortran_legacy);
languages.insert("ftn" , &fortran_legacy);
languages.insert("pfo" , &fortran_legacy);
languages.insert("f90" , &fortran_modern);
languages.insert("f95" , &fortran_modern);
languages.insert("go" , &go);
languages.insert("h" , &c_header);
languages.insert("hs" , &haskell);
languages.insert("hpp" , &cpp_header);
languages.insert("hh" , &cpp_header);
languages.insert("html" , &html);
languages.insert("java" , &java);
languages.insert("js" , &java_script);
languages.insert("jl" , &julia);
languages.insert("json" , &json);
languages.insert("jsx" , &jsx);
languages.insert("less" , &less);
languages.insert("m" , &objective_c);
languages.insert("md" , &markdown);
languages.insert("mm" , &objective_cpp);
languages.insert("php" , &php);
languages.insert("pas" , &pascal);
languages.insert("pl" , &perl);
languages.insert("py" , &python);
languages.insert("r" , &r);
languages.insert("rake" , &ruby);
languages.insert("rb" , &ruby);
languages.insert("rhtml" , &ruby_html);
languages.insert("rs" , &rust);
languages.insert("sass" , &sass);
languages.insert("scss" , &sass);
languages.insert("sql" , &sql);
languages.insert("swift" , &swift);
languages.insert("toml" , &toml);
languages.insert("ts" , &type_script);
languages.insert("xml" , &xml);
languages.insert("yaml" , &yaml);
languages.insert("yml" , &yaml);
if matches.is_present("languages") {
for (_, language) in languages.iter() {
let ref language = language.borrow();
println!("{:<25}", language.name);
}
}
let paths = matches.values_of("input").unwrap();
let mut ignored_directories: Vec<String> = Vec::new();
if let Some(user_ignored) = matches.value_of("exclude") {
for ignored in user_ignored.split(",") {
ignored_directories.push(ignored.to_owned());
}
}
let mut sort = String::new();
if let Some(sort_by) = matches.value_of("sort") {
match &*sort_by.to_lowercase() {
"files" | "total" | "blanks" | "comments" | "code" => sort.push_str(&*sort_by.to_lowercase()),
_ => println!("--sort must be any of the following files, total, blanks, comments, code"),
}
}
let sort_empty = sort.is_empty();
println!("{}", ROW);
println!(" {:<15} {:>15} {:>15} {:>15} {:>15} {:>15}",
"Language", "Files", "Total", "Blanks", "Comments", "Code");
println!("{}", ROW);
for path in paths {
let files = get_all_files(path.to_owned(), &ignored_directories);
for file in files {
let extension = unwrap_opt_cont!(unwrap_opt_cont!(Path::new(&file).extension()).to_str());
let lowercase: &str = &extension.to_lowercase();
let language = unwrap_opt_cont!(languages.get_mut(lowercase));
language.borrow_mut().files.push(file.to_owned());
}
}
let mut total = Language::new_blank("Total");
for (_, language) in &mut languages {
if language.borrow().printed {
continue;
}
let files = language.borrow_mut().files.clone();
for file in files {
let mut contents = String::new();
let is_fortran = language.borrow().name.contains("FORTRAN");
let _ = unwrap_rs_cont!(unwrap_rs_cont!(File::open(&file)).read_to_string(&mut contents));
let mut is_in_comments = false;
let lines = contents.lines();
'line: for line in lines {
let line = if is_fortran {line} else {line.trim()};
language.borrow_mut().lines += 1;
if line.trim().is_empty() {
language.borrow_mut().blanks += 1;
continue;
}
if !language.borrow().multi_line.is_empty() {
let multi_line = language.borrow().multi_line;
if line.starts_with(multi_line) {
is_in_comments = true;
} else if contains_comments(line, multi_line) {
language.borrow_mut().code += 1;
is_in_comments = true;
}
}
let paths = matches.values_of("input").unwrap();
if is_in_comments {
if line.contains(language.borrow().multi_line_end) {
is_in_comments = false;
}
let mut ignored_directories: Vec<String> = Vec::new();
language.borrow_mut().comments += 1;
continue;
}
let single_comments = language.borrow().line_comment.split(",");
for single in single_comments {
if line.starts_with(single) {
language.borrow_mut().comments += 1;
continue 'line;
}
}
language.borrow_mut().code += 1;
}
}
if !language.borrow().is_empty() {
language.borrow_mut().printed = true;
if sort_empty {
println!("{}", *language.borrow());
}
}
let language = language.borrow();
if let Some(user_ignored) = matches.value_of("exclude") {
for ignored in user_ignored.split(",") {
ignored_directories.push(ignored.to_owned());
}
}
total.total += language.files.len();
total.lines += language.lines;
total.comments += language.comments;
total.blanks += language.blanks;
total.code += language.code;
}
let mut sort = String::new();
if let Some(sort_by) = matches.value_of("sort") {
match &*sort_by.to_lowercase() {
"files" | "total" | "blanks" | "comments" | "code" => sort.push_str(&*sort_by.to_lowercase()),
_ => println!("--sort must be any of the following files, total, blanks, comments, code"),
}
}
let sort_empty = sort.is_empty();
if !sort_empty {
let mut unsorted_vec:Vec<(&&str, &&Rc<RefCell<Language>>)> = languages.iter().collect();
match &*sort {
"files" => {
unsorted_vec.sort_by(|a, b| {
let ref a = *a.1.borrow();
let ref b = *b.1.borrow();
b.files.len().cmp(&a.files.len())
})
},
"total" => {
unsorted_vec.sort_by(|a, b| {
let ref a = *a.1.borrow();
let ref b = *b.1.borrow();
b.lines.cmp(&a.lines)
})
},
"blanks" => {
unsorted_vec.sort_by(|a, b| {
let ref a = *a.1.borrow();
let ref b = *b.1.borrow();
b.blanks.cmp(&a.blanks)
})
},
"comments" => {
unsorted_vec.sort_by(|a, b| {
let ref a = *a.1.borrow();
let ref b = *b.1.borrow();
b.comments.cmp(&a.comments)
})
},
"code" => {
unsorted_vec.sort_by(|a, b| {
let ref a = *a.1.borrow();
let ref b = *b.1.borrow();
b.code.cmp(&a.code)
})
},
_ => unreachable!(),
};
println!("{}", ROW);
println!(" {:<15} {:>15} {:>15} {:>15} {:>15} {:>15}",
"Language", "Files", "Total", "Blanks", "Comments", "Code");
println!("{}", ROW);
for path in paths {
let files = get_all_files(path.to_owned(), &ignored_directories);
for (_, language) in unsorted_vec {
for file in files {
let extension = unwrap_opt_cont!(unwrap_opt_cont!(Path::new(&file).extension()).to_str());
if !language.borrow().is_empty() && language.borrow().printed {
language.borrow_mut().printed = false;
println!("{}", *language.borrow());
}
}
}
let lowercase: &str = &extension.to_lowercase();
let mut language = unwrap_opt_cont!(languages.get_mut(lowercase));
language.files.push(file.to_owned());
}
}
let mut total = Language::new_blank("Total");
for (_, language) in &mut languages {
for file in language.files.iter() {
let mut contents = String::new();
let is_fortran = language.name.contains("FORTRAN");
let _ = unwrap_rs_cont!(unwrap_rs_cont!(File::open(&file)).read_to_string(&mut contents));
let mut is_in_comments = false;
'line: for line in contents.lines() {
let line = if is_fortran {line} else {line.trim()};
language.lines += 1;
if line.trim().is_empty() {
language.blanks += 1;
continue;
}
if !language.multi_line.is_empty() {
if line.starts_with(language.multi_line) {
is_in_comments = true;
} else if contains_comments(line, language.multi_line) {
language.code += 1;
is_in_comments = true;
}
}
if is_in_comments {
if line.contains(language.multi_line_end) {
is_in_comments = false;
}
language.comments += 1;
continue;
}
if !language.line_comment.is_empty() {
let single_comments = language.line_comment.split(",");
for single in single_comments {
if line.starts_with(single) {
language.comments += 1;
continue 'line;
}
}
}
language.code += 1;
}
}
if !language.is_empty() && sort_empty {
println!("{}", language);
}
total.total += language.files.len();
total.lines += language.lines;
total.comments += language.comments;
total.blanks += language.blanks;
total.code += language.code;
}
if !sort_empty {
let mut unsorted_vec:Vec<(&&str, &Language)> = languages.iter().collect();
match &*sort {
"files" => {
unsorted_vec.sort_by(|a, b| b.1.files.len().cmp(&a.1.files.len()))
},
"total" => {
unsorted_vec.sort_by(|a, b| b.1.lines.cmp(&a.1.lines))
},
"blanks" => {
unsorted_vec.sort_by(|a, b| b.1.blanks.cmp(&a.1.blanks))
},
"comments" => {
unsorted_vec.sort_by(|a, b| b.1.comments.cmp(&a.1.comments))
},
"code" => {
unsorted_vec.sort_by(|a, b| b.1.code.cmp(&a.1.code))
},
_ => unreachable!(),
};
for (_, language) in unsorted_vec {
if !language.is_empty() {
println!("{}", language);
}
}
}
println!("{}", ROW);
println!("{}", total);
println!("{}", ROW);
println!("{}", ROW);
println!("{}", total);
println!("{}", ROW);
}