use std::path::Path;
use std::path::PathBuf;
use maud::Markup;
use maud::html;
use wdl_ast::AstToken;
use wdl_ast::Documented;
use wdl_ast::SupportedVersion;
use wdl_ast::v1::Decl;
use wdl_ast::v1::MetadataValue;
use wdl_ast::v1::StructDefinition;
use crate::VersionBadge;
use crate::docs_tree::PageSections;
use crate::links::PageLinkIndex;
use crate::meta::DESCRIPTION_KEY;
use crate::meta::DefinitionMeta;
use crate::meta::MetaMap;
use crate::meta::MetaMapExt;
use crate::meta::MetaMapValueSource;
use crate::meta::doc_comments;
use crate::meta::main_container;
use crate::meta::parse_metadata_items;
use crate::page::DeclarationHero;
#[derive(Debug)]
struct Member {
decl: Decl,
meta: MetaMap,
}
impl Member {
fn new(decl: Decl, meta: MetaMap) -> Self {
Self { decl, meta }
}
}
impl DefinitionMeta for Member {
fn meta(&self) -> &MetaMap {
&self.meta
}
}
#[derive(Debug)]
pub struct Struct {
meta: MetaMap,
members: Vec<Member>,
definition: StructDefinition,
version: VersionBadge,
external: bool,
wdl_path: Option<PathBuf>,
}
impl DefinitionMeta for Struct {
fn meta(&self) -> &MetaMap {
&self.meta
}
}
impl Struct {
pub fn new(
definition: StructDefinition,
version: SupportedVersion,
external: bool,
wdl_path: Option<PathBuf>,
enable_doc_comments: bool,
) -> Self {
let mut meta = definition
.metadata()
.map(|meta| parse_metadata_items(meta.items()))
.fold(MetaMap::new(), |mut acc, mut meta| {
acc.append(&mut meta);
acc
});
if enable_doc_comments && let Some(comments) = definition.doc_comments() {
meta.append(&mut doc_comments(comments));
}
let parameter_meta = definition
.parameter_metadata()
.map(|meta| parse_metadata_items(meta.items()))
.fold(MetaMap::new(), |mut acc, mut meta| {
acc.append(&mut meta);
acc
});
let members = parse_member_meta(&definition, ¶meter_meta, enable_doc_comments);
Self {
meta,
members,
definition,
version: VersionBadge::new(version),
external,
wdl_path,
}
}
pub fn render(
&self,
assets: &Path,
links: &PageLinkIndex,
page_dir: &Path,
) -> (Markup, PageSections) {
let name = self.definition.name();
let name = name.text();
let members = html! {
div class="main__section" {
h2 id="struct-members" class="main__section-header" { "Members" }
div class="main__grid-container" {
div class="main__grid-struct-member-container" {
div class="main__grid-header-cell" { "Name" }
div class="main__grid-header-cell" { "Type" }
div class="main__grid-header-cell" { "Description" }
div class="main__grid-header-separator" {}
@for member in self.members.iter() {
@let member_name = member.decl.name();
@let member_id = format!("member.{}", member_name.text());
div id=(member_id) class="main__grid-row" x-data="{ description_expanded: false }" {
div class="main__grid-cell" {
code { (member_name.text()) }
}
div class="main__grid-cell" {
(links.render_type(&member.decl.ty().to_string(), page_dir))
}
div class="main__grid-cell" {
(member.meta().render_description(true))
}
div x-show="description_expanded" class="main__grid-full-width-cell" {
(member.meta().render_description(false))
}
}
div class="main__grid-row-separator" {}
}
}
}
}
};
let meta_markup = self
.meta
.render_remaining(&[DESCRIPTION_KEY])
.map_or_else(|| html! {}, |markup| html! { (markup) });
let mut hero = DeclarationHero::new("Struct", name, self.meta.render_description(false))
.kind_class("text-brand-pink-400")
.pagefind_type("struct")
.badge(self.version.render());
if let Some(path) = self.wdl_path.as_deref() {
hero = hero.source_path(path);
}
let markup = html! {
(hero.render(assets))
@if let Some(body) = self.meta.render_authored_body(assets) {
(body)
}
div class="main__section" {
sprocket-code language="wdl" copyable expandable line-numbers {
(self.definition)
}
}
div class="main__section" {
(meta_markup)
}
(members)
};
(
main_container("struct", self.external, markup),
PageSections::default(),
)
}
}
fn parse_member_meta(
definition: &StructDefinition,
parameter_meta: &MetaMap,
enable_doc_comments: bool,
) -> Vec<Member> {
definition
.members()
.map(|decl| {
let name = decl.name().text().to_owned();
let mut meta_map = MetaMap::default();
if let Some(MetaMapValueSource::MetaValue(meta)) = parameter_meta.get(&name) {
match meta {
MetadataValue::Object(o) => {
for item in o.items() {
meta_map.insert(
item.name().text().to_string(),
MetaMapValueSource::MetaValue(item.value().clone()),
);
}
}
MetadataValue::String(_s) => {
meta_map.insert(
DESCRIPTION_KEY.to_string(),
MetaMapValueSource::MetaValue(meta.clone()),
);
}
_ => {}
}
}
if enable_doc_comments && let Some(comments) = decl.doc_comments() {
meta_map.append(&mut doc_comments(comments));
}
Member::new(Decl::Unbound(decl), meta_map)
})
.collect()
}
#[cfg(test)]
mod tests {
use std::path::Path;
use std::path::PathBuf;
use wdl_ast::Document;
use wdl_ast::SupportedVersion;
use wdl_ast::version::V1;
use super::Struct;
use crate::links::PageLinkIndex;
#[test]
fn links_member_struct_types() {
let (doc, _) = Document::parse(
r#"
version 1.2
struct Employee {
Person person
Int id
}
"#,
None,
);
let item = doc.ast().into_v1().unwrap().items().next().unwrap();
let def = item.into_struct_definition().unwrap();
let r#struct = Struct::new(def, SupportedVersion::V1(V1::Two), false, None, true);
let links = PageLinkIndex::from_pages([("Person", PathBuf::from("Person-struct.html"))]);
let (markup, _) = r#struct.render(Path::new("assets"), &links, Path::new(""));
let html = markup.into_string();
assert!(html.contains("href=\"Person-struct.html\""));
assert!(html.contains(">Person</a>"));
assert!(!html.contains("href=\"Int"));
}
}