use glob::glob;
use lazy_static::lazy_static;
use regex::Regex;
use crate::{
error_mod::Error,
public_api_mod::{RED, RESET, YELLOW},
Result,
};
#[derive(Debug)]
struct RsMarker {
pub md_filename: String,
pub marker_name: String,
pub comment_symbol: String,
pub pos_start: usize,
pub pos_end: usize,
}
#[derive(Debug)]
struct MdSegment {
pub md_filename: String,
pub marker_name: String,
pub pos_start: usize,
pub pos_end: usize,
pub text: String,
}
pub fn auto_md_to_doc_comments() -> Result<()> {
let mut cache_md_segments = vec![];
for rs_filename in rs_files()?.iter() {
let mut rs_text_content = std::fs::read_to_string(rs_filename)?;
if rs_text_content.contains("\r\n") {
return Err(Error::ErrorFromString(format!(
"{RED}Error: {rs_filename} has CRLF line endings instead of LF. Correct the file! {RESET}"
)));
}
let markers = rs_file_markers(&rs_text_content)?;
if !markers.is_empty() {
for marker in markers.iter().rev() {
let segment_text = get_md_segments_using_cache(
&mut cache_md_segments,
&marker.md_filename,
&marker.marker_name,
&marker.comment_symbol,
)?;
rs_text_content.replace_range(marker.pos_start..marker.pos_end, &segment_text);
}
println!(" {YELLOW}Write file: {rs_filename}{RESET}");
std::fs::write(rs_filename, rs_text_content)?;
}
}
Ok(())
}
fn rs_files() -> Result<Vec<String>> {
let mut rs_files = vec![];
for filename_result in glob("src/**/*.rs")? {
let filename_pathbuff = filename_result?;
let rs_filename = filename_pathbuff
.to_str()
.ok_or_else(|| Error::ErrorFromStr("filename_pathbuff is None"))?
.to_string();
rs_files.push(rs_filename);
}
for filename_result in glob("tests/**/*.rs")? {
let filename_pathbuff = filename_result?;
let rs_filename = filename_pathbuff
.to_str()
.ok_or_else(|| Error::ErrorFromStr("filename_pathbuff is None"))?
.to_string();
rs_files.push(rs_filename);
}
for filename_result in glob("examples/**/*.rs")? {
let filename_pathbuff = filename_result?;
let rs_filename = filename_pathbuff
.to_str()
.ok_or_else(|| Error::ErrorFromStr("filename_pathbuff is None"))?
.to_string();
rs_files.push(rs_filename);
}
Ok(rs_files)
}
lazy_static! {
static ref REGEX_RS_START: Regex = Regex::new(r#"(?m)^ *?// region: auto_md_to_doc_comments include (.*?) (.*?) (.*?)$"#).expect("regex new");
static ref REGEX_RS_END: Regex = Regex::new(r#"(?m)^ *?// endregion: auto_md_to_doc_comments include (.*?) (.*?) (.*?)$"#).expect("regex new");
}
fn rs_file_markers(rs_text_content: &str) -> Result<Vec<RsMarker>> {
let mut markers = vec![];
for cap in REGEX_RS_START.captures_iter(rs_text_content) {
let rs_marker = RsMarker {
md_filename: cap[1].to_string(),
marker_name: cap[2].to_string(),
comment_symbol: cap[3].to_string(),
pos_start: cap.get(0).ok_or_else(|| Error::ErrorFromStr("cap get 0 is None"))?.end() + 1,
pos_end: 0,
};
markers.push(rs_marker);
}
for cap in REGEX_RS_END.captures_iter(rs_text_content) {
let marker = markers
.iter_mut()
.find(|m| m.md_filename == cap[1] && m.marker_name == cap[2])
.ok_or_else(|| Error::ErrorFromStr("find is None"))?;
marker.pos_end = cap.get(0).ok_or_else(|| Error::ErrorFromStr("cap get 0 is None"))?.start();
}
Ok(markers)
}
lazy_static! {
static ref REGEX_MD_START: Regex = Regex::new(r#"(?m)^\[//\]: # \(auto_md_to_doc_comments segment start (.*?)\)$"#).expect("regex new");
static ref REGEX_MD_END: Regex = Regex::new(r#"(?m)^\[//\]: # \(auto_md_to_doc_comments segment end (.*?)\)$"#).expect("regex new");
}
fn get_md_segments_using_cache(cache: &mut Vec<MdSegment>, md_filename: &str, marker_name: &str, comment_symbol: &str) -> Result<String> {
if let Some(_seg) = cache.iter().find(|m| m.md_filename == md_filename) {
let segment = cache
.iter()
.find(|m| m.md_filename == md_filename && m.marker_name == marker_name)
.ok_or_else(|| Error::ErrorFromStr("find is None"))?;
Ok(segment.text.to_string())
} else {
println!(" {YELLOW}Read file: {md_filename}{RESET}");
let md_text_content = std::fs::read_to_string(md_filename)?;
if md_text_content.contains("\r\n") {
return Err(Error::ErrorFromString(format!(
"{RED}Error: {md_filename} has CRLF line endings instead of LF. Correct the file! {RESET}"
)));
}
for cap in REGEX_MD_START.captures_iter(&md_text_content) {
cache.push(MdSegment {
md_filename: md_filename.to_owned(),
marker_name: cap[1].to_owned(),
pos_start: cap.get(0).ok_or_else(|| Error::ErrorFromStr("cap get 0 is None"))?.end() + 1,
pos_end: 0,
text: String::new(),
});
}
for cap in REGEX_MD_END.captures_iter(&md_text_content) {
let segment = cache
.iter_mut()
.find(|m| m.md_filename == md_filename && m.marker_name == cap[1])
.ok_or_else(|| Error::ErrorFromStr("find is None"))?;
segment.pos_end = cap.get(0).ok_or_else(|| Error::ErrorFromStr("cap get 0 is None"))?.start();
let mut last_line_was_comment = true;
for line in md_text_content[segment.pos_start..segment.pos_end].lines() {
if line.starts_with("[//]: # (") {
last_line_was_comment = true;
} else if last_line_was_comment && line.is_empty() {
last_line_was_comment = false;
} else {
last_line_was_comment = false;
segment.text.push_str(comment_symbol);
if !line.is_empty() {
segment.text.push(' ');
}
segment.text.push_str(line);
segment.text.push('\n');
}
}
}
let segment = cache
.iter()
.find(|m| m.md_filename == md_filename && m.marker_name == marker_name)
.ok_or_else(|| Error::ErrorFromStr("find is None"))?;
Ok(segment.text.to_string())
}
}