use std::collections::BTreeMap;
use std::path::Path;
use maud::Markup;
use maud::html;
use wdl_ast::AstNode;
use wdl_ast::AstToken;
use wdl_ast::SyntaxKind;
use wdl_ast::v1::MetadataValue;
use crate::Markdown;
use crate::Render;
pub(crate) const DESCRIPTION_KEY: &str = "description";
const HELP_KEY: &str = "help";
const EXTERNAL_HELP_KEY: &str = "external_help";
const WARNING_KEY: &str = "warning";
const DESCRIPTION_MAX_LENGTH: usize = 140;
const DESCRIPTION_CLIP_LENGTH: usize = 80;
pub(crate) type MetaMap = BTreeMap<String, MetadataValue>;
pub(crate) trait MetaMapExt {
fn render_description(&self, summarize: bool) -> Markup;
fn render_remaining(&self, filter_keys: &[&str], assets: &Path) -> Option<Markup>;
}
impl MetaMapExt for MetaMap {
fn render_description(&self, summarize: bool) -> Markup {
let desc = self
.get(DESCRIPTION_KEY)
.map(|v| match v {
MetadataValue::String(s) => {
let t = s.text().expect("meta string should not be interpolated");
t.text().to_string()
}
_ => "ERROR: description not of type String".to_string(),
})
.unwrap_or_else(|| "No description provided".to_string());
if !summarize {
return Markdown(desc).render();
}
match summarize_if_needed(desc, DESCRIPTION_MAX_LENGTH, DESCRIPTION_CLIP_LENGTH) {
MaybeSummarized::No(desc) => Markdown(desc).render(),
MaybeSummarized::Yes(summary) => {
html! {
div class="main__summary-container" {
(summary)
"..."
button type="button" class="main__button" x-on:click="description_expanded = !description_expanded" {
b x-text="description_expanded ? 'Hide full description' : 'Show full description'" {}
}
}
}
}
}
}
fn render_remaining(&self, filter_keys: &[&str], assets: &Path) -> Option<Markup> {
let custom_keys = &[HELP_KEY, EXTERNAL_HELP_KEY, WARNING_KEY];
let filtered_items = self
.iter()
.filter(|(k, _v)| {
!filter_keys.contains(&k.as_str()) && !custom_keys.contains(&k.as_str())
})
.collect::<Vec<_>>();
let help_item = self.get(HELP_KEY);
let external_help_item = self.get(EXTERNAL_HELP_KEY);
let warning_item = self.get(WARNING_KEY);
let any_additional_items = !filtered_items.is_empty();
let custom_key_present =
help_item.is_some() || external_help_item.is_some() || warning_item.is_some();
if !(any_additional_items || custom_key_present) {
return None;
}
let external_link_on_click = if let Some(MetadataValue::String(s)) = external_help_item {
Some(format!(
"window.open('{}', '_blank')",
s.text()
.expect("meta string should not be interpolated")
.text()
))
} else {
None
};
Some(html! {
@if let Some(help) = help_item {
div class="markdown-body" {
(render_value(help))
}
}
@if let Some(on_click) = external_link_on_click {
button type="button" class="main__button" x-on:click=(on_click) {
b { "Go to External Documentation" }
img src=(assets.join("link.svg").to_string_lossy()) alt="External Documentation Icon" class="size-5";
}
}
@if let Some(warning) = warning_item {
div class="metadata__warning" {
img src=(assets.join("information-circle.svg").to_string_lossy()) alt="Warning Icon" class="size-5";
p { (render_value(warning)) }
}
}
@if any_additional_items {
div class="main__grid-nested-container" {
@for (key, value) in filtered_items {
(render_key_value(key, value))
}
}
}
})
}
}
fn render_value(value: &MetadataValue) -> Markup {
match value {
MetadataValue::String(s) => {
let inner_text = s
.text()
.map(|t| t.text().to_string())
.expect("meta string should not be interpolated");
Markdown(inner_text).render()
}
MetadataValue::Boolean(b) => html! { code { (b.text()) } },
MetadataValue::Integer(i) => html! { code { (i.text()) } },
MetadataValue::Float(f) => html! { code { (f.text()) } },
MetadataValue::Null(n) => html! { code { (n.text()) } },
MetadataValue::Array(a) => {
html! {
div class="main__grid-meta-array-container" {
@for item in a.elements() {
@match item {
MetadataValue::Array(_) | MetadataValue::Object(_) => {
(render_value(&item))
}
_ => {
div class="main__grid-meta-array-item" {
code { (item.text()) }
}
}
}
}
}
}
}
MetadataValue::Object(o) => {
html! {
div class="main__grid-nested-container" {
@for item in o.items() {
(render_key_value(item.name().text(), &item.value()))
}
}
}
}
}
}
fn render_key_value(key: &str, value: &MetadataValue) -> Markup {
let (ty, rhs_markup) = match value {
MetadataValue::String(s) => (
s.inner().kind(),
html! { code { (s.text().expect("meta string should not be interpolated").text()) } },
),
MetadataValue::Boolean(b) => (b.inner().kind(), html! { code { (b.text()) } }),
MetadataValue::Integer(i) => (i.inner().kind(), html! { code { (i.text()) } }),
MetadataValue::Float(f) => (f.inner().kind(), html! { code { (f.text()) } }),
MetadataValue::Null(n) => (n.inner().kind(), html! { code { (n.text()) } }),
MetadataValue::Array(a) => {
let markup = html! {
div class="main__grid-meta-array-container" {
@for item in a.elements() {
@match item {
MetadataValue::Array(_) | MetadataValue::Object(_) => {
(render_value(&item))
}
_ => {
div class="main__grid-meta-array-item" {
code { (item.text()) }
}
}
}
}
}
};
(a.inner().kind(), markup)
}
MetadataValue::Object(o) => {
let markup = html! {
div class="main__grid-nested-container" {
@for item in o.items() {
(render_key_value(item.name().text(), &item.value()))
}
}
};
(o.inner().kind(), markup)
}
};
let lhs_markup = match ty {
SyntaxKind::MetadataArrayNode | SyntaxKind::MetadataObjectNode => {
html! { code { (key) } }
}
_ => {
html! { code { (key) } }
}
};
html! {
div class="main__grid-nested-row" {
div class="main__grid-nested-cell" {
(lhs_markup)
}
div class="main__grid-nested-cell" {
(rhs_markup)
}
}
}
}
#[derive(Debug)]
pub(crate) enum MaybeSummarized {
Yes(String),
No(String),
}
pub(crate) fn summarize_if_needed(
in_string: String,
max_length: usize,
clip_length: usize,
) -> MaybeSummarized {
if in_string.len() > max_length {
MaybeSummarized::Yes(in_string[..clip_length].trim_end().to_string())
} else {
MaybeSummarized::No(in_string)
}
}