use std::path::PathBuf;
use maud::Markup;
use maud::html;
use wdl_ast::AstNode;
use wdl_ast::AstToken;
use wdl_ast::SupportedVersion;
use wdl_ast::v1::CommandSection;
use wdl_ast::v1::RuntimeSection;
use wdl_ast::v1::TaskDefinition;
use super::*;
use crate::command_section::CommandSectionExt;
use crate::docs_tree::Header;
use crate::docs_tree::PageSections;
use crate::meta::DESCRIPTION_KEY;
use crate::meta::main_container;
use crate::meta::parse_metadata_items;
use crate::parameter::Parameter;
#[derive(Debug)]
pub struct Task {
name: String,
version: VersionBadge,
meta: MetaMap,
inputs: Vec<Parameter>,
outputs: Vec<Parameter>,
runtime_section: Option<RuntimeSection>,
command_section: Option<CommandSection>,
wdl_path: Option<PathBuf>,
}
impl DefinitionMeta for Task {
fn meta(&self) -> &MetaMap {
&self.meta
}
}
impl Task {
pub fn new(
name: String,
version: SupportedVersion,
definition: TaskDefinition,
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,
runtime_section: definition.runtime(),
command_section: definition.command(),
wdl_path,
}
}
pub fn render_meta(&self, assets: &Path) -> Option<Markup> {
self.meta()
.render_remaining(&[DESCRIPTION_KEY, "outputs"], assets)
}
pub fn render_runtime_section(&self) -> Markup {
match &self.runtime_section {
Some(runtime_section) => {
let rows = runtime_section
.items()
.map(|entry| {
{
html! {
div class="main__grid-row" {
div class="main__grid-cell" {
code { (entry.name().text()) }
}
div class="main__grid-cell" {
code { ({let e = entry.expr(); e.text().to_string()}) }
}
}
}
}
.into_string()
})
.collect::<Vec<_>>()
.join(&html! { div class="main__grid-row-separator" {} }.into_string());
html! {
div class="main__section" {
h2 id="runtime" class="main__section-header" { "Default Runtime Attributes" }
div class="main__grid-container" {
div class="main__grid-runtime-container" {
div class="main__grid-header-cell" { "Attribute" }
div class="main__grid-header-cell" { "Value" }
div class="main__grid-header-separator" {}
(PreEscaped(rows))
}
}
}
}
}
_ => {
html! {}
}
}
}
pub fn render_command_section(&self) -> Markup {
match &self.command_section {
Some(command_section) => {
html! {
div class="main__section" {
h2 id="command" class="main__section-header" { "Command" }
sprocket-code language="wdl" class="pt-8" {
(command_section.script())
}
}
}
}
_ => {
html! {}
}
}
}
pub fn render(&self, assets: &Path) -> (Markup, PageSections) {
let mut headers = PageSections::default();
let (input_markup, inner_headers) = self.render_inputs(assets);
headers.extend(inner_headers);
let markup = html! {
span class="text-brand-violet-400" data-pagefind-filter="type:task" { "Task" }
h1 id="title" class="main__title" data-pagefind-meta="title" { code { (self.name()) } }
div class="markdown-body mb-4" {
(self.render_description(false))
}
div class="main__badge-container" {
(self.render_version())
}
(self.render_run_with(assets))
@if let Some(meta) = self.render_meta(assets) {
div class="main__section" {
(meta)
}
}
(input_markup)
(self.render_outputs(assets))
(self.render_runtime_section())
(self.render_command_section())
};
headers.push(Header::Header("Outputs".to_string(), "outputs".to_string()));
headers.push(Header::Header("Runtime".to_string(), "runtime".to_string()));
headers.push(Header::Header("Command".to_string(), "command".to_string()));
(
main_container("task", self.wdl_path.is_none(), markup),
headers,
)
}
}
impl Runnable for Task {
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_task() {
let (doc, _) = Document::parse(
r#"
version 1.0
## This comment should be ignored.
task my_task {
input {
String name
}
output {
String greeting = "Hello, ${name}!"
}
runtime {
docker: "ubuntu:latest"
}
meta {
description: "A simple task"
}
}
"#,
None,
);
let doc_item = doc.ast().into_v1().unwrap().items().next().unwrap();
let ast_task = doc_item.into_task_definition().unwrap();
let task = Task::new(
ast_task.name().text().to_owned(),
SupportedVersion::V1(V1::Zero),
ast_task,
None,
false,
);
assert_eq!(task.name(), "my_task");
assert_eq!(
task.meta()
.get("description")
.unwrap()
.clone()
.into_meta()
.unwrap()
.unwrap_string()
.text()
.unwrap()
.text(),
"A simple task"
);
assert_eq!(task.inputs().len(), 1);
assert_eq!(task.outputs().len(), 1);
}
#[test]
fn task_with_doc_comments() {
let (doc, _) = Document::parse(
r#"
version 1.0
## This is my task. It greets people.
task my_task {
input {
## The name to greet.
String name
}
output {
## The generated greeting.
String greeting = "Hello, ${name}!"
}
runtime {
docker: "ubuntu:latest"
}
meta {
description: "This description should be overwritten."
}
}
"#,
None,
);
let doc_item = doc.ast().into_v1().unwrap().items().next().unwrap();
let ast_task = doc_item.into_task_definition().unwrap();
let task = Task::new(
ast_task.name().text().to_owned(),
SupportedVersion::V1(V1::Zero),
ast_task,
None,
true,
);
assert_eq!(task.name(), "my_task");
assert_eq!(
task.meta()
.get("description")
.unwrap()
.clone()
.text()
.unwrap(),
"This is my task. It greets people."
);
assert_eq!(task.inputs().len(), 1);
let input = &task.inputs()[0];
assert_eq!(
input
.meta()
.get("description")
.unwrap()
.clone()
.text()
.unwrap(),
"The name to greet."
);
assert_eq!(task.outputs().len(), 1);
let output = &task.outputs()[0];
assert_eq!(
output
.meta()
.get("description")
.unwrap()
.clone()
.text()
.unwrap(),
"The generated greeting."
);
}
}