add basic cloc function
This commit is contained in:
197
src/commands/cloc.rs
Normal file
197
src/commands/cloc.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use ignore::WalkBuilder;
|
||||
|
||||
const CYAN: &str = "\x1b[36m";
|
||||
const GREEN: &str = "\x1b[32m";
|
||||
const YELLOW: &str = "\x1b[33m";
|
||||
const RESET: &str = "\x1b[0m";
|
||||
const SEPARATOR: &str = "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━";
|
||||
|
||||
pub fn run(path: Option<PathBuf>) -> Result<()> {
|
||||
let path = path.unwrap_or_else(|| env::current_dir().unwrap());
|
||||
let mut languages: HashMap<&str, (usize, usize)> = HashMap::new();
|
||||
let mut livewire_php_lines: usize = 0;
|
||||
let mut livewire_blade_lines: usize = 0;
|
||||
let mut livewire_files: usize = 0;
|
||||
let mut test_lines: usize = 0;
|
||||
let mut test_files: usize = 0;
|
||||
|
||||
for entry in WalkBuilder::new(&path).build() {
|
||||
let entry = entry?;
|
||||
if !entry.file_type().map_or(false, |ft| ft.is_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let file_path = entry.path();
|
||||
let filename = file_path.file_name().unwrap_or_default().to_string_lossy();
|
||||
|
||||
if is_lock_file(&filename) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Livewire SFC: ⚡*.blade.php (excluding test files)
|
||||
if filename.starts_with('⚡')
|
||||
&& filename.ends_with(".blade.php")
|
||||
&& !filename.ends_with(".test.php")
|
||||
{
|
||||
let Ok(content) = fs::read_to_string(file_path) else {
|
||||
continue;
|
||||
};
|
||||
livewire_files += 1;
|
||||
let mut in_php = true;
|
||||
for line in content.lines() {
|
||||
if in_php && line.trim() == "?>" {
|
||||
in_php = false;
|
||||
} else if in_php {
|
||||
livewire_php_lines += 1;
|
||||
} else {
|
||||
livewire_blade_lines += 1;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// PHP tests: *.test.php or .php in tests/ directory
|
||||
let in_tests_dir = file_path.components().any(|c| c.as_os_str() == "tests");
|
||||
if filename.ends_with(".test.php") || (in_tests_dir && filename.ends_with(".php")) {
|
||||
let Ok(content) = fs::read_to_string(file_path) else {
|
||||
continue;
|
||||
};
|
||||
test_lines += content.lines().count();
|
||||
test_files += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Blade templates: *.blade.php (not Livewire SFC)
|
||||
if filename.ends_with(".blade.php") {
|
||||
let Ok(content) = fs::read_to_string(file_path) else {
|
||||
continue;
|
||||
};
|
||||
let entry = languages.entry("Blade").or_insert((0, 0));
|
||||
entry.0 += content.lines().count();
|
||||
entry.1 += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(lang) = detect_language(file_path) {
|
||||
let Ok(content) = fs::read_to_string(file_path) else {
|
||||
continue;
|
||||
};
|
||||
let entry = languages.entry(lang).or_insert((0, 0));
|
||||
entry.0 += content.lines().count();
|
||||
entry.1 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut sorted: Vec<_> = languages.into_iter().collect();
|
||||
sorted.sort_by(|a, b| b.1 .0.cmp(&a.1 .0));
|
||||
|
||||
println!("{CYAN}📊 Statistiques du code{RESET}");
|
||||
println!("{SEPARATOR}");
|
||||
|
||||
let mut total_lines: usize = 0;
|
||||
let mut total_files: usize = 0;
|
||||
|
||||
if livewire_files > 0 {
|
||||
print_line(
|
||||
GREEN,
|
||||
"Livewire SFC (PHP)",
|
||||
livewire_php_lines,
|
||||
Some(livewire_files),
|
||||
);
|
||||
print_line(GREEN, "Livewire SFC (Blade)", livewire_blade_lines, None);
|
||||
total_lines += livewire_php_lines + livewire_blade_lines;
|
||||
total_files += livewire_files;
|
||||
}
|
||||
|
||||
for (lang, (lines, files)) in &sorted {
|
||||
print_line(GREEN, lang, *lines, Some(*files));
|
||||
total_lines += lines;
|
||||
total_files += files;
|
||||
}
|
||||
|
||||
if test_files > 0 {
|
||||
print_line(YELLOW, "Tests", test_lines, Some(test_files));
|
||||
}
|
||||
|
||||
println!("{SEPARATOR}");
|
||||
print_line(CYAN, "Total (hors tests)", total_lines, Some(total_files));
|
||||
print_line(
|
||||
CYAN,
|
||||
"Total",
|
||||
total_lines + test_lines,
|
||||
Some(total_files + test_files),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_line(color: &str, name: &str, lines: usize, files: Option<usize>) {
|
||||
match files {
|
||||
Some(f) => println!(
|
||||
"{color}{:<25}{RESET} {:>6} lignes ({f} fichiers)",
|
||||
name, lines
|
||||
),
|
||||
None => println!("{color}{:<25}{RESET} {:>6} lignes", name, lines),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_lock_file(filename: &str) -> bool {
|
||||
matches!(
|
||||
filename,
|
||||
"composer.lock"
|
||||
| "package-lock.json"
|
||||
| "yarn.lock"
|
||||
| "pnpm-lock.yaml"
|
||||
| "Cargo.lock"
|
||||
| "Gemfile.lock"
|
||||
| "poetry.lock"
|
||||
| "flake.lock"
|
||||
)
|
||||
}
|
||||
|
||||
fn detect_language(path: &Path) -> Option<&'static str> {
|
||||
let filename = path.file_name()?.to_str()?;
|
||||
match filename {
|
||||
"Dockerfile" | "Containerfile" => return Some("Docker"),
|
||||
"Makefile" | "GNUmakefile" => return Some("Makefile"),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let ext = path.extension()?.to_str()?;
|
||||
match ext {
|
||||
"php" => Some("PHP"),
|
||||
"js" | "mjs" | "cjs" => Some("JavaScript"),
|
||||
"ts" | "mts" | "cts" => Some("TypeScript"),
|
||||
"jsx" => Some("JSX"),
|
||||
"tsx" => Some("TSX"),
|
||||
"rs" => Some("Rust"),
|
||||
"py" | "pyw" => Some("Python"),
|
||||
"rb" => Some("Ruby"),
|
||||
"go" => Some("Go"),
|
||||
"java" => Some("Java"),
|
||||
"kt" | "kts" => Some("Kotlin"),
|
||||
"swift" => Some("Swift"),
|
||||
"c" | "h" => Some("C"),
|
||||
"cpp" | "cc" | "cxx" | "hpp" | "hxx" | "hh" => Some("C++"),
|
||||
"cs" => Some("C#"),
|
||||
"hs" | "lhs" => Some("Haskell"),
|
||||
"ex" | "exs" => Some("Elixir"),
|
||||
"erl" | "hrl" => Some("Erlang"),
|
||||
"dart" => Some("Dart"),
|
||||
"lua" => Some("Lua"),
|
||||
"r" | "R" => Some("R"),
|
||||
"scala" | "sc" => Some("Scala"),
|
||||
"zig" => Some("Zig"),
|
||||
"sh" | "bash" | "zsh" | "fish" => Some("Shell"),
|
||||
"sql" => Some("SQL"),
|
||||
"vue" => Some("Vue"),
|
||||
"svelte" => Some("Svelte"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user