wdl_doc/callable/
workflow.rs

1//! Create HTML documentation for WDL workflows.
2
3use maud::Markup;
4use wdl_ast::AstToken;
5use wdl_ast::v1::InputSection;
6use wdl_ast::v1::MetadataSection;
7use wdl_ast::v1::MetadataValue;
8use wdl_ast::v1::OutputSection;
9use wdl_ast::v1::ParameterMetadataSection;
10
11use super::*;
12use crate::meta::render_value;
13use crate::parameter::Parameter;
14
15/// A workflow in a WDL document.
16#[derive(Debug)]
17pub struct Workflow {
18    /// The name of the workflow.
19    name: String,
20    /// The meta of the workflow.
21    meta: MetaMap,
22    /// The inputs of the workflow.
23    inputs: Vec<Parameter>,
24    /// The outputs of the workflow.
25    outputs: Vec<Parameter>,
26}
27
28impl Workflow {
29    /// Create a new workflow.
30    pub fn new(
31        name: String,
32        meta_section: Option<MetadataSection>,
33        parameter_meta: Option<ParameterMetadataSection>,
34        input_section: Option<InputSection>,
35        output_section: Option<OutputSection>,
36    ) -> Self {
37        let meta = match meta_section {
38            Some(mds) => parse_meta(&mds),
39            _ => MetaMap::default(),
40        };
41        let parameter_meta = match parameter_meta {
42            Some(pmds) => parse_parameter_meta(&pmds),
43            _ => MetaMap::default(),
44        };
45        let inputs = match input_section {
46            Some(is) => parse_inputs(&is, &parameter_meta),
47            _ => Vec::new(),
48        };
49        let outputs = match output_section {
50            Some(os) => parse_outputs(&os, &meta, &parameter_meta),
51            _ => Vec::new(),
52        };
53
54        Self {
55            name,
56            meta,
57            inputs,
58            outputs,
59        }
60    }
61
62    /// Returns the `name` entry from the meta section, if it exists.
63    pub fn name_override(&self) -> Option<Markup> {
64        self.meta.get("name").map(render_value)
65    }
66
67    /// Returns the `category` entry from the meta section, if it exists.
68    pub fn category(&self) -> Option<String> {
69        self.meta.get("category").and_then(|v| match v {
70            MetadataValue::String(s) => Some(s.text().unwrap().text().to_string()),
71            _ => None,
72        })
73    }
74
75    /// Renders the meta section of the workflow as HTML.
76    ///
77    /// This will render all metadata key-value pairs except for `name`,
78    /// `category`, `description`, and `outputs`.
79    pub fn render_meta(&self) -> Markup {
80        let mut kv = self
81            .meta
82            .iter()
83            .filter(|(k, _)| !matches!(k.as_str(), "name" | "category" | "description" | "outputs"))
84            .peekable();
85        html! {
86            @if kv.peek().is_some() {
87                div {
88                    h2 { "Meta" }
89                    @for (key, value) in kv {
90                        p {
91                            b { (key) ":" } " " (render_value(value))
92                        }
93                    }
94                }
95            }
96        }
97    }
98
99    /// Render the workflow as HTML.
100    pub fn render(&self) -> Markup {
101        html! {
102            div class="table-auto border-collapse" {
103                h1 { @if let Some(name) = self.name_override() { (name) } @else { (self.name) } }
104                @if let Some(category) = self.category() {
105                    h2 { "Category: " (category) }
106                }
107                (self.description())
108                (self.render_meta())
109                (self.render_inputs())
110                (self.render_outputs())
111            }
112        }
113    }
114}
115
116impl Callable for Workflow {
117    fn name(&self) -> &str {
118        &self.name
119    }
120
121    fn meta(&self) -> &MetaMap {
122        &self.meta
123    }
124
125    fn inputs(&self) -> &[Parameter] {
126        &self.inputs
127    }
128
129    fn outputs(&self) -> &[Parameter] {
130        &self.outputs
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use wdl_ast::Document;
137
138    use super::*;
139
140    #[test]
141    fn test_workflow() {
142        let (doc, _) = Document::parse(
143            r#"
144            version 1.0
145            workflow test {
146                input {
147                    String name
148                }
149                output {
150                    String greeting = "Hello, ${name}!"
151                }
152            }
153            "#,
154        );
155
156        let doc_item = doc.ast().into_v1().unwrap().items().next().unwrap();
157        let ast_workflow = doc_item.into_workflow_definition().unwrap();
158
159        let workflow = Workflow::new(
160            ast_workflow.name().text().to_string(),
161            ast_workflow.metadata(),
162            ast_workflow.parameter_metadata(),
163            ast_workflow.input(),
164            ast_workflow.output(),
165        );
166
167        assert_eq!(workflow.name(), "test");
168        assert_eq!(workflow.inputs.len(), 1);
169        assert_eq!(workflow.outputs.len(), 1);
170    }
171}