VERSION 1.1, added sorting, added support for 26 languages, replaced getopts with clap

This commit is contained in:
Aaron Power 2015-09-20 10:40:07 +01:00
parent ee045d302c
commit 3bbc39f937
8 changed files with 357 additions and 137 deletions

30
Cargo.lock generated
View File

@ -1,17 +1,24 @@
[root]
name = "rusty-cloc"
version = "0.1.0"
name = "tokei"
version = "1.1.0"
dependencies = [
"getopts 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "getopts"
version = "0.2.11"
name = "ansi_term"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ansi_term 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"yaml-rust 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -20,15 +27,12 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.1.8"
name = "strsim"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "log"
version = "0.3.1"
name = "yaml-rust"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

@ -1,14 +1,14 @@
[package]
name = "rusty-cloc"
version = "0.1.0"
name = "tokei"
version = "1.1.0"
authors = ["Aaronepower <theaaronepower@gmail.com>"]
[dependencies]
getopts = "0.2"
glob = "*"
[profile.dev]
debug = true
[profile.release]
opt-level = 3
opt-level = 3
[dependencies]
clap = {version = "*", features = ["yaml"]}
glob = "*"

View File

@ -1,11 +1,67 @@
# rusty-cloc
A CLOC(Count Lines Of Code) program, written in Rust.
# Tokei
A blazingly fast CLOC(Count Lines Of Code) program, written in Rust.
# Options
```
Aaron P. <theaaronepower@gmail.com>
A quick CLOC (Count Lines Of Code) tool
`--exclude-dir` exclude one, or more directories from the search.
###### Example
`rusty-cloc --exclude-dir=node_modules`
USAGE:
tokei [FLAGS] [OPTIONS] [--] <input>...
Will ignore everything within a folder named `node_modules`
FLAGS:
-h, --help Prints help information
-l, --languages prints out supported languages and their extensions
-V, --version Prints version information
OPTIONS:
-e, --exclude <exclude>... Will ignore all files and directories containing the word ie --exclude node_modules
-s, --sort <sort> Will sort based on a certain column ie --sort=files will sort by file count.
ARGS:
input... The input file(s)/directory(ies)
```
# Supported Languages
```
ActionScript (as)
C (c)
ColdFusion CFScript (cfc)
ColdFusion (cfm)
Clojure (clj)
CoffeeScript (coffee)
C++ (cpp)
C# (cs)
CSS (css)
D (d)
Dart (dart)
LISP (el)
Go (go)
C Header (h)
C++ Header (hpp)
Haskell (hs)
HTML (html)
Java (java)
JavaScript (js)
JSON (json)
JSX (jsx)
Objective-C (m)
Objective-C++ (mm)
Pascal (pas)
PHP (php)
Perl (pl)
Python (py)
R (r)
Ruby (rb)
Ruby HTML (rhtml)
Rust (rs)
Sass (sass)
BASH (sh)
SQL (sql)
Swift (swift)
TypeScript (ts)
XML (xml)
YAML (yml)
```

25
cli.yml Normal file
View File

@ -0,0 +1,25 @@
name: Tokei
version: 1.1
author: Aaron P. <theaaronepower@gmail.com>
about: A quick CLOC (Count Lines Of Code) tool
args:
- exclude:
short: e
long: exclude
multiple: true
help: Will ignore all files and directories containing the word ie --exclude node_modules
takes_value: true
- sort:
short: s
long: sort
takes_value: true
help: Will sort based on a certain column ie --sort=files will sort by file count.
- input:
index: 1
multiple: true
required: true
help: The input file(s)/directory(ies)
- languages:
short: l
long: languages
help: prints out supported languages and their extensions

View File

@ -41,7 +41,7 @@ pub fn contains_comments(file: &str, comment: &str) -> bool {
return true
}
false
false
}
pub fn get_all_files(path: String, ignored_directories: &Vec<String>) -> Vec<String> {
@ -49,13 +49,16 @@ pub fn get_all_files(path: String, ignored_directories: &Vec<String>) -> Vec<Str
if let Ok(result) = metadata(&path) {
if result.is_dir() {
let dir = fs::read_dir(&path).unwrap();
let dir = match fs::read_dir(&path) {
Ok(value) => value,
Err(err) => panic!("ERROR: {:?}", err),
};
'file: for entry in dir {
let entry = entry.unwrap();
let entry = unwrap_rs_cont!(entry);
let file_path = entry.path();
let file_str = file_path.to_str().unwrap();
let file_str = unwrap_opt_cont!(file_path.to_str());
let file_string = file_str.to_owned();
let path_metadata = metadata(&file_string).unwrap();
let path_metadata = unwrap_rs_cont!(metadata(file_str));
if path_metadata.is_dir() {
for ignored_directory in ignored_directories {
@ -74,11 +77,15 @@ pub fn get_all_files(path: String, ignored_directories: &Vec<String>) -> Vec<Str
files.push(path);
}
} else {
for path_buf in glob(&path).unwrap() {
let file_path = path_buf.unwrap().as_path().to_str().unwrap().to_owned();
let iter = match glob(&path) {
Ok(value) => value,
Err(err) => panic!("{:?}", err)
};
for path_buf in iter {
let file_path = unwrap_opt_cont!(unwrap_rs_cont!(path_buf).as_path().to_str()).to_owned();
files.push(file_path);
}
}
files
}
}

View File

@ -6,11 +6,12 @@ pub struct Language<'a> {
pub multi_line: &'a str,
pub multi_line_end: &'a str,
pub files: Vec<String>,
pub code: u32,
pub comments: u32,
pub blanks: u32,
pub lines: u32,
pub code: usize,
pub comments: usize,
pub blanks: usize,
pub lines: usize,
pub total: usize,
pub size: usize,
}
impl<'a> Language<'a> {
@ -30,6 +31,71 @@ impl<'a> Language<'a> {
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,
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,
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,
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,
size: 0,
}
}
@ -40,13 +106,11 @@ impl<'a> Language<'a> {
impl<'a> fmt::Display for Language<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut total;
if self.total == 0 {
total = self.files.len()
let total = if self.total == 0 {
self.files.len()
} else {
total = self.total;
}
write!(f," {: <15} {: >15} {:>15} {:>15} {:>15} {:>15} ", self.name, total, self.lines, self.blanks, self.comments, self.code)
self.total
};
write!(f," {: <15} {: >15} {:>15} {:>15} {:>15} {:>15}", self.name, total, self.lines, self.blanks, self.comments, self.code)
}
}
}

