use std::collections::HashMap;
use std::fmt;
use std::fs::{self, File};
use std::io::{self, BufRead, BufReader};
use std::ops;
use std::path::PathBuf;
use crossbeam_channel as chan;
use crossbeam_utils::thread;
use super::languages::{self, Language};
use super::states::*;
use super::Config;
const BUF_SIZE: usize = 1 << 16;
#[derive(Debug)]
pub struct LOCCount<'a>(HashMap<&'a str, (CountResult, usize)>);
impl<'a> ops::AddAssign<CountResult> for LOCCount<'a> {
#[inline]
fn add_assign(&mut self, rhs: CountResult) {
self.0
.entry(rhs.lang)
.and_modify(|(cnt_res, num_files)| {
*cnt_res += rhs;
*num_files += 1;
})
.or_insert((rhs, 1));
}
}
impl<'a, 'b: 'a> ops::AddAssign<LOCCount<'b>> for LOCCount<'a> {
#[inline]
fn add_assign(&mut self, rhs: LOCCount<'b>) {
for (lang, content) in rhs.0.iter() {
self.0
.entry(lang)
.and_modify(|(cnt_res, num_files)| {
*cnt_res += content.0;
*num_files += content.1;
})
.or_insert((content.0, content.1));
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct CountResult {
lang: &'static str,
pub total: usize,
pub code: usize,
pub comments: usize,
pub blank: usize,
}
impl ops::AddAssign for CountResult {
fn add_assign(&mut self, rhs: Self) {
if cfg!(debug_assertions) {
if self.lang != "Total" {
debug_assert_eq!(self.lang, rhs.lang);
}
}
self.total += rhs.total;
self.code += rhs.code;
self.comments += rhs.comments;
self.blank += rhs.blank;
}
}
impl CountResult {
#[inline]
pub fn new(lang: &'static str) -> Self {
CountResult {
lang,
total: 0,
code: 0,
comments: 0,
blank: 0,
}
}
}
#[derive(Debug)]
struct Coordinator<'coord> {
config: &'coord Config,
tx: chan::Sender<PathBuf>,
rx: chan::Receiver<LOCCount<'coord>>,
}
impl<'coord> Coordinator<'coord> {
#[inline]
fn run(self) -> io::Result<LOCCount<'coord>> {
self.walk_paths()?;
self.aggregate_results()
}
#[inline]
fn aggregate_results(self) -> io::Result<LOCCount<'coord>> {
drop(self.tx);
let mut ret: LOCCount<'coord> = LOCCount(HashMap::new());
rlocc_dbg_log!("[Coordinator][aggregate_results] Blocking on res_rx...");
while let Ok(res) = self.rx.recv() {
rlocc_dbg_log!(
"[Coordinator][aggregate_results] Received '{:?}'. Blocking on res_rx again...",
res
);
ret += res;
}
rlocc_dbg_log!("[Coordinator][aggregate_results] res_rs looks disconnected and empty!");
Ok(ret)
}
#[inline]
fn walk_paths(&self) -> io::Result<()> {
for path in self.config.paths.iter() {
if path.is_file() {
rlocc_dbg_log!("[Coordinator][walk_paths] Sending {:?}...", path);
self.tx.send(path.to_owned()).unwrap();
} else if path.is_dir() && !languages::is_vcs(&path) {
rlocc_dbg_log!("[Coordinator][walk_paths] Diving into {:?}...", path);
self.__walk(path)?;
} else {
rlocc_dbg_log!(
"[Coordinator][walk_paths] Skipping non-regular file {:?}.",
path
);
}
}
Ok(())
}
fn __walk(&self, path: &PathBuf) -> io::Result<()> {
for direntry in fs::read_dir(path)? {
let direntry = direntry?.path();
if direntry.is_file() {
rlocc_dbg_log!("[Coordinator][__walk] Sending {:?}...", direntry);
self.tx.send(direntry).unwrap();
} else if direntry.is_dir() && !languages::is_vcs(&direntry) {
rlocc_dbg_log!("[Coordinator][__walk] Diving into {:?}...", direntry);
self.__walk(&direntry)?;
} else {
rlocc_dbg_log!(
"[Coordinator][__walk] Skipping non-regular file {:?}.",
direntry
);
}
}
Ok(())
}
}
#[derive(Debug)]
pub struct ParsingState<'line> {
pub curr_line: Option<&'line str>,
pub curr_line_counted: bool,
pub curr_lang: &'line Language,
}
impl<'line> ParsingState<'line> {
#[inline]
pub fn new(lang: &'line Language) -> Self {
ParsingState {
curr_line: None,
curr_line_counted: false,
curr_lang: lang,
}
}
}
#[derive(Debug)]
struct Worker<'w> {
id: usize,
tx: chan::Sender<LOCCount<'w>>,
rx: chan::Receiver<PathBuf>,
sm: LOCStateMachine,
buffer: String,
}
impl<'w, 'line, 'worker: 'line> Worker<'w> {
fn run(mut self) -> io::Result<()> {
rlocc_dbg_log!("[Worker-{}][run] Blocking on paths_rx...", self.id);
let mut ret = LOCCount(HashMap::new());
while let Ok(path) = self.rx.recv() {
rlocc_dbg_log!(
"[Worker-{}][run] Received {:?} from paths_rx!",
self.id,
path
);
match self.process_file(&path) {
Ok(res) => {
rlocc_dbg_log!(
"[Worker-{}][run] Calculation for file {:?} has been completed!",
self.id,
path
);
ret += res;
rlocc_dbg_log!(
"[Worker-{}][run] Sent! Now blocking on paths_rx again...",
self.id
);
}
Err(_err) => {
rlocc_dbg_log!(
"[Worker-{}][run] Error while processing file {:?}: {:#?}",
self.id,
path,
_err
);
}
};
}
rlocc_dbg_log!(
"[Worker-{}][run] paths_rx looks disconnected and empty!",
self.id
);
rlocc_dbg_log!(
"[Worker-{}][run] Sending '{:?}' down on res_rx...",
self.id,
ret
);
self.tx.send(ret).unwrap();
rlocc_dbg_log!("[Worker-{}][run] Sent! My job is done...", self.id);
drop(self.tx);
Ok(())
}
fn process_file(&mut self, path: &PathBuf) -> io::Result<CountResult> {
let (_, lang) = languages::guess_language(path)?;
let mut ret = CountResult::new(lang.name);
self.sm.reset();
let mut file_rd = BufReader::with_capacity(BUF_SIZE, File::open(path)?);
loop {
let mut ps = ParsingState::new(lang);
self.buffer.clear();
match file_rd.read_line(&mut self.buffer) {
Ok(0) => {
rlocc_dbg_log!(
"[worker-{}][process_file] Reached EOF in file {:?}",
self.id,
path
);
break;
}
Ok(_) => {
self.process_line(&mut ps, &mut ret)?;
}
Err(err) => {
rlocc_dbg_log!(
"[worker-{}][process_file] Error reading lines in file {:?}: {}",
self.id,
path,
err
);
return Err(err);
}
}
}
Ok(ret)
}
#[inline]
fn process_line(
&'worker mut self,
ps: &mut ParsingState<'line>,
cr: &mut CountResult,
) -> io::Result<()> {
ps.curr_line = Some(&self.buffer.trim_start());
self.sm.process(ps, cr);
cr.total += 1;
debug_assert_eq!(cr.total, cr.code + cr.comments + cr.blank);
Ok(())
}
}
pub fn count_all(config: &Config) -> io::Result<LOCCount> {
let mut ret: Option<io::Result<LOCCount>> = None;
thread::scope(|s| {
let (paths_tx, paths_rx) = chan::unbounded();
let (res_tx, res_rx) = chan::unbounded();
for id in 0..config.num_threads {
let tx = res_tx.clone();
let rx = paths_rx.clone();
s.spawn(move |_| {
let worker = Worker {
id,
tx,
rx,
sm: LOCStateMachine::new(),
buffer: String::with_capacity(BUF_SIZE),
};
worker.run()
});
}
drop(paths_rx);
drop(res_tx);
let coord = Coordinator {
config,
tx: paths_tx,
rx: res_rx,
};
ret = Some(coord.run());
})
.unwrap();
ret.unwrap()
}
impl fmt::Display for LOCCount<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
const MAX_OUT_WIDTH: usize = 80;
const LANG_WIDTH: usize = 25;
const FILES_WIDTH: usize = 11;
const LINES_WIDTH: usize = 11;
const CODE_WIDTH: usize = 11;
const COMM_WIDTH: usize = 11;
const BLANK_WIDTH: usize = 11;
writeln!(f, "{:-^max$}", "", max = MAX_OUT_WIDTH)?;
writeln!(
f,
"{:<law$}{:>fw$}{:>liw$}{:>bw$}{:>cmw$}{:>cdw$}",
"Language",
"Files",
"Lines",
"Blanks",
"Comments",
"Code",
law = LANG_WIDTH,
fw = FILES_WIDTH,
liw = LINES_WIDTH,
bw = BLANK_WIDTH,
cmw = COMM_WIDTH,
cdw = CODE_WIDTH,
)?;
writeln!(f, "{:-^max$}", "", max = MAX_OUT_WIDTH)?;
let mut total_cr = CountResult::new("Total");
let mut total_files = 0;
for (lang_name, (cr, fc)) in &self.0 {
total_cr += *cr;
total_files += fc;
writeln!(
f,
"{:<law$}{:>fw$}{:>liw$}{:>bw$}{:>cmw$}{:>cdw$}",
lang_name,
fc,
cr.total,
cr.blank,
cr.comments,
cr.code,
law = LANG_WIDTH,
fw = FILES_WIDTH,
liw = LINES_WIDTH,
bw = BLANK_WIDTH,
cmw = COMM_WIDTH,
cdw = CODE_WIDTH,
)?;
}
writeln!(f, "{:-^max$}", "", max = MAX_OUT_WIDTH)?;
writeln!(
f,
"{:<law$}{:>fw$}{:>liw$}{:>bw$}{:>cmw$}{:>cdw$}",
total_cr.lang,
total_files,
total_cr.total,
total_cr.blank,
total_cr.comments,
total_cr.code,
law = LANG_WIDTH,
fw = FILES_WIDTH,
liw = LINES_WIDTH,
bw = BLANK_WIDTH,
cmw = COMM_WIDTH,
cdw = CODE_WIDTH,
)?;
write!(f, "{:-^max$}", "", max = MAX_OUT_WIDTH)
}
}