use crate::{RdDocument, RdNode};
const SOURCE_FILES_PREFIX: &str = "% Please edit documentation in ";
const SOURCE_FILES_CONTINUATION_PREFIX: &str = "% ";
const GENERATED_PREFIX: &str = "% Generated by ";
const GENERATED_SUFFIX: &str = ": do not edit by hand";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RdGenerationHeader<'a> {
generator: Option<RdGenerator>,
source_files: Vec<&'a str>,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RdGenerator {
Roxygen2,
Unknown(String),
}
impl<'a> RdGenerationHeader<'a> {
pub fn generator(&self) -> Option<&RdGenerator> {
self.generator.as_ref()
}
pub fn is_generated(&self) -> bool {
self.generator().is_some()
}
pub fn source_files(&self) -> &[&'a str] {
&self.source_files
}
pub fn has_sources(&self) -> bool {
!self.source_files.is_empty()
}
}
impl RdDocument {
pub fn generation_header(&self) -> Option<RdGenerationHeader<'_>> {
let mut generator = None;
let mut source_files = Vec::new();
let mut source_block_active = false;
let mut recognized = false;
for node in self.nodes() {
let comment = match node {
RdNode::Comment(comment) => comment.as_str(),
RdNode::Text(text) if text.chars().all(char::is_whitespace) => continue,
_ => break,
};
if comment.contains('\r') {
source_block_active = false;
} else if let Some(found_generator) = parse_generated_marker(comment) {
if generator.is_none() {
generator = Some(found_generator);
}
recognized = true;
source_block_active = false;
} else if let Some(payload) = comment.strip_prefix(SOURCE_FILES_PREFIX) {
recognized = true;
source_block_active = true;
append_sources(payload, &mut source_files);
} else if source_block_active {
if let Some(payload) = comment.strip_prefix(SOURCE_FILES_CONTINUATION_PREFIX) {
recognized = true;
append_sources(payload, &mut source_files);
} else {
source_block_active = false;
}
}
}
recognized.then_some(RdGenerationHeader {
generator,
source_files,
})
}
}
fn parse_generated_marker(comment: &str) -> Option<RdGenerator> {
let name = comment
.strip_prefix(GENERATED_PREFIX)?
.strip_suffix(GENERATED_SUFFIX)?;
if name.is_empty() || name.chars().any(|ch| matches!(ch, ':' | '\r' | '\n')) {
return None;
}
Some(if name == "roxygen2" {
RdGenerator::Roxygen2
} else {
RdGenerator::Unknown(name.to_owned())
})
}
fn append_sources<'a>(payload: &'a str, source_files: &mut Vec<&'a str>) {
source_files.extend(
payload
.split(',')
.map(str::trim)
.filter(|path| !path.is_empty()),
);
}