use clap::{Parser, Subcommand};
use std::path::Path;
use std::time::Instant;
mod analyzer;
mod color;
mod lang;
mod logger;
mod render;
mod utils;
use crate::logger::{Level, Log, Verbosity, logger};
#[derive(Parser, Debug)]
#[command(name = "lcount", version)]
#[command(about = "A command-line tool written in Rust that counts lines of code. It recursively scans directories, counts lines by programming language, and visualizes them in a colorful horizontal bar chart in the terminal.", long_about = None)]
#[command(propagate_version = true)]
struct Args {
#[arg(short, long, default_value = ".", global = true)]
path: String,
#[arg(short, long, action = clap::ArgAction::Count, global = true)]
verbose: u8,
#[command(subcommand)]
command: Commands,
}
#[derive(Debug, Subcommand)]
enum Commands {
Stats {
#[arg(short, long, value_name = "COMMIT_HASH")]
commit: Option<String>,
},
Diff {
#[arg(short, long)]
base: String,
#[arg(short, long, default_value_t = String::from("WORKTREE"))]
target: String,
},
}
fn main() {
let args = Args::parse();
let path = Path::new(&args.path);
if !path.exists() || !path.is_dir() {
logger(
Log::new(
Level::Error,
format!(
"The path doesn't exist or isn't a directory: \"{}\". Please check if the path is correct.",
&args.path
),
None,
),
None,
);
return;
}
let verbose = args.verbose;
let t_start = Instant::now();
match args.command {
Commands::Stats { commit } => {
let stats = if let Some(commit_hash) = &commit {
let repo = match utils::open_repository(path) {
Ok(r) => r,
Err(log) => {
logger(log, Some(Verbosity::from_u8(&verbose)));
return;
}
};
let commit = match utils::get_commit_from(&repo, commit_hash) {
Ok(c) => c,
Err(log) => {
logger(log, Some(Verbosity::from_u8(&verbose)));
return;
}
};
logger(
Log::new(
Level::Info,
format!(
"scanning commit {} ({})",
&commit_hash[..12.min(commit_hash.len())],
commit.summary().unwrap_or("<no message>")
),
Some(Verbosity::Verbose),
),
Some(Verbosity::from_u8(&verbose)),
);
analyzer::analyze_project(
&analyzer::Project::Commit {
repo: &repo,
commit: &commit,
},
&verbose,
)
} else {
analyzer::analyze_project(&analyzer::Project::Disk { root: path }, &verbose)
};
let t_analyzed = t_start.elapsed();
logger(
Log::new(
Level::Info,
format!("analyze: {:.2?}", t_analyzed),
Some(Verbosity::Verbose),
),
Some(Verbosity::from_u8(&verbose)),
);
if let Err(log) = render::render(render::Mode::Stats { stats }) {
logger(log, Some(Verbosity::from_u8(&verbose)));
}
}
Commands::Diff { base, target } => {
let repo = match utils::open_repository(path) {
Ok(r) => r,
Err(log) => {
logger(log, Some(Verbosity::from_u8(&verbose)));
return;
}
};
let base_commit = match utils::get_commit_from(&repo, &base) {
Ok(c) => c,
Err(log) => {
logger(log, Some(Verbosity::from_u8(&verbose)));
return;
}
};
logger(
Log::new(
Level::Info,
format!(
"diff base commit {} ({})",
&base[..12.min(base.len())],
base_commit.summary().unwrap_or("<no message>")
),
Some(Verbosity::Verbose),
),
Some(Verbosity::from_u8(&verbose)),
);
let base_project = analyzer::Project::Commit {
repo: &repo,
commit: &base_commit,
};
let base_stats = analyzer::analyze_project(&base_project, &verbose);
let target_stats = if target.eq_ignore_ascii_case("WORKTREE") {
logger(
Log::new(
Level::Info,
"target: WORKTREE".to_string(),
Some(Verbosity::Verbose),
),
Some(Verbosity::from_u8(&verbose)),
);
analyzer::analyze_project(&analyzer::Project::Disk { root: path }, &verbose)
} else {
let target_commit = match utils::get_commit_from(&repo, &target) {
Ok(c) => c,
Err(log) => {
logger(log, Some(Verbosity::from_u8(&verbose)));
return;
}
};
logger(
Log::new(
Level::Info,
format!(
"target commit {} ({})",
&target[..12.min(target.len())],
target_commit.summary().unwrap_or("<no message>")
),
Some(Verbosity::Verbose),
),
Some(Verbosity::from_u8(&verbose)),
);
analyzer::analyze_project(
&analyzer::Project::Commit {
repo: &repo,
commit: &target_commit,
},
&verbose,
)
};
let t_analyzed = t_start.elapsed();
logger(
Log::new(
Level::Info,
format!("analyze: {:.2?}", t_analyzed),
Some(Verbosity::Verbose),
),
Some(Verbosity::from_u8(&verbose)),
);
if let Err(log) = render::render(render::Mode::Diff {
base: base_stats,
target: target_stats,
}) {
logger(log, Some(Verbosity::from_u8(&verbose)));
}
}
}
}