mirror of
https://git.proxmox.com/git/rustc
synced 2025-08-14 19:56:49 +00:00
168 lines
5.8 KiB
Rust
168 lines
5.8 KiB
Rust
//! An example of using syntect for code analysis.
|
|
//! Basically a fancy lines of code count program that works
|
|
//! for all languages Sublime Text supports and also counts things
|
|
//! like number of functions and number of types defined.
|
|
//!
|
|
//! Another thing it does that other line count programs can't always
|
|
//! do is properly count comments in embedded syntaxes. For example
|
|
//! JS, CSS and Ruby comments embedded in ERB files.
|
|
use syntect::parsing::{SyntaxSet, ParseState, ScopeStackOp, ScopeStack};
|
|
use syntect::highlighting::{ScopeSelector, ScopeSelectors};
|
|
use syntect::easy::{ScopeRegionIterator};
|
|
|
|
use std::path::Path;
|
|
use std::io::{BufRead, BufReader};
|
|
use std::fs::File;
|
|
use walkdir::{DirEntry, WalkDir};
|
|
use std::str::FromStr;
|
|
|
|
#[derive(Debug)]
|
|
struct Selectors {
|
|
comment: ScopeSelector,
|
|
doc_comment: ScopeSelectors,
|
|
function: ScopeSelector,
|
|
types: ScopeSelectors,
|
|
}
|
|
|
|
impl Default for Selectors {
|
|
fn default() -> Selectors {
|
|
Selectors {
|
|
comment: ScopeSelector::from_str("comment - comment.block.attribute").unwrap(),
|
|
doc_comment: ScopeSelectors::from_str("comment.line.documentation, comment.block.documentation").unwrap(),
|
|
function: ScopeSelector::from_str("entity.name.function").unwrap(),
|
|
types: ScopeSelectors::from_str("entity.name.class, entity.name.struct, entity.name.enum, entity.name.type").unwrap(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct Stats {
|
|
selectors: Selectors,
|
|
files: usize,
|
|
functions: usize,
|
|
types: usize,
|
|
lines: usize,
|
|
chars: usize,
|
|
code_lines: usize,
|
|
comment_lines: usize,
|
|
comment_chars: usize,
|
|
comment_words: usize,
|
|
doc_comment_lines: usize,
|
|
doc_comment_words: usize,
|
|
}
|
|
|
|
fn print_stats(stats: &Stats) {
|
|
println!();
|
|
println!("################## Stats ###################");
|
|
println!("File count: {:>6}", stats.files);
|
|
println!("Total characters: {:>6}", stats.chars);
|
|
println!();
|
|
println!("Function count: {:>6}", stats.functions);
|
|
println!("Type count (structs, enums, classes): {:>6}", stats.types);
|
|
println!();
|
|
println!("Code lines (traditional SLOC): {:>6}", stats.code_lines);
|
|
println!("Total lines (w/ comments & blanks): {:>6}", stats.lines);
|
|
println!("Comment lines (comment but no code): {:>6}", stats.comment_lines);
|
|
println!("Blank lines (lines-blank-comment): {:>6}", stats.lines-stats.code_lines-stats.comment_lines);
|
|
println!();
|
|
println!("Lines with a documentation comment: {:>6}", stats.doc_comment_lines);
|
|
println!("Total words written in doc comments: {:>6}", stats.doc_comment_words);
|
|
println!("Total words written in all comments: {:>6}", stats.comment_words);
|
|
println!("Characters of comment: {:>6}", stats.comment_chars);
|
|
}
|
|
|
|
fn is_ignored(entry: &DirEntry) -> bool {
|
|
entry.file_name()
|
|
.to_str()
|
|
.map(|s| s.starts_with('.') && s.len() > 1 || s.ends_with(".md"))
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
fn count_line(ops: &[(usize, ScopeStackOp)], line: &str, stack: &mut ScopeStack, stats: &mut Stats) {
|
|
stats.lines += 1;
|
|
|
|
let mut line_has_comment = false;
|
|
let mut line_has_doc_comment = false;
|
|
let mut line_has_code = false;
|
|
for (s, op) in ScopeRegionIterator::new(ops, line) {
|
|
stack.apply(op).unwrap();
|
|
if s.is_empty() { // in this case we don't care about blank tokens
|
|
continue;
|
|
}
|
|
if stats.selectors.comment.does_match(stack.as_slice()).is_some() {
|
|
let words = s.split_whitespace().filter(|w| w.chars().all(|c| c.is_alphanumeric() || c == '.' || c == '\'')).count();
|
|
if stats.selectors.doc_comment.does_match(stack.as_slice()).is_some() {
|
|
line_has_doc_comment = true;
|
|
stats.doc_comment_words += words;
|
|
}
|
|
stats.comment_chars += s.len();
|
|
stats.comment_words += words;
|
|
line_has_comment = true;
|
|
} else if !s.chars().all(|c| c.is_whitespace()) {
|
|
line_has_code = true;
|
|
}
|
|
if stats.selectors.function.does_match(stack.as_slice()).is_some() {
|
|
stats.functions += 1;
|
|
}
|
|
if stats.selectors.types.does_match(stack.as_slice()).is_some() {
|
|
stats.types += 1;
|
|
}
|
|
}
|
|
if line_has_comment && !line_has_code {
|
|
stats.comment_lines += 1;
|
|
}
|
|
if line_has_doc_comment {
|
|
stats.doc_comment_lines += 1;
|
|
}
|
|
if line_has_code {
|
|
stats.code_lines += 1;
|
|
}
|
|
}
|
|
|
|
fn count(ss: &SyntaxSet, path: &Path, stats: &mut Stats) {
|
|
let syntax = match ss.find_syntax_for_file(path).unwrap_or(None) {
|
|
Some(syntax) => syntax,
|
|
None => return
|
|
};
|
|
stats.files += 1;
|
|
let mut state = ParseState::new(syntax);
|
|
|
|
let f = File::open(path).unwrap();
|
|
let mut reader = BufReader::new(f);
|
|
let mut line = String::new();
|
|
let mut stack = ScopeStack::new();
|
|
while reader.read_line(&mut line).unwrap() > 0 {
|
|
{
|
|
let ops = state.parse_line(&line, ss).unwrap();
|
|
stats.chars += line.len();
|
|
count_line(&ops, &line, &mut stack, stats);
|
|
}
|
|
line.clear();
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
let ss = SyntaxSet::load_defaults_newlines(); // note we load the version with newlines
|
|
|
|
let args: Vec<String> = std::env::args().collect();
|
|
let path = if args.len() < 2 {
|
|
"."
|
|
} else {
|
|
&args[1]
|
|
};
|
|
|
|
println!("################## Files ###################");
|
|
let mut stats = Stats::default();
|
|
let walker = WalkDir::new(path).into_iter();
|
|
for entry in walker.filter_entry(|e| !is_ignored(e)) {
|
|
let entry = entry.unwrap();
|
|
if entry.file_type().is_file() {
|
|
println!("{}", entry.path().display());
|
|
count(&ss, entry.path(), &mut stats);
|
|
}
|
|
}
|
|
|
|
// println!("{:?}", stats);
|
|
print_stats(&stats);
|
|
}
|