wdl_doc/
parameter.rs

1//! Create HTML documentation for WDL parameters.
2
3use maud::Markup;
4use maud::html;
5use wdl_ast::AstNode;
6use wdl_ast::AstToken;
7use wdl_ast::v1::Decl;
8use wdl_ast::v1::MetadataValue;
9
10use crate::callable::Group;
11use crate::meta::render_value;
12
13/// Whether a parameter is an input or output.
14#[derive(Debug, Clone, Copy)]
15pub enum InputOutput {
16    /// An input parameter.
17    Input,
18    /// An output parameter.
19    Output,
20}
21
22/// A parameter (input or output) in a workflow or task.
23#[derive(Debug)]
24pub struct Parameter {
25    /// The declaration of the parameter.
26    decl: Decl,
27    /// Any meta entries associated with the parameter.
28    meta: Option<MetadataValue>,
29    /// Whether the parameter is an input or output.
30    io: InputOutput,
31}
32
33impl Parameter {
34    /// Create a new parameter.
35    pub fn new(decl: Decl, meta: Option<MetadataValue>, io: InputOutput) -> Self {
36        Self { decl, meta, io }
37    }
38
39    /// Get the name of the parameter.
40    pub fn name(&self) -> String {
41        self.decl.name().text().to_owned()
42    }
43
44    /// Get the type of the parameter.
45    pub fn ty(&self) -> String {
46        self.decl.ty().to_string()
47    }
48
49    /// Get whether the parameter is an input or output.
50    pub fn io(&self) -> InputOutput {
51        self.io
52    }
53
54    /// Get the Expr value of the parameter as a String.
55    pub fn expr(&self) -> String {
56        self.decl
57            .expr()
58            .map(|expr| expr.text().to_string())
59            .unwrap_or("None".to_string())
60    }
61
62    /// Get whether the input parameter is required.
63    ///
64    /// Returns `None` for outputs.
65    pub fn required(&self) -> Option<bool> {
66        match self.io {
67            InputOutput::Input => match self.decl.as_unbound_decl() {
68                Some(d) => Some(!d.ty().is_optional()),
69                _ => Some(false),
70            },
71            InputOutput::Output => None,
72        }
73    }
74
75    /// Get the "group" of the parameter.
76    pub fn group(&self) -> Option<Group> {
77        if let Some(MetadataValue::Object(o)) = &self.meta {
78            for item in o.items() {
79                if item.name().text() == "group" {
80                    if let MetadataValue::String(s) = item.value() {
81                        return s.text().map(|t| t.text().to_string()).map(Group);
82                    }
83                }
84            }
85        }
86        None
87    }
88
89    /// Get the description of the parameter.
90    pub fn description(&self) -> Markup {
91        if let Some(meta) = &self.meta {
92            if let MetadataValue::String(_) = meta {
93                return render_value(meta);
94            } else if let MetadataValue::Object(o) = meta {
95                for item in o.items() {
96                    if item.name().text() == "description" {
97                        if let MetadataValue::String(_) = item.value() {
98                            return render_value(&item.value());
99                        }
100                    }
101                }
102            }
103        }
104        html! {}
105    }
106
107    /// Render the remaining metadata as HTML.
108    ///
109    /// This will render any metadata that is not rendered elsewhere.
110    pub fn render_remaining_meta(&self) -> Markup {
111        if let Some(MetadataValue::Object(o)) = &self.meta {
112            let filtered_items = o.items().filter(|item| {
113                item.name().text() != "description" && item.name().text() != "group"
114            });
115            return html! {
116                ul {
117                    @for item in filtered_items {
118                        li {
119                            b { (item.name().text()) ":" } " " (render_value(&item.value()))
120                        }
121                    }
122                }
123            };
124        }
125        html! {}
126    }
127
128    /// Render the parameter as HTML.
129    pub fn render(&self) -> Markup {
130        if self.required() == Some(true) {
131            html! {
132                tr class="border" {
133                    td class="border" { (self.name()) }
134                    td class="border" { code { (self.ty()) } }
135                    td class="border" { (self.description()) }
136                    td class="border" { (self.render_remaining_meta()) }
137                }
138            }
139        } else {
140            html! {
141                tr class="border" {
142                    td class="border" { (self.name()) }
143                    td class="border" { code { (self.ty()) } }
144                    td class="border" { code { (self.expr()) } }
145                    td class="border" { (self.description()) }
146                    td class="border" { (self.render_remaining_meta()) }
147                }
148            }
149        }
150    }
151}