rd_ast/view/
generation.rs1use crate::{RdDocument, RdNode};
2
3const SOURCE_FILES_PREFIX: &str = "% Please edit documentation in ";
4const SOURCE_FILES_CONTINUATION_PREFIX: &str = "% ";
5const GENERATED_PREFIX: &str = "% Generated by ";
6const GENERATED_SUFFIX: &str = ": do not edit by hand";
7
8#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct RdGenerationHeader<'a> {
22 generator: Option<RdGenerator>,
23 source_files: Vec<&'a str>,
24}
25
26#[non_exhaustive]
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29pub enum RdGenerator {
30 Roxygen2,
31 Unknown(String),
32}
33
34impl<'a> RdGenerationHeader<'a> {
35 pub fn generator(&self) -> Option<&RdGenerator> {
36 self.generator.as_ref()
37 }
38
39 pub fn is_generated(&self) -> bool {
40 self.generator().is_some()
41 }
42
43 pub fn source_files(&self) -> &[&'a str] {
44 &self.source_files
45 }
46
47 pub fn has_sources(&self) -> bool {
48 !self.source_files.is_empty()
49 }
50}
51
52impl RdDocument {
53 pub fn generation_header(&self) -> Option<RdGenerationHeader<'_>> {
59 let mut generator = None;
60 let mut source_files = Vec::new();
61 let mut source_block_active = false;
62 let mut recognized = false;
63
64 for node in self.nodes() {
65 let comment = match node {
66 RdNode::Comment(comment) => comment.as_str(),
67 RdNode::Text(text) if text.chars().all(char::is_whitespace) => continue,
68 _ => break,
69 };
70
71 if comment.contains('\r') {
72 source_block_active = false;
73 } else if let Some(found_generator) = parse_generated_marker(comment) {
74 if generator.is_none() {
75 generator = Some(found_generator);
76 }
77 recognized = true;
78 source_block_active = false;
79 } else if let Some(payload) = comment.strip_prefix(SOURCE_FILES_PREFIX) {
80 recognized = true;
81 source_block_active = true;
82 append_sources(payload, &mut source_files);
83 } else if source_block_active {
84 if let Some(payload) = comment.strip_prefix(SOURCE_FILES_CONTINUATION_PREFIX) {
85 recognized = true;
86 append_sources(payload, &mut source_files);
87 } else {
88 source_block_active = false;
89 }
90 }
91 }
92
93 recognized.then_some(RdGenerationHeader {
94 generator,
95 source_files,
96 })
97 }
98}
99
100fn parse_generated_marker(comment: &str) -> Option<RdGenerator> {
101 let name = comment
102 .strip_prefix(GENERATED_PREFIX)?
103 .strip_suffix(GENERATED_SUFFIX)?;
104 if name.is_empty() || name.chars().any(|ch| matches!(ch, ':' | '\r' | '\n')) {
105 return None;
106 }
107 Some(if name == "roxygen2" {
108 RdGenerator::Roxygen2
109 } else {
110 RdGenerator::Unknown(name.to_owned())
111 })
112}
113
114fn append_sources<'a>(payload: &'a str, source_files: &mut Vec<&'a str>) {
115 source_files.extend(
116 payload
117 .split(',')
118 .map(str::trim)
119 .filter(|path| !path.is_empty()),
120 );
121}