use std::path::PathBuf;
use maud::Markup;
use wdl_ast::SupportedVersion;
use wdl_ast::v1::MetadataValue;
use wdl_ast::v1::WorkflowDefinition;
use super::*;
use crate::docs_tree::Header;
use crate::docs_tree::PageSections;
use crate::meta::DESCRIPTION_KEY;
use crate::meta::MetaMapValueSource;
use crate::meta::doc_comments;
use crate::meta::main_container;
use crate::meta::parse_metadata_items;
use crate::parameter::Parameter;
const NAME_KEY: &str = "name";
const CATEGORY_KEY: &str = "category";
#[derive(Debug)]
pub(crate) struct Workflow {
name: String,
version: VersionBadge,
meta: MetaMap,
inputs: Vec<Parameter>,
outputs: Vec<Parameter>,
wdl_path: Option<PathBuf>,
}
impl DefinitionMeta for Workflow {
fn meta(&self) -> &MetaMap {
&self.meta
}
}
impl Workflow {
pub fn new(
name: String,
version: SupportedVersion,
definition: WorkflowDefinition,
wdl_path: Option<PathBuf>,
enable_doc_comments: bool,
) -> Self {
let mut meta = match definition.metadata() {
Some(mds) => parse_metadata_items(mds.items()),
_ => MetaMap::default(),
};
if enable_doc_comments && let Some(comments) = definition.doc_comments() {
meta.append(&mut doc_comments(comments));
}
let parameter_meta = match definition.parameter_metadata() {
Some(pmds) => parse_metadata_items(pmds.items()),
_ => MetaMap::default(),
};
let inputs = match definition.input() {
Some(is) => parse_inputs(&is, ¶meter_meta, enable_doc_comments),
_ => Vec::new(),
};
let outputs = match definition.output() {
Some(os) => parse_outputs(&os, &meta, ¶meter_meta, enable_doc_comments),
_ => Vec::new(),
};
Self {
name,
version: VersionBadge::new(version),
meta,
inputs,
outputs,
wdl_path,
}
}
pub fn name_override(&self) -> Option<String> {
self.meta.get(NAME_KEY).and_then(MetaMapValueSource::text)
}
pub fn category(&self) -> Option<String> {
self.meta
.get(CATEGORY_KEY)
.and_then(MetaMapValueSource::text)
}
pub fn render_name(&self) -> Markup {
if let Some(name) = self.name_override() {
html! { (name) }
} else {
html! { code { (self.name) } }
}
}
pub fn render_meta(&self, assets: &Path) -> Option<Markup> {
self.meta().render_remaining(
&[
DESCRIPTION_KEY,
NAME_KEY,
CATEGORY_KEY,
"allowNestedInputs",
"allow_nested_inputs",
"outputs",
],
assets,
)
}
pub fn render_allow_nested_inputs(&self) -> Markup {
if let Some(MetaMapValueSource::MetaValue(MetadataValue::Boolean(b))) = self
.meta
.get("allowNestedInputs")
.or(self.meta.get("allow_nested_inputs"))
&& b.value()
{
return html! {
div class="main__badge main__badge--success" {
span class="main__badge-text" {
"Nested Inputs Allowed"
}
}
};
}
html! {
div class="main__badge main__badge--disabled" {
span class="main__badge-text" {
"Nested Inputs Not Allowed"
}
}
}
}
pub fn render_category(&self) -> Option<Markup> {
self.category().map(|category| {
html! {
div class="main__badge" {
span class="main__badge-text" {
"Category"
}
div class="main__badge-inner" {
span class="main__badge-inner-text" {
(category)
}
}
}
}
})
}
pub fn render(&self, assets: &Path) -> (Markup, PageSections) {
let mut headers = PageSections::default();
let meta_markup = if let Some(meta) = self.render_meta(assets) {
html! { (meta) }
} else {
html! {}
};
let (input_markup, inner_headers) = self.render_inputs(assets);
headers.extend(inner_headers);
let markup = html! {
span class="text-brand-emerald-400" data-pagefind-filter="type:workflow" { "Workflow" }
h1 id="title" class="main__title" data-pagefind-meta="title" { (self.render_name()) }
div class="markdown-body mb-4" {
(self.render_description(false))
}
div class="main__badge-container" {
(self.render_version())
@if let Some(badge) = self.render_category() {
(badge)
}
(self.render_allow_nested_inputs())
}
(self.render_run_with(assets))
div class="main__section" {
(meta_markup)
}
(input_markup)
(self.render_outputs(assets))
};
headers.push(Header::Header("Outputs".to_string(), "outputs".to_string()));
(
main_container("workflow", self.wdl_path.is_none(), markup),
headers,
)
}
}
impl Runnable for Workflow {
fn name(&self) -> &str {
&self.name
}
fn version(&self) -> &VersionBadge {
&self.version
}
fn inputs(&self) -> &[Parameter] {
&self.inputs
}
fn outputs(&self) -> &[Parameter] {
&self.outputs
}
fn wdl_path(&self) -> Option<&Path> {
self.wdl_path.as_deref()
}
}
#[cfg(test)]
mod tests {
use wdl_ast::Document;
use wdl_ast::version::V1;
use super::*;
#[test]
fn test_workflow() {
let (doc, _) = Document::parse(
r#"
version 1.0
## This comment should be ignored.
workflow test {
input {
String name
}
output {
String greeting = "Hello, ${name}!"
}
}
"#,
None,
);
let doc_item = doc.ast().into_v1().unwrap().items().next().unwrap();
let ast_workflow = doc_item.into_workflow_definition().unwrap();
let workflow = Workflow::new(
ast_workflow.name().text().to_string(),
SupportedVersion::V1(V1::Zero),
ast_workflow,
None,
false,
);
assert_eq!(workflow.name(), "test");
assert!(workflow.meta().get("description").is_none());
assert_eq!(workflow.inputs.len(), 1);
assert_eq!(workflow.outputs.len(), 1);
}
#[test]
fn workflow_with_doc_comments() {
let (doc, _) = Document::parse(
r#"
version 1.0
## This is my workflow. It greets people.
workflow test {
input {
## The name to greet.
String name
}
output {
## The generated greeting.
String greeting = "Hello, ${name}!"
}
}
"#,
None,
);
let doc_item = doc.ast().into_v1().unwrap().items().next().unwrap();
let ast_workflow = doc_item.into_workflow_definition().unwrap();
let workflow = Workflow::new(
ast_workflow.name().text().to_string(),
SupportedVersion::V1(V1::Zero),
ast_workflow,
None,
true,
);
assert_eq!(workflow.name(), "test");
assert_eq!(
workflow
.meta()
.get("description")
.unwrap()
.clone()
.text()
.unwrap(),
"This is my workflow. It greets people."
);
assert_eq!(workflow.inputs().len(), 1);
let input = &workflow.inputs()[0];
assert_eq!(
input
.meta()
.get("description")
.unwrap()
.clone()
.text()
.unwrap(),
"The name to greet."
);
assert_eq!(workflow.outputs.len(), 1);
let output = &workflow.outputs()[0];
assert_eq!(
output
.meta()
.get("description")
.unwrap()
.clone()
.text()
.unwrap(),
"The generated greeting."
);
}
}