Skip to content

Commit 796e37c

Browse files
committed
secondaryFiles in outputs
1 parent c1a48d5 commit 796e37c

6 files changed

Lines changed: 100 additions & 54 deletions

File tree

crates/cwl-execution/src/expression.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,9 @@ pub(crate) fn process_expressions(tool: &mut CWLDocument, input_values: &mut Inp
217217
if input.secondary_files.is_empty() {
218218
continue;
219219
}
220-
221-
if let DefaultValue::File(file) = &input_values.inputs[&tmp] {
222-
set_self(&file.snapshot())?;
220+
221+
if let Some(DefaultValue::File(file)) = input_values.inputs.get(&tmp) {
222+
set_self(&file.preload())?;
223223
for sec_file in &mut input.secondary_files {
224224
sec_file.pattern = replace_expressions(&sec_file.pattern)?;
225225
}

crates/cwl-execution/src/util.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
expression::{output_eval, set_self, unset_self},
2+
expression::{output_eval, replace_expressions, set_self, unset_self},
33
io::{copy_dir, copy_file, get_first_file_with_prefix},
44
};
55
use cwl::{
@@ -10,6 +10,7 @@ use cwl::{
1010
types::{CWLType, DefaultValue, Directory, File},
1111
};
1212
use glob::glob;
13+
use log::info;
1314
use serde_yaml::{Mapping, Value};
1415
use std::{
1516
collections::HashMap,
@@ -233,10 +234,31 @@ fn evaluate_output_impl(
233234

234235
fn handle_file_output(entry: &PathBuf, initial_dir: &Path, output: &CommandOutputParameter) -> Result<DefaultValue, Box<dyn Error>> {
235236
let current_dir = env::temp_dir().to_string_lossy().into_owned();
236-
let path = &initial_dir.join(entry.strip_prefix(current_dir).unwrap_or(entry));
237+
let path = &initial_dir.join(entry.strip_prefix(&current_dir).unwrap_or(entry));
237238
fs::copy(entry, path).map_err(|e| format!("Failed to copy file from {entry:?} to {path:?}: {e}"))?;
238-
eprintln!("📜 Wrote output file: {path:?}");
239-
Ok(DefaultValue::File(get_file_metadata(path, output.format.clone())))
239+
info!("📜 Wrote output file: {path:?}");
240+
241+
let mut file = get_file_metadata(path, output.format.clone());
242+
if !output.secondary_files.is_empty() {
243+
set_self(&file)?;
244+
let folder = entry.parent().unwrap_or(Path::new("."));
245+
let mut secondary_files = vec![];
246+
for secondary in &output.secondary_files {
247+
let pattern = replace_expressions(&secondary.pattern)?;
248+
let pattern = format!("{}*{}", folder.to_string_lossy(), pattern);
249+
for entry in glob(&pattern)? {
250+
let entry = entry?;
251+
let sec_path = initial_dir.join(&entry);
252+
fs::copy(&entry, &sec_path).map_err(|e| format!("Failed to copy file from {entry:?} to {sec_path:?}: {e}"))?;
253+
info!("📜 Wrote secondary file: {sec_path:?}");
254+
secondary_files.push(DefaultValue::File(get_file_metadata(&sec_path, None)));
255+
}
256+
}
257+
file.secondary_files = Some(secondary_files);
258+
unset_self()?;
259+
}
260+
261+
Ok(DefaultValue::File(file))
240262
}
241263

242264
fn handle_dir_output(entry: &PathBuf, initial_dir: &Path) -> Result<DefaultValue, Box<dyn Error>> {

crates/cwl/src/clt.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,7 @@ baseCommand:
300300
let tool = CommandLineTool::default().with_outputs(vec![CommandOutputParameter {
301301
id: "stdout".to_string(),
302302
type_: CWLType::Stdout,
303-
output_binding: None,
304-
format: None,
303+
..Default::default()
305304
}]);
306305
assert!(tool.has_stdout_output());
307306
}
@@ -311,8 +310,7 @@ baseCommand:
311310
let tool = CommandLineTool::default().with_outputs(vec![CommandOutputParameter {
312311
id: "stderr".to_string(),
313312
type_: CWLType::Stderr,
314-
output_binding: None,
315-
format: None,
313+
..Default::default()
316314
}]);
317315
assert!(tool.has_stderr_output());
318316
}

crates/cwl/src/inputs.rs

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::types::SecondaryFileSchema;
2+
13
use super::{
24
deserialize::Identifiable,
35
types::{CWLType, DefaultValue},
@@ -121,48 +123,6 @@ where
121123
Ok(parameters)
122124
}
123125

124-
#[derive(Serialize, Debug, Default, PartialEq, Clone)]
125-
pub struct SecondaryFileSchema {
126-
pub pattern: String,
127-
pub required: bool,
128-
}
129-
130-
impl From<String> for SecondaryFileSchema {
131-
fn from(pattern: String) -> Self {
132-
if pattern.ends_with("?") {
133-
let pattern = pattern.trim_end_matches('?').to_string();
134-
SecondaryFileSchema { pattern, required: false }
135-
} else {
136-
SecondaryFileSchema { pattern, required: true }
137-
}
138-
}
139-
}
140-
141-
impl<'de> Deserialize<'de> for SecondaryFileSchema {
142-
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
143-
where
144-
D: Deserializer<'de>,
145-
{
146-
let value: Value = Deserialize::deserialize(deserializer)?;
147-
match value {
148-
Value::String(pattern) => Ok(SecondaryFileSchema::from(pattern)),
149-
Value::Mapping(map) => {
150-
let pattern = map
151-
.get("pattern")
152-
.and_then(|v| v.as_str())
153-
.ok_or_else(|| serde::de::Error::custom("Expected string for pattern"))?
154-
.to_string();
155-
let required = map
156-
.get("required")
157-
.and_then(|v| v.as_bool())
158-
.unwrap_or(true);
159-
Ok(SecondaryFileSchema { pattern, required })
160-
}
161-
_ => Err(serde::de::Error::custom("Expected string or mapping for secondary file schema")),
162-
}
163-
}
164-
}
165-
166126
#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone)]
167127
#[serde(rename_all = "camelCase")]
168128
pub struct WorkflowStepInputParameter {

crates/cwl/src/outputs.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::{deserialize::Identifiable, types::CWLType};
2+
use crate::types::SecondaryFileSchema;
23
use serde::{Deserialize, Deserializer, Serialize};
34
use serde_yaml::Value;
45

@@ -12,6 +13,8 @@ pub struct CommandOutputParameter {
1213
pub output_binding: Option<CommandOutputBinding>,
1314
#[serde(skip_serializing_if = "Option::is_none")]
1415
pub format: Option<String>,
16+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
17+
pub secondary_files: Vec<SecondaryFileSchema>,
1518
}
1619

1720
impl CommandOutputParameter {

crates/cwl/src/types.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ impl File {
420420
let current = env::current_dir().unwrap_or_default();
421421
let absolute_path = if path.is_absolute() { path } else { &current.join(path) };
422422
let absolute_str = absolute_path.display().to_string();
423-
let metadata = fs::metadata(path).expect("Could not get metadata");
423+
let metadata = fs::metadata(path).unwrap_or_else(|_| panic!("Could not get metadata for {absolute_str}"));
424424
let mut hasher = Sha1::new();
425425
let hash = fs::read(path).ok().map(|f| {
426426
hasher.update(&f);
@@ -442,6 +442,30 @@ impl File {
442442
..Default::default()
443443
}
444444
}
445+
446+
pub fn preload(&self) -> Self {
447+
let loc = self.location.clone().unwrap_or_default();
448+
let path = Path::new(&loc);
449+
450+
let mut item = self.clone();
451+
if item.path.is_none() {
452+
item.path = Some(path.display().to_string());
453+
}
454+
455+
if item.basename.is_none() {
456+
item.basename = path.file_name().map(|f| f.to_string_lossy().into_owned());
457+
}
458+
459+
if item.nameroot.is_none() {
460+
item.nameroot = path.file_stem().map(|f| f.to_string_lossy().into_owned());
461+
}
462+
463+
if item.nameext.is_none() {
464+
item.nameext = path.extension().map(|f| format!(".{}", f.to_string_lossy()));
465+
}
466+
467+
item
468+
}
445469
}
446470

447471
fn resolve_format(format: Option<String>) -> Option<String> {
@@ -467,6 +491,45 @@ impl PathItem for File {
467491
}
468492
}
469493

494+
#[derive(Serialize, Debug, Default, PartialEq, Clone)]
495+
pub struct SecondaryFileSchema {
496+
pub pattern: String,
497+
pub required: bool,
498+
}
499+
500+
impl From<String> for SecondaryFileSchema {
501+
fn from(pattern: String) -> Self {
502+
if pattern.ends_with("?") {
503+
let pattern = pattern.trim_end_matches('?').to_string();
504+
SecondaryFileSchema { pattern, required: false }
505+
} else {
506+
SecondaryFileSchema { pattern, required: true }
507+
}
508+
}
509+
}
510+
511+
impl<'de> Deserialize<'de> for SecondaryFileSchema {
512+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
513+
where
514+
D: Deserializer<'de>,
515+
{
516+
let value: Value = Deserialize::deserialize(deserializer)?;
517+
match value {
518+
Value::String(pattern) => Ok(SecondaryFileSchema::from(pattern)),
519+
Value::Mapping(map) => {
520+
let pattern = map
521+
.get("pattern")
522+
.and_then(|v| v.as_str())
523+
.ok_or_else(|| serde::de::Error::custom("Expected string for pattern"))?
524+
.to_string();
525+
let required = map.get("required").and_then(|v| v.as_bool()).unwrap_or(true);
526+
Ok(SecondaryFileSchema { pattern, required })
527+
}
528+
_ => Err(serde::de::Error::custom("Expected string or mapping for secondary file schema")),
529+
}
530+
}
531+
}
532+
470533
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
471534
#[serde(rename_all = "camelCase")]
472535
pub struct Directory {

0 commit comments

Comments
 (0)