use std::borrow::Cow;
use rowan::NodeOrToken;
use crate::ast::{AstNode, RoxygenBlock, RoxygenParagraph, RoxygenSection, RoxygenTag};
use crate::parser::parse;
use crate::parser::roxygen::{
MdArgPiece, advance_md_col, is_fragile_for_md, is_known_rd_macro, is_rd_braceless_drop_macro,
is_two_arg_rd_macro, md_fence_run_closes, md_ws_gauge, resolve_md_inline,
resolve_md_inline_pieces, split_table_row_cells, sticky_braceless_code_mode,
};
use crate::roxygen::entities;
use crate::syntax::{SyntaxKind, SyntaxNode, SyntaxToken};
mod collect;
mod escapes;
mod linkrefs;
mod md_blocks;
mod md_links;
mod section;
mod serialize;
mod sexpr;
#[cfg(test)]
mod tests;
mod text;
use self::collect::*;
use self::escapes::*;
use self::linkrefs::*;
use self::md_blocks::*;
use self::md_links::*;
use self::section::*;
use self::serialize::*;
use self::sexpr::*;
use self::text::*;
pub fn project_to_rd(text: &str) -> String {
let cst = parse(text).cst;
let scan_end = roxygen_scan_end(&cst);
let mut groups: Vec<Vec<RoxygenBlock>> = Vec::new();
let mut named: Vec<(String, usize)> = Vec::new();
for block in cst.descendants().filter_map(RoxygenBlock::cast) {
if block.syntax().text_range().start() >= scan_end {
continue;
}
match topic_name(&block) {
Some(name) => match named.iter().find(|(n, _)| *n == name) {
Some(&(_, idx)) => groups[idx].push(block),
None => {
named.push((name, groups.len()));
groups.push(vec![block]);
}
},
None => groups.push(vec![block]),
}
}
let mut sections: Vec<String> = Vec::new();
for group in &groups {
match group.as_slice() {
[block] => project_block(block, &mut sections),
blocks => project_merged_topic(blocks, &mut sections),
}
}
sections.sort();
sections.join("\n")
}
fn roxygen_scan_end(root: &SyntaxNode) -> rowan::TextSize {
root.children_with_tokens()
.filter(|el| {
!matches!(
el.kind(),
SyntaxKind::WHITESPACE
| SyntaxKind::NEWLINE
| SyntaxKind::COMMENT
| SyntaxKind::SEMICOLON
| SyntaxKind::ROXYGEN_BLOCK
)
})
.last()
.map(|el| el.text_range().end())
.unwrap_or_default()
}
fn topic_name(block: &RoxygenBlock) -> Option<String> {
let mut rdname: Option<String> = None;
for section in block.sections() {
let Some(tag) = section.tag() else { continue };
match tag.name().as_deref() {
Some("name") => {
let value = tag.text().map(|t| t.text().trim().to_string());
if let Some(v) = value.filter(|v| !v.is_empty()) {
return Some(v);
}
}
Some("rdname") if rdname.is_none() => {
rdname = tag
.text()
.map(|t| t.text().trim().to_string())
.filter(|v| !v.is_empty());
}
_ => {}
}
}
rdname
}
#[derive(Clone)]
enum Inline {
Text(String),
Macro(SyntaxNode),
MdCode(String),
MdEmphasis {
strong: bool,
children: Vec<Inline>,
},
MdList(SyntaxNode),
MdListResolved {
ordered: bool,
items: Vec<Vec<Inline>>,
},
MdLink(String),
MdInlineLink {
url: String,
display: Vec<Inline>,
},
MdRefLink {
dest: String,
display: Vec<Inline>,
},
MdShortcutLink {
display: Vec<Inline>,
},
MdImage(String),
MdCodeBlock(SyntaxNode),
MdIndentedCode(SyntaxNode),
MdHtml(String),
MdHtmlBlock(SyntaxNode),
MdTable(SyntaxNode),
MdHeading(SyntaxNode),
MdBlockQuote(SyntaxNode),
BraceGroup(Vec<Inline>),
BracelessItem,
StickyVerbatim {
code: bool,
lines: Vec<String>,
},
}