use crate::error_mod::Error;
use crate::public_api_mod::{RED, RESET, YELLOW};
use crate::Result;
use std::fs::File;
use std::io::{BufRead, BufReader};
#[derive(Default, Debug)]
pub struct LinesOfCode {
pub src_code_lines: usize,
pub src_doc_comment_lines: usize,
pub src_comment_lines: usize,
pub tests_lines: usize,
pub examples_lines: usize,
}
pub fn auto_lines_of_code(link: &str) -> Result<()> {
println!(" {YELLOW}Running auto_lines_of_code{RESET}");
let link = if link.is_empty() {
crate::auto_git_mod::process_git_remote()
} else {
link.to_string()
};
let lines_of_code = count_lines_of_code()?;
let text_to_include = to_string_as_shield_badges(&lines_of_code, &link);
include_into_readme_md(&text_to_include)?;
println!(" {YELLOW}Finished auto_lines_of_code{RESET}");
Ok(())
}
pub fn count_lines_of_code() -> Result<LinesOfCode> {
let mut lines_of_code = LinesOfCode::default();
let files = crate::utils_mod::traverse_dir_with_exclude_dir(
camino::Utf8Path::new("src").as_std_path(),
"/*.rs",
&["/.git".to_string(), "/target".to_string(), "/docs".to_string()],
)?;
for rs_file_name in files.iter() {
let file = File::open(rs_file_name)?;
let reader = BufReader::new(file);
let mut is_unit_test = false;
for line in reader.lines() {
let line = line?; let line = line.trim_start();
if line == "// exclude from auto_lines_of_code" {
break;
}
if line.starts_with("///") || line.starts_with("//!") {
lines_of_code.src_doc_comment_lines += 1;
} else if line.starts_with("//") || line.starts_with("/!") {
lines_of_code.src_comment_lines += 1;
} else if line.starts_with("#[cfg(test)]") {
is_unit_test = true;
} else if is_unit_test {
lines_of_code.tests_lines += 1;
} else {
lines_of_code.src_code_lines += 1;
}
}
}
let files = crate::utils_mod::traverse_dir_with_exclude_dir(
camino::Utf8Path::new("tests").as_std_path(),
"/*.rs",
&["/.git".to_string(), "/target".to_string(), "/docs".to_string()],
)?;
for rs_file_name in files.iter() {
let file = File::open(rs_file_name)?;
let reader = BufReader::new(file);
for _line in reader.lines() {
lines_of_code.tests_lines += 1;
}
}
let files = crate::utils_mod::traverse_dir_with_exclude_dir(
camino::Utf8Path::new("examples").as_std_path(),
"/*.rs",
&["/.git".to_string(), "/target".to_string(), "/docs".to_string()],
)?;
for rs_file_name in files.iter() {
let file = File::open(rs_file_name)?;
let reader = BufReader::new(file);
for _line in reader.lines().enumerate() {
lines_of_code.examples_lines += 1;
}
}
Ok(lines_of_code)
}
fn to_string_as_shield_badges(v: &LinesOfCode, link: &str) -> String {
let src_code_lines = format!(
"[]({})",
v.src_code_lines, link
);
let src_doc_comment_lines = format!(
"[]({})",
v.src_doc_comment_lines, link
);
let src_comment_lines = format!(
"[]({})",
v.src_comment_lines, link
);
let example_lines = format!(
"[]({})",
v.examples_lines, link
);
let tests_lines = format!(
"[]({})",
v.tests_lines, link
);
format!(
"{}\n{}\n{}\n{}\n{}\n",
src_code_lines, src_doc_comment_lines, src_comment_lines, example_lines, tests_lines
)
}
fn include_into_readme_md(include_str: &str) -> Result<()> {
let start_delimiter = "[//]: # (auto_lines_of_code start)";
let end_delimiter = "[//]: # (auto_lines_of_code end)";
let file_name = "README.md";
if let Ok(readme_content) = std::fs::read_to_string(file_name) {
if readme_content.contains("\r\n") {
return Err(Error::ErrorFromString(format!(
"{RED}Error: {} has CRLF line endings instead of LF. Correct the file! {RESET}",
file_name
)));
}
let mut new_readme_content = String::with_capacity(readme_content.len());
if let Some(mut pos_start) = readme_content.find(start_delimiter) {
pos_start += start_delimiter.len();
if let Some(pos_end) = readme_content.find(end_delimiter) {
new_readme_content.push_str(&readme_content[..pos_start]);
new_readme_content.push('\n');
new_readme_content.push_str(include_str);
new_readme_content.push('\n');
new_readme_content.push_str(&readme_content[pos_end..]);
std::fs::write(file_name, new_readme_content)?;
}
}
}
Ok(())
}