use super::config::Config; use crate::types::*; use pandoc_types::definition::{Attr, Block, Inline, Meta, MetaValue, Pandoc}; use std::collections::BTreeMap; pub(crate) fn into_pandoc(entity: Entity, config: &Config) -> (Pandoc, BTreeMap) { let mut meta = Meta::null(); let title = vec![Inline::Code(Attr::null(), entity.name.clone())]; meta.0 .insert("title".to_string(), MetaValue::MetaString(entity.name)); let mut content = Vec::new(); content.push(Block::Header(1, Attr::null(), title)); if !entity.documentation.is_empty() { content.push(Block::Header( 2, Attr::null(), vec![Inline::Str(String::from("Description"))], )); content.push(Block::Div( Attr(String::new(), vec![String::from("doc")], vec![]), vec![raw_markdown(entity.documentation.clone())], )); } let mut inline_children = BTreeMap::new(); let mut separate_children = BTreeMap::new(); let entity_kind = entity.kind; for (children_kind, children) in entity.children { if is_inline(config, entity.language, &entity_kind, &children_kind) { inline_children.insert(children_kind, children); } else { // By default, do not inline separate_children.insert(children_kind, children); } } for (section_name, children) in &separate_children { if let Some(members_list) = member_list(children, LinkType::External) { content.push(Block::Header( 2, Attr::null(), vec![Inline::Str(section_name.0.clone())], )); content.push(members_list); } } let mut inline_documentation = Vec::new(); for (section_name, children) in inline_children { if let Some(members_list) = member_list(&children, LinkType::Anchor) { content.push(Block::Header( 2, Attr::null(), vec![Inline::Str(section_name.0.clone())], )); content.push(members_list); inline_documentation.push(Block::Header( 2, Attr::null(), vec![Inline::Str(section_name.0 + " Documentation")], )); for (id, child) in children { inline_documentation.push(Block::Header( 3, Attr(id.0, vec![], vec![]), vec![Inline::Code(Attr::null(), child.name)], )); inline_documentation.push(Block::Div( Attr(String::new(), vec![String::from("doc")], vec![]), vec![raw_markdown(child.documentation)], )); } } } content.append(&mut inline_documentation); let leftovers = separate_children .into_iter() .map(|(_section_name, children)| children) .flatten() .collect(); (Pandoc(meta, content), leftovers) } fn is_inline( config: &Config, language: Language, parent_kind: &EntityKind, children_kind: &ChildrenKind, ) -> bool { config .inlines .get(&language) .and_then(|lang_inlines| lang_inlines.get(parent_kind)) .map(|children_inlines| children_inlines.contains(children_kind)) == Some(true) } fn str_block(content: String) -> Block { Block::Plain(vec![Inline::Str(content)]) } fn entity_link(id: &EntityId, name: String, link_type: LinkType) -> Inline { use pandoc_types::definition::Target; use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; use std::iter::once; // https://url.spec.whatwg.org/#fragment-percent-encode-set const FRAGMENT: &AsciiSet = &CONTROLS .add(b' ') .add(b'"') .add(b'<') .add(b'>') .add(b'`') .add(b'#') .add(b'?') .add(b'{') .add(b'}'); Inline::Link( Attr::null(), vec![Inline::Code(Attr::null(), name)], Target( once(match link_type { LinkType::External => "./", LinkType::Anchor => "#", }) .chain(utf8_percent_encode(&id.0, FRAGMENT)) .collect(), String::new(), ), ) } fn raw_markdown(text: String) -> Block { use pandoc_types::definition::Format; Block::RawBlock(Format(String::from("markdown")), text) } #[derive(Debug, Clone, Copy)] enum LinkType { Anchor, External, } fn member_list<'a>( members: impl IntoIterator, link_type: LinkType, ) -> Option { let definitions: Vec<(Vec, Vec>)> = members .into_iter() .map(|(id, entity)| { ( vec![entity_link(id, entity.name.clone(), link_type)], vec![vec![str_block(entity.brief_description.clone())]], ) }) .collect(); if definitions.is_empty() { None } else { Some(Block::DefinitionList(definitions)) } }