1pub mod task;
4pub mod workflow;
5
6use std::collections::BTreeMap;
7use std::collections::BTreeSet;
8
9use maud::Markup;
10use maud::html;
11use wdl_ast::AstToken;
12use wdl_ast::v1::InputSection;
13use wdl_ast::v1::MetadataSection;
14use wdl_ast::v1::MetadataValue;
15use wdl_ast::v1::OutputSection;
16use wdl_ast::v1::ParameterMetadataSection;
17
18use crate::meta::render_value;
19use crate::parameter::InputOutput;
20use crate::parameter::Parameter;
21
22pub type MetaMap = BTreeMap<String, MetadataValue>;
24
25#[derive(Debug, Eq, PartialEq)]
27pub struct Group(pub String);
28
29impl PartialOrd for Group {
30 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
31 Some(self.cmp(other))
32 }
33}
34
35impl Ord for Group {
36 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
37 if self.0 == "Common" {
38 return std::cmp::Ordering::Less;
39 }
40 if other.0 == "Common" {
41 return std::cmp::Ordering::Greater;
42 }
43 if self.0 == "Resources" {
44 return std::cmp::Ordering::Greater;
45 }
46 if other.0 == "Resources" {
47 return std::cmp::Ordering::Less;
48 }
49 self.0.cmp(&other.0)
50 }
51}
52
53pub trait Callable {
55 fn name(&self) -> &str;
57
58 fn meta(&self) -> &MetaMap;
60
61 fn inputs(&self) -> &[Parameter];
63
64 fn outputs(&self) -> &[Parameter];
66
67 fn description(&self) -> Markup {
69 self.meta()
70 .get("description")
71 .map(render_value)
72 .unwrap_or_else(|| html! {})
73 }
74
75 fn required_inputs(&self) -> impl Iterator<Item = &Parameter> {
77 self.inputs().iter().filter(|param| {
78 param
79 .required()
80 .expect("inputs should return Some(required)")
81 })
82 }
83
84 fn input_groups(&self) -> BTreeSet<Group> {
90 self.inputs()
91 .iter()
92 .filter_map(|param| param.group())
93 .map(|arg0: Group| Group(arg0.0.clone()))
94 .collect()
95 }
96
97 fn inputs_in_group<'a>(&'a self, group: &'a Group) -> impl Iterator<Item = &'a Parameter> {
99 self.inputs().iter().filter(move |param| {
100 if let Some(param_group) = param.group() {
101 if param_group == *group {
102 return true;
103 }
104 }
105 false
106 })
107 }
108
109 fn other_inputs(&self) -> impl Iterator<Item = &Parameter> {
112 self.inputs().iter().filter(|param| {
113 !param
114 .required()
115 .expect("inputs should return Some(required)")
116 && param.group().is_none()
117 })
118 }
119
120 fn render_required_inputs(&self) -> Markup {
122 let mut iter = self.required_inputs().peekable();
123 if iter.peek().is_some() {
124 return html! {
125 h3 { "Required Inputs" }
126 table class="border" {
127 thead class="border" { tr {
128 th { "Name" }
129 th { "Type" }
130 th { "Description" }
131 th { "Additional Meta" }
132 }}
133 tbody class="border" {
134 @for param in iter {
135 (param.render())
136 }
137 }
138 }
139 };
140 };
141 html! {}
142 }
143
144 fn render_group_inputs(&self) -> Markup {
146 let group_tables = self.input_groups().into_iter().map(|group| {
147 html! {
148 h3 { (group.0) }
149 table class="border" {
150 thead class="border" { tr {
151 th { "Name" }
152 th { "Type" }
153 th { "Default" }
154 th { "Description" }
155 th { "Additional Meta" }
156 }}
157 tbody class="border" {
158 @for param in self.inputs_in_group(&group) {
159 (param.render())
160 }
161 }
162 }
163 }
164 });
165 html! {
166 @for group_table in group_tables {
167 (group_table)
168 }
169 }
170 }
171
172 fn render_other_inputs(&self) -> Markup {
174 let mut iter = self.other_inputs().peekable();
175 if iter.peek().is_some() {
176 return html! {
177 h3 { "Other Inputs" }
178 table class="border" {
179 thead class="border" { tr {
180 th { "Name" }
181 th { "Type" }
182 th { "Default" }
183 th { "Description" }
184 th { "Additional Meta" }
185 }}
186 tbody class="border" {
187 @for param in iter {
188 (param.render())
189 }
190 }
191 }
192 };
193 };
194 html! {}
195 }
196
197 fn render_inputs(&self) -> Markup {
199 html! {
200 h2 { "Inputs" }
201 (self.render_required_inputs())
202 (self.render_group_inputs())
203 (self.render_other_inputs())
204 }
205 }
206
207 fn render_outputs(&self) -> Markup {
209 html! {
210 h2 { "Outputs" }
211 table {
212 thead class="border" { tr {
213 th { "Name" }
214 th { "Type" }
215 th { "Expression" }
216 th { "Description" }
217 th { "Additional Meta" }
218 }}
219 tbody class="border" {
220 @for param in self.outputs() {
221 (param.render())
222 }
223 }
224 }
225 }
226 }
227}
228
229fn parse_meta(meta: &MetadataSection) -> MetaMap {
231 meta.items()
232 .map(|m| {
233 let name = m.name().text().to_owned();
234 let item = m.value();
235 (name, item)
236 })
237 .collect()
238}
239
240fn parse_parameter_meta(parameter_meta: &ParameterMetadataSection) -> MetaMap {
242 parameter_meta
243 .items()
244 .map(|m| {
245 let name = m.name().text().to_owned();
246 let item = m.value();
247 (name, item)
248 })
249 .collect()
250}
251
252fn parse_inputs(input_section: &InputSection, parameter_meta: &MetaMap) -> Vec<Parameter> {
254 input_section
255 .declarations()
256 .map(|decl| {
257 let name = decl.name().text().to_owned();
258 let meta = parameter_meta.get(&name);
259 Parameter::new(decl.clone(), meta.cloned(), InputOutput::Input)
260 })
261 .collect()
262}
263
264fn parse_outputs(
266 output_section: &OutputSection,
267 meta: &MetaMap,
268 parameter_meta: &MetaMap,
269) -> Vec<Parameter> {
270 let output_meta: MetaMap = meta
271 .get("outputs")
272 .and_then(|v| match v {
273 MetadataValue::Object(o) => Some(o),
274 _ => None,
275 })
276 .map(|o| {
277 o.items()
278 .map(|i| (i.name().text().to_owned(), i.value().clone()))
279 .collect()
280 })
281 .unwrap_or_default();
282
283 output_section
284 .declarations()
285 .map(|decl| {
286 let name = decl.name().text().to_owned();
287 let meta = parameter_meta.get(&name).or_else(|| output_meta.get(&name));
288 Parameter::new(
289 wdl_ast::v1::Decl::Bound(decl.clone()),
290 meta.cloned(),
291 InputOutput::Output,
292 )
293 })
294 .collect()
295}
296
297#[cfg(test)]
298mod tests {
299 use wdl_ast::Document;
300
301 use super::*;
302
303 #[test]
304 fn test_group_cmp() {
305 let common = Group("Common".to_string());
306 let resources = Group("Resources".to_string());
307 let a = Group("A".to_string());
308 let b = Group("B".to_string());
309 let c = Group("C".to_string());
310
311 let mut groups = vec![c, a, resources, common, b];
312 groups.sort();
313 assert_eq!(
314 groups,
315 vec![
316 Group("Common".to_string()),
317 Group("A".to_string()),
318 Group("B".to_string()),
319 Group("C".to_string()),
320 Group("Resources".to_string()),
321 ]
322 );
323 }
324
325 #[test]
326 fn test_parse_meta() {
327 let wdl = r#"
328 version 1.1
329
330 workflow wf {
331 meta {
332 name: "Workflow"
333 description: "A workflow"
334 }
335 }
336 "#;
337
338 let (doc, _) = Document::parse(wdl);
339 let doc_item = doc.ast().into_v1().unwrap().items().next().unwrap();
340 let meta_map = parse_meta(
341 &doc_item
342 .as_workflow_definition()
343 .unwrap()
344 .metadata()
345 .unwrap(),
346 );
347 assert_eq!(meta_map.len(), 2);
348 assert_eq!(
349 meta_map
350 .get("name")
351 .unwrap()
352 .clone()
353 .unwrap_string()
354 .text()
355 .unwrap()
356 .text(),
357 "Workflow"
358 );
359 assert_eq!(
360 meta_map
361 .get("description")
362 .unwrap()
363 .clone()
364 .unwrap_string()
365 .text()
366 .unwrap()
367 .text(),
368 "A workflow"
369 );
370 }
371
372 #[test]
373 fn test_parse_parameter_meta() {
374 let wdl = r#"
375 version 1.1
376
377 workflow wf {
378 input {
379 Int a
380 }
381 parameter_meta {
382 a: {
383 description: "An integer"
384 }
385 }
386 }
387 "#;
388
389 let (doc, _) = Document::parse(wdl);
390 let doc_item = doc.ast().into_v1().unwrap().items().next().unwrap();
391 let meta_map = parse_parameter_meta(
392 &doc_item
393 .as_workflow_definition()
394 .unwrap()
395 .parameter_metadata()
396 .unwrap(),
397 );
398 assert_eq!(meta_map.len(), 1);
399 assert_eq!(
400 meta_map
401 .get("a")
402 .unwrap()
403 .clone()
404 .unwrap_object()
405 .items()
406 .next()
407 .unwrap()
408 .value()
409 .clone()
410 .unwrap_string()
411 .text()
412 .unwrap()
413 .text(),
414 "An integer"
415 );
416 }
417
418 #[test]
419 fn test_parse_inputs() {
420 let wdl = r#"
421 version 1.1
422
423 workflow wf {
424 input {
425 Int a
426 Int b
427 Int c
428 }
429 parameter_meta {
430 a: "An integer"
431 c: {
432 description: "Another integer"
433 }
434 }
435 }
436 "#;
437
438 let (doc, _) = Document::parse(wdl);
439 let doc_item = doc.ast().into_v1().unwrap().items().next().unwrap();
440 let meta_map = parse_parameter_meta(
441 &doc_item
442 .as_workflow_definition()
443 .unwrap()
444 .parameter_metadata()
445 .unwrap(),
446 );
447 let inputs = parse_inputs(
448 &doc_item.as_workflow_definition().unwrap().input().unwrap(),
449 &meta_map,
450 );
451 assert_eq!(inputs.len(), 3);
452 assert_eq!(inputs[0].name(), "a");
453 assert_eq!(inputs[0].description().into_string(), "An integer");
454 assert_eq!(inputs[1].name(), "b");
455 assert_eq!(inputs[1].description().into_string(), "");
456 assert_eq!(inputs[2].name(), "c");
457 assert_eq!(inputs[2].description().into_string(), "Another integer");
458 }
459
460 #[test]
461 fn test_parse_outputs() {
462 let wdl = r#"
463 version 1.1
464
465 workflow wf {
466 output {
467 Int a = 1
468 Int b = 2
469 Int c = 3
470 }
471 meta {
472 outputs: {
473 a: "An integer"
474 c: {
475 description: "Another integer"
476 }
477 }
478 }
479 parameter_meta {
480 b: "A different place!"
481 }
482 }
483 "#;
484
485 let (doc, _) = Document::parse(wdl);
486 let doc_item = doc.ast().into_v1().unwrap().items().next().unwrap();
487 let meta_map = parse_meta(
488 &doc_item
489 .as_workflow_definition()
490 .unwrap()
491 .metadata()
492 .unwrap(),
493 );
494 let parameter_meta = parse_parameter_meta(
495 &doc_item
496 .as_workflow_definition()
497 .unwrap()
498 .parameter_metadata()
499 .unwrap(),
500 );
501 let outputs = parse_outputs(
502 &doc_item.as_workflow_definition().unwrap().output().unwrap(),
503 &meta_map,
504 ¶meter_meta,
505 );
506 assert_eq!(outputs.len(), 3);
507 assert_eq!(outputs[0].name(), "a");
508 assert_eq!(outputs[0].description().into_string(), "An integer");
509 assert_eq!(outputs[1].name(), "b");
510 assert_eq!(outputs[1].description().into_string(), "A different place!");
511 assert_eq!(outputs[2].name(), "c");
512 assert_eq!(outputs[2].description().into_string(), "Another integer");
513 }
514}