codestats 0.6.0

A CLI tool for analyzing code statistics across different programming languages
use std::path::Path;

use anyhow::Result;

use super::{
	encoding::{FileEncoding, decode_bytes},
	file_io::LineSource,
	line_classifier::{self, CommentState, LineType},
	stats::{AnalysisResults, FileContribution, FileStats},
};
use crate::langs::Language;

#[derive(Default)]
pub(super) struct LineCounts {
	pub(super) total: u64,
	pub(super) code: u64,
	pub(super) comment: u64,
	pub(super) blank: u64,
	pub(super) shebang: u64,
}

impl LineCounts {
	pub(super) fn classify_and_count(
		&mut self,
		line: &str,
		lang_info: Option<&Language>,
		comment_state: &mut CommentState,
		is_first_line: bool,
	) {
		let line_type = line_classifier::classify_line(line, lang_info, comment_state, is_first_line);
		match line_type {
			LineType::Code => self.code += 1,
			LineType::Comment => self.comment += 1,
			LineType::Blank => self.blank += 1,
			LineType::Shebang => self.shebang += 1,
		}
		self.total += 1;
	}
}

pub(super) fn process_lines<S>(
	file_path: &Path,
	file_size: u64,
	results: &mut AnalysisResults,
	collect_details: bool,
	language: &'static Language,
	encoding: FileEncoding,
	source: &mut S,
) -> Result<()>
where
	S: LineSource,
{
	let mut is_first_line = true;
	let line_counts = count_lines_with(
		|handle| {
			source.for_each_line(&mut |line_bytes| {
				let decoded = decode_bytes(line_bytes, encoding, is_first_line);
				handle(decoded.as_ref(), is_first_line);
				is_first_line = false;
			})
		},
		language,
	)?;
	finish_file_stats(file_path, file_size, results, collect_details, language, &line_counts);
	Ok(())
}

pub(super) fn finish_file_stats(
	file_path: &Path,
	file_size: u64,
	results: &mut AnalysisResults,
	collect_details: bool,
	language: &'static Language,
	line_counts: &LineCounts,
) {
	let total = line_counts.total;
	let code = line_counts.code;
	let comment = line_counts.comment;
	let blank = line_counts.blank;
	let shebang = line_counts.shebang;
	let contribution = FileContribution::new(total, code, comment, blank, shebang, file_size);
	let file_stats = collect_details
		.then(|| FileStats::new(file_path.display().to_string(), total, code, comment, blank, shebang, file_size));
	results.add_file_stats(language, contribution, file_stats);
}

fn count_lines_with(
	mut for_each: impl FnMut(&mut dyn FnMut(&str, bool)) -> Result<()>,
	language: &'static Language,
) -> Result<LineCounts> {
	let mut line_counts = LineCounts::default();
	let mut comment_state = CommentState::new();
	for_each(&mut |line, is_first_line| {
		line_counts.classify_and_count(line, Some(language), &mut comment_state, is_first_line);
	})?;
	Ok(line_counts)
}