19
src/macros.rs Normal file
View File

@ -0,0 +1,19 @@
#[inline(always)]
macro_rules! unwrap_opt_cont {
($option:expr) => {
match $option {
Some(result) => result,
None => continue,
}
}
}
#[inline(always)]
macro_rules! unwrap_rs_cont {
($result:expr) => {
match $result {
Ok(result) => result,
Err(_) => continue,
}
}
}

View File

@ -1,122 +1,140 @@
extern crate getopts;
#[macro_use]
extern crate clap;
#[macro_use]
pub mod macros;
pub mod language;
pub mod fsutil;
use std::env;
use std::io::Read;
use std::path::Path;
use std::fs::File;
use std::collections::HashMap;
use getopts::Options;
use std::collections::BTreeMap;
use clap::App;
use language::Language;
use fsutil::{get_all_files, contains_comments};
fn main() {
let args: Vec<String> = env::args().collect();
let mut opts = Options::new();
let yaml = load_yaml!("../cli.yml");
let matches = App::from_yaml(yaml).get_matches();
opts.optflag("h", "help", "Print this help menu");
opts.optopt("", "exclude-dir",
"Example: --exclude-dir=docs",
"\tDirectories wanted to be ignored");
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("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("json" , Language::new_blank("JSON"));
languages.insert("jsx" , Language::new_c("JSX"));
languages.insert("el" , Language::new("LISP", ";", "#|", "|#"));
languages.insert("m" , Language::new_c("Objective-C"));
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("ts" , Language::new_c("TypeScript"));
languages.insert("xml" , Language::new_html("XML"));
languages.insert("yml" , Language::new_single("YAML", "#"));
let matches = opts.parse(&args[1..]).unwrap();
let mut ignored_directories: Vec<String> = Vec::new();
ignored_directories.push(".git".to_string());
if matches.is_present("languages") {
for (ext, language) in languages {
println!("{:<25} ({})", language.name, ext);
}
return;
}
if matches.opt_present("h") {
let brief = format!("Usage: {} [options] [paths]", args[0].clone());
println!("{}", opts.usage(&brief));
return;
let paths = matches.values_of("input").unwrap();
let mut ignored_directories: Vec<String> = Vec::new();
if let Some(user_ignored) = matches.values_of("exclude") {
for ignored in user_ignored {
ignored_directories.push(ignored.to_owned());
}
}
if matches.opt_present("exclude-dir") {
let exclude_args = matches.opt_str("exclude-dir").unwrap();
let exclude_vec = exclude_args.split(",");
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();
for excluded in exclude_vec {
ignored_directories.push(excluded.to_string());
}
}
if matches.free.is_empty() {
println!("ERROR: ");
println!("You must provide a file, or folder path as an argument.");
return;
}
let row = "----------------------------------------------------------------------------------------------------";
let row = "--------------------------------------------------------------------------------------------------";
println!("{}", row);
println!(" {:<15} {:>15} {:>15} {:>15} {:>15} {:>15} ",
"language", "files", "total", "blanks", "comments", "code");
println!(" {:<15} {:>15} {:>15} {:>15} {:>15} {:>15}",
"Language", "Files", "Total", "Blanks", "Comments", "Code");
println!("{}", row);
let mut languages: HashMap<&str, Language> = HashMap::new();
languages.insert("cpp" , Language::new("C++", "//","/*","*/"));
languages.insert("hpp" , Language::new("C++ Header", "//","/*","*/"));
languages.insert("c" , Language::new("C", "//","/*","*/"));
languages.insert("h" , Language::new("C Header", "//","/*","*/"));
languages.insert("css" , Language::new("CSS", "//","/*","*/"));
languages.insert("java" , Language::new("Java", "//","/*","*/"));
languages.insert("js" , Language::new("JavaScript", "//","/*","*/"));
languages.insert("rs" , Language::new("Rust", "//","/*","*/"));
languages.insert("xml" , Language::new("XML", "<!--","<!--","-->"));
languages.insert("html" , Language::new("HTML", "<!--","<!--","-->"));
languages.insert("py" , Language::new("Python", "#","'''","'''"));
languages.insert("rb" , Language::new("Ruby", "#","=begin","=end"));
languages.insert("php" , Language::new("PHP", "#,//","/*","*/"));
for path in matches.free {
let files = get_all_files(path, &ignored_directories);
for path in paths {
let files = get_all_files(path.to_owned(), &ignored_directories);
for file in files {
let extension = match Path::new(&file).extension() {
Some(result) => result.to_str().unwrap(),
None => continue,
};
let extension = unwrap_opt_cont!(unwrap_opt_cont!(Path::new(&file).extension()).to_str());
let mut language = match languages.get_mut(extension) {
Some(result) => result,
None => continue,
};
language.files.push(file.to_string());
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("Total", "", "", "");
let mut total = Language::new_blank("Total");
for (_, language) in languages.iter_mut() {
for (_, language) in &mut languages {
for file in language.files.iter() {
let mut buffer: Vec<u8> = Vec::new();
let mut file_ref = match File::open(&file) {
Ok(result) => result,
_ => continue,
};
let _ = file_ref.read_to_end(&mut buffer);
let contents = match String::from_utf8(buffer) {
Ok(result) => result,
Err(_) => continue,
};
let mut file_ref = unwrap_rs_cont!(File::open(&file));
let mut contents = String::new();
let _ = unwrap_rs_cont!(file_ref.read_to_string(&mut contents));
let mut is_in_comments = false;
for line in contents.lines() {
'line: for line in contents.lines() {
let line = line.trim();
language.lines += 1;
if line.starts_with(language.multi_line) {
language.comments += 1;
is_in_comments = true;
} else if contains_comments(line, language.multi_line) {
language.code += 1;
is_in_comments = true;
}
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) {
@ -129,16 +147,15 @@ fn main() {
for single in single_comments {
if line.starts_with(single) {
language.comments += 1;
} else if line.trim().is_empty() {
language.blanks += 1;
} else {
language.code += 1;
}
}
continue 'line;
}
}
language.code += 1;
}
}
if !language.is_empty() {
if !language.is_empty() && sort_empty {
println!("{}", language);
}
@ -149,7 +166,35 @@ fn main() {
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);
}
}