use std::path::Path;
use maud::Markup;
use maud::PreEscaped;
use maud::html;
use wdl_ast::AstNode;
use wdl_ast::AstToken;
use wdl_ast::v1::Decl;
use wdl_ast::v1::MetadataValue;
use crate::meta::DESCRIPTION_KEY;
use crate::meta::MaybeSummarized;
use crate::meta::MetaMap;
use crate::meta::MetaMapExt;
use crate::meta::MetaMapValueSource;
use crate::meta::summarize_if_needed;
const EXPR_MAX_LENGTH: usize = 80;
const EXPR_CLIP_LENGTH: usize = 50;
#[derive(Debug, Eq, PartialEq)]
pub(crate) struct Group(pub String);
impl Group {
pub fn display_name(&self) -> &str {
&self.0
}
pub fn id(&self) -> String {
self.0.replace(" ", "-").to_lowercase()
}
}
impl PartialOrd for Group {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Group {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if self.0 == other.0 {
return std::cmp::Ordering::Equal;
}
if self.0 == "Common" {
return std::cmp::Ordering::Less;
}
if other.0 == "Common" {
return std::cmp::Ordering::Greater;
}
if self.0 == "Resources" {
return std::cmp::Ordering::Greater;
}
if other.0 == "Resources" {
return std::cmp::Ordering::Less;
}
self.0.cmp(&other.0)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum InputOutput {
Input,
Output,
}
#[derive(Debug)]
pub(crate) struct Parameter {
decl: Decl,
meta: MetaMap,
io: InputOutput,
}
impl Parameter {
pub fn new(decl: Decl, meta: Option<MetaMapValueSource>, io: InputOutput) -> Self {
let meta = match &meta {
Some(m) => {
match m {
MetaMapValueSource::Comment(_) => {
MetaMap::from([(DESCRIPTION_KEY.to_string(), m.clone())])
}
MetaMapValueSource::MetaValue(meta) => match meta {
MetadataValue::Object(o) => o
.items()
.map(|item| {
(
item.name().text().to_string(),
MetaMapValueSource::MetaValue(item.value().clone()),
)
})
.collect(),
MetadataValue::String(_s) => {
MetaMap::from([(DESCRIPTION_KEY.to_string(), m.clone())])
}
_ => {
MetaMap::default()
}
},
}
}
None => MetaMap::default(),
};
Self { decl, meta, io }
}
pub fn name(&self) -> String {
self.decl.name().text().to_owned()
}
pub fn meta(&self) -> &MetaMap {
&self.meta
}
pub fn ty(&self) -> String {
self.decl.ty().to_string()
}
pub fn render_expr(&self, summarize: bool) -> Markup {
let expr = self
.decl
.expr()
.map(|expr| expr.text().to_string())
.unwrap_or("None".to_string());
if !summarize {
let mut lines = expr.lines();
let first_line = lines.next().expect("expr should have at least one line");
let common_indent = lines
.clone()
.map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
.min()
.unwrap_or(0);
let remaining_expr = lines
.map(|line| line.chars().skip(common_indent).collect::<String>())
.collect::<Vec<_>>()
.join("\n");
let full_expr = if remaining_expr.is_empty() {
first_line
} else {
&format!("{first_line}\n{remaining_expr}")
};
return html! {
sprocket-code language="wdl" {
(full_expr)
}
};
}
match summarize_if_needed(expr, EXPR_MAX_LENGTH, EXPR_CLIP_LENGTH) {
MaybeSummarized::No(expr) => {
html! { code { (expr) } }
}
MaybeSummarized::Yes(summary) => {
html! {
div class="main__summary-container" {
code { (summary) }
"..."
button type="button" class="main__button" x-on:click="expr_expanded = !expr_expanded" {
b x-text="expr_expanded ? 'Hide full expression' : 'Show full expression'" {}
}
}
}
}
}
}
pub fn required(&self) -> Option<bool> {
match self.io {
InputOutput::Input => match self.decl.as_unbound_decl() {
Some(d) => Some(!d.ty().is_optional()),
_ => Some(false),
},
InputOutput::Output => None,
}
}
pub fn group(&self) -> Option<Group> {
self.meta()
.get("group")
.and_then(MetaMapValueSource::text)
.map(Group)
}
pub fn description(&self, summarize: bool) -> Markup {
self.meta().render_description(summarize)
}
pub fn render_remaining_meta(&self, assets: &Path) -> Option<Markup> {
self.meta()
.render_remaining(&[DESCRIPTION_KEY, "group"], assets)
}
pub fn render(&self, assets: &Path) -> Markup {
let show_expr = self.required() != Some(true);
html! {
div class="main__grid-row" x-data=(
if show_expr { "{ description_expanded: false, expr_expanded: false }" } else { "{ description_expanded: false }" }
) {
div class="main__grid-cell" {
code { (self.name()) }
}
div class="main__grid-cell" {
code { (self.ty()) }
}
@if show_expr {
div class="main__grid-cell" { (self.render_expr(true)) }
}
div class="main__grid-cell" {
(self.description(true))
}
div x-show="description_expanded" class="main__grid-full-width-cell" {
(self.description(false))
}
@if show_expr {
div x-show="expr_expanded" class="main__grid-full-width-cell" {
(self.render_expr(false))
}
}
}
@if let Some(addl_meta) = self.render_remaining_meta(assets) {
div class="main__grid-full-width-cell" x-data="{ addl_meta_expanded: false }" {
div class="main__addl-meta-outer-container" {
button type="button" class="main__button" x-on:click="addl_meta_expanded = !addl_meta_expanded" {
b x-text="addl_meta_expanded ? 'Hide Additional Meta' : 'Show Additional Metadata'" {}
}
div x-show="addl_meta_expanded" class="main__addl-meta-inner-container" {
(addl_meta)
}
}
}
}
}
}
}
pub(crate) fn render_non_required_parameters_table<'a, I>(params: I, assets: &Path) -> Markup
where
I: Iterator<Item = &'a Parameter>,
{
let params = params.collect::<Vec<_>>();
let third_col = if params.iter().any(|p| p.required().is_none()) {
"Expression"
} else {
"Default"
};
let rows = params
.iter()
.map(|param| param.render(assets).into_string())
.collect::<Vec<_>>()
.join(&html! { div class="main__grid-row-separator" {} }.into_string());
html! {
div class="main__grid-container" {
div class="main__grid-non-req-param-container" {
div class="main__grid-header-cell" { "Name" }
div class="main__grid-header-cell" { "Type" }
div class="main__grid-header-cell" { (third_col) }
div class="main__grid-header-cell" { "Description" }
div class="main__grid-header-separator" {}
(PreEscaped(rows))
}
}
}
}