Skip to content

Commit 1d234b5

Browse files
committed
support Files and Directories as WorkdirRequirement to prepare for #103
1 parent b217005 commit 1d234b5

7 files changed

Lines changed: 147 additions & 69 deletions

File tree

crates/cwl-execution/src/expression.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{environment::RuntimeEnvironment, split_ranges};
22
use cwl::{
33
clt::{Argument, Command},
44
et::{Expression, ExpressionType},
5-
requirements::Requirement,
5+
requirements::{Requirement, WorkDirItem},
66
types::{DefaultValue, Entry},
77
CWLDocument,
88
};
@@ -187,15 +187,17 @@ pub(crate) fn process_tool_expressions(tool: &mut CWLDocument) -> Result<(), Box
187187
for requirement in requirements {
188188
if let Requirement::InitialWorkDirRequirement(wd_req) = requirement {
189189
for listing in &mut wd_req.listing {
190-
listing.entryname = replace_expressions(&listing.entryname)?;
191-
listing.entry = match &mut listing.entry {
192-
Entry::Source(src) => {
193-
*src = replace_expressions(src)?;
194-
Entry::Source(src.clone())
195-
}
196-
Entry::Include(include) => {
197-
include.include = replace_expressions(&include.include)?;
198-
Entry::Include(include.clone())
190+
if let WorkDirItem::Dirent(dirent) = listing {
191+
dirent.entryname = replace_expressions(&dirent.entryname)?;
192+
dirent.entry = match &mut dirent.entry {
193+
Entry::Source(src) => {
194+
*src = replace_expressions(src)?;
195+
Entry::Source(src.clone())
196+
}
197+
Entry::Include(include) => {
198+
include.include = replace_expressions(&include.include)?;
199+
Entry::Include(include.clone())
200+
}
199201
}
200202
}
201203
}

crates/cwl-execution/src/staging.rs

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
use cwl::{
66
inputs::CommandInputParameter,
77
outputs::CommandOutputParameter,
8-
requirements::Requirement,
8+
requirements::{Requirement, WorkDirItem},
99
types::{CWLType, DefaultValue, Directory, Entry, File, PathItem},
1010
CWLDocument,
1111
};
@@ -81,29 +81,49 @@ fn stage_requirements(requirements: &Option<Vec<Requirement>>, tool_path: &Path,
8181
for requirement in requirements {
8282
if let Requirement::InitialWorkDirRequirement(iwdr) = requirement {
8383
for listing in &iwdr.listing {
84-
let into_path = path.join(&listing.entryname); //stage as listing's entry name
85-
let path_str = &into_path.to_string_lossy();
86-
match &listing.entry {
87-
Entry::Source(src) => {
88-
if fs::exists(src).unwrap_or(false) {
89-
copy_file(src, &into_path).map_err(|e| format!("Failed to copy file from {} to {}: {}", src, path_str, e))?;
90-
} else {
91-
create_and_write_file(&into_path, src).map_err(|e| format!("Failed to create file {:?}: {}", into_path, e))?;
84+
let into_path = match listing {
85+
WorkDirItem::Dirent(dirent) => path.join(&dirent.entryname),
86+
WorkDirItem::FileOrDirectory(val) => match &**val {
87+
DefaultValue::File(file) => {
88+
let location = Path::new(file.location.as_ref().unwrap());
89+
path.join(location.file_name().unwrap())
90+
}
91+
DefaultValue::Directory(directory) => {
92+
let location = Path::new(directory.location.as_ref().unwrap());
93+
path.join(location.file_name().unwrap())
9294
}
93-
}
94-
Entry::Include(include) => {
95-
let mut include_path = tool_path.join(&include.include);
96-
if !include_path.exists() || !include_path.is_file() {
97-
let current = env::current_dir()?;
98-
let file_path: String = include.include.clone().trim_start_matches(|c: char| !c.is_alphabetic()).to_string();
99-
include_path = current.join(file_path.clone());
100-
if !include_path.exists() || !include_path.is_file() {
101-
include_path = current.join(tool_path).join(file_path);
95+
_ => unreachable!(),
96+
},
97+
WorkDirItem::Expression(_) => unreachable!(), //resolved before!
98+
};
99+
//stage as listing's entry name
100+
let path_str = &into_path.to_string_lossy();
101+
match &listing {
102+
WorkDirItem::Dirent(dirent) => match &dirent.entry {
103+
Entry::Source(src) => {
104+
if fs::exists(src).unwrap_or(false) {
105+
copy_file(src, &into_path).map_err(|e| format!("Failed to copy file from {} to {}: {}", src, path_str, e))?;
106+
} else {
107+
create_and_write_file(&into_path, src).map_err(|e| format!("Failed to create file {:?}: {}", into_path, e))?;
102108
}
103109
}
104-
copy_file(include_path.to_str().unwrap(), &into_path)
105-
.map_err(|e| format!("Failed to copy file from {:?} to {:?}: {}", include_path, into_path, e))?;
106-
}
110+
Entry::Include(include) => {
111+
let path = get_iwdr_src(tool_path, &include.include)?;
112+
copy_file(&path, &into_path).map_err(|e| format!("Failed to copy file from {:?} to {:?}: {}", path, into_path, e))?;
113+
}
114+
},
115+
WorkDirItem::FileOrDirectory(val) => match &**val {
116+
DefaultValue::File(file) => {
117+
let path = get_iwdr_src(tool_path, file.location.as_ref().unwrap())?;
118+
copy_file(&path, &into_path).map_err(|e| format!("Failed to copy file from {:?} to {:?}: {}", path, into_path, e))?;
119+
}
120+
DefaultValue::Directory(directory) => {
121+
let path = get_iwdr_src(tool_path, directory.location.as_ref().unwrap())?;
122+
copy_dir(&path, &into_path).map_err(|e| format!("Failed to copy dir from {:?} to {:?}: {}", path, into_path, e))?;
123+
}
124+
_ => unreachable!(),
125+
},
126+
WorkDirItem::Expression(_) => unreachable!(),
107127
}
108128
staged_files.push(path_str.clone().into_owned());
109129
}
@@ -120,6 +140,20 @@ fn stage_requirements(requirements: &Option<Vec<Requirement>>, tool_path: &Path,
120140
Ok(staged_files)
121141
}
122142

143+
fn get_iwdr_src(tool_path: &Path, basepath: &String) -> Result<PathBuf, Box<dyn Error + 'static>> {
144+
let mut path = tool_path.join(basepath);
145+
if !path.exists() {
146+
let current = env::current_dir()?;
147+
let file_path: String = basepath.clone().trim_start_matches(|c: char| !c.is_alphabetic()).to_string();
148+
path = current.join(file_path.clone());
149+
if !path.exists() {
150+
path = current.join(tool_path).join(file_path);
151+
}
152+
}
153+
154+
Ok(path)
155+
}
156+
123157
fn stage_input_files(
124158
inputs: &[CommandInputParameter],
125159
runtime: &mut RuntimeEnvironment,

crates/cwl-execution/src/validate.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{environment::RuntimeEnvironment, io::get_file_property};
22
use cwl::{
33
clt::{Argument, Command, CommandLineTool},
44
inputs::CommandInputParameter,
5-
requirements::Requirement,
5+
requirements::{Requirement, WorkDirItem},
66
types::{DefaultValue, Entry, EnviromentDefs, PathItem},
77
CWLDocument,
88
};
@@ -112,16 +112,18 @@ fn set_placeholder_values_requirements(requirements: &mut Vec<Requirement>, runt
112112

113113
if let Requirement::InitialWorkDirRequirement(wd_req) = requirement {
114114
for listing in &mut wd_req.listing {
115-
listing.entryname = set_placeholder_values_in_string(&listing.entryname, runtime, inputs);
116-
listing.entry = match &mut listing.entry {
117-
Entry::Source(src) => {
118-
*src = set_placeholder_values_in_string(src, runtime, inputs);
119-
Entry::Source(src.clone())
120-
}
121-
Entry::Include(include) => {
122-
let updated_include = set_placeholder_values_in_string(&include.include, runtime, inputs);
123-
include.include = updated_include;
124-
Entry::Include(include.clone())
115+
if let WorkDirItem::Dirent(dirent) = listing {
116+
dirent.entryname = set_placeholder_values_in_string(&dirent.entryname, runtime, inputs);
117+
dirent.entry = match &mut dirent.entry {
118+
Entry::Source(src) => {
119+
*src = set_placeholder_values_in_string(src, runtime, inputs);
120+
Entry::Source(src.clone())
121+
}
122+
Entry::Include(include) => {
123+
let updated_include = set_placeholder_values_in_string(&include.include, runtime, inputs);
124+
include.include = updated_include;
125+
Entry::Include(include.clone())
126+
}
125127
}
126128
}
127129
}

crates/cwl/src/requirements.rs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use super::types::{Dirent, Entry, EnviromentDefs};
2-
use crate::{types::Include, CWLDocument, StringOrNumber};
2+
use crate::{
3+
types::{DefaultValue, Include},
4+
CWLDocument, StringOrNumber,
5+
};
36
use serde::{Deserialize, Deserializer, Serialize};
47
use serde_yaml::{Mapping, Value};
58

@@ -126,45 +129,57 @@ fn get_entry_name(input: &str) -> String {
126129
format!("$(inputs.{})", i.to_lowercase()).to_string()
127130
}
128131

132+
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
133+
#[serde(untagged)]
134+
pub enum WorkDirItem {
135+
Dirent(Dirent),
136+
FileOrDirectory(Box<DefaultValue>),
137+
Expression(String),
138+
}
139+
129140
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
130141
#[serde(rename_all = "camelCase")]
131142
pub struct InitialWorkDirRequirement {
132-
pub listing: Vec<Dirent>,
143+
pub listing: Vec<WorkDirItem>,
133144
}
134145

135146
impl InitialWorkDirRequirement {
136147
pub fn from_file(filename: &str) -> Self {
137148
InitialWorkDirRequirement {
138-
listing: vec![Dirent {
149+
listing: vec![WorkDirItem::Dirent(Dirent {
139150
entryname: filename.to_string(),
140151
entry: Entry::from_file(filename),
141-
}],
152+
})],
142153
}
143154
}
144155
pub fn from_files(filenames: &[&str]) -> Self {
145156
InitialWorkDirRequirement {
146157
listing: filenames
147158
.iter()
148-
.map(|&filename| Dirent {
149-
entryname: filename.to_string(),
150-
entry: Entry::Source(get_entry_name(filename)),
159+
.map(|&filename| {
160+
WorkDirItem::Dirent(Dirent {
161+
entryname: filename.to_string(),
162+
entry: Entry::Source(get_entry_name(filename)),
163+
})
151164
})
152165
.collect(),
153166
}
154167
}
155168
pub fn from_contents(entryname: &str, contents: &str) -> Self {
156169
InitialWorkDirRequirement {
157-
listing: vec![Dirent {
170+
listing: vec![WorkDirItem::Dirent(Dirent {
158171
entryname: entryname.to_string(),
159172
entry: Entry::Source(contents.to_string()),
160-
}],
173+
})],
161174
}
162175
}
163176

164177
pub fn add_files(&mut self, filenames: &[&str]) {
165-
self.listing.extend(filenames.iter().map(|&f| Dirent {
166-
entryname: f.to_string(),
167-
entry: Entry::Source(get_entry_name(f)),
178+
self.listing.extend(filenames.iter().map(|&f| {
179+
WorkDirItem::Dirent(Dirent {
180+
entryname: f.to_string(),
181+
entry: Entry::Source(get_entry_name(f)),
182+
})
168183
}));
169184
}
170185
}
@@ -262,7 +277,10 @@ mod tests {
262277
pub fn test_initial_workdir_requirement() {
263278
let req = InitialWorkDirRequirement::from_file("../../tests/test_data/echo.py");
264279
assert_eq!(req.listing.len(), 1);
265-
assert_eq!(req.listing[0].entryname, "../../tests/test_data/echo.py".to_string());
280+
assert!(matches!(req.listing[0], WorkDirItem::Dirent(_)));
281+
if let WorkDirItem::Dirent(dirent) = &req.listing[0] {
282+
assert_eq!(dirent.entryname, "../../tests/test_data/echo.py".to_string());
283+
}
266284
}
267285

268286
#[test]

src/cwl.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use cwl::{
44
inputs::{CommandInputParameter, WorkflowStepInput},
55
load_tool,
66
outputs::WorkflowOutputParameter,
7-
requirements::Requirement,
7+
requirements::{Requirement, WorkDirItem},
88
types::{DefaultValue, Entry, PathItem},
99
wf::{StringOrDocument, Workflow, WorkflowStep},
1010
};
@@ -51,8 +51,10 @@ impl Saveable for CommandLineTool {
5151
}
5252
} else if let Requirement::InitialWorkDirRequirement(iwdr) = requirement {
5353
for listing in &mut iwdr.listing {
54-
if let Entry::Include(include) = &mut listing.entry {
55-
include.include = resolve_path(&include.include, path);
54+
if let WorkDirItem::Dirent(dirent) = listing {
55+
if let Entry::Include(include) = &mut dirent.entry {
56+
include.include = resolve_path(&include.include, path);
57+
}
5658
}
5759
}
5860
}
@@ -327,10 +329,10 @@ mod tests {
327329
assert_eq!(
328330
*req_0,
329331
Requirement::InitialWorkDirRequirement(InitialWorkDirRequirement {
330-
listing: vec![Dirent {
332+
listing: vec![WorkDirItem::Dirent(Dirent {
331333
entry: Entry::from_file(os_path("../../test/script.py")),
332334
entryname: "test/script.py".to_string()
333-
}]
335+
})]
334336
})
335337
);
336338
assert_eq!(

tests/doc_tests.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
///This file contains all examples described here: <https://fairagro.github.io/m4.4_sciwin_client/examples/tool-creation>/
22
mod common;
33
use common::{check_git_user, setup_python};
4-
use cwl::{clt::Command, load_tool, load_workflow, requirements::Requirement, types::Entry};
4+
use cwl::{
5+
clt::Command,
6+
load_tool, load_workflow,
7+
requirements::{Requirement, WorkDirItem},
8+
types::Entry,
9+
};
510
use cwl_execution::io::copy_dir;
611
use s4n::commands::{
712
execute::{execute_local, LocalExecuteArgs, Runner},
@@ -240,9 +245,15 @@ pub fn test_implicit_inputs_hardcoded_files() {
240245

241246
if let Requirement::InitialWorkDirRequirement(initial) = &requirements[0] {
242247
assert_eq!(initial.listing.len(), 2);
243-
assert_eq!(initial.listing[0].entryname, "load.py");
244-
assert_eq!(initial.listing[1].entryname, "file.txt");
245-
assert_eq!(initial.listing[1].entry, Entry::Source("$(inputs.file_txt)".into()));
248+
assert!(matches!(initial.listing[0], WorkDirItem::Dirent(_)));
249+
assert!(matches!(initial.listing[1], WorkDirItem::Dirent(_)));
250+
if let WorkDirItem::Dirent(dirent) = &initial.listing[0] {
251+
assert_eq!(dirent.entryname, "load.py");
252+
}
253+
if let WorkDirItem::Dirent(dirent) = &initial.listing[1] {
254+
assert_eq!(dirent.entryname, "file.txt");
255+
assert_eq!(dirent.entry, Entry::Source("$(inputs.file_txt)".into()));
256+
}
246257
} else {
247258
panic!("InitialWorkDirRequirement not found!");
248259
}

tests/tool_integration_test.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use common::os_path;
33
use cwl::{
44
clt::{Argument, CommandLineTool},
55
load_tool,
6-
requirements::{NetworkAccess, Requirement},
6+
requirements::{NetworkAccess, Requirement, WorkDirItem},
77
types::{CWLType, Entry},
88
};
99
use fstest::fstest;
@@ -77,8 +77,15 @@ pub fn tool_create_test_inputs_outputs() {
7777
if let Some(req) = &tool.requirements {
7878
if let Requirement::InitialWorkDirRequirement(iwdr) = &req[0] {
7979
assert_eq!(iwdr.listing.len(), 2);
80-
assert_eq!(iwdr.listing[0].entryname, script);
81-
assert_eq!(iwdr.listing[1].entryname, input);
80+
assert!(matches!(iwdr.listing[0], WorkDirItem::Dirent(_)));
81+
assert!(matches!(iwdr.listing[1], WorkDirItem::Dirent(_)));
82+
83+
if let WorkDirItem::Dirent(dirent) = &iwdr.listing[0] {
84+
assert_eq!(dirent.entryname, script);
85+
}
86+
if let WorkDirItem::Dirent(dirent) = &iwdr.listing[1] {
87+
assert_eq!(dirent.entryname, input);
88+
}
8289
} else {
8390
panic!("Not an InitialWorkDirRequirement")
8491
}
@@ -393,7 +400,9 @@ pub fn test_shell_script() {
393400
if let Some(req) = &tool.requirements {
394401
assert_eq!(req.len(), 1);
395402
if let Requirement::InitialWorkDirRequirement(iwdr) = &req[0] {
396-
assert_eq!(iwdr.listing[0].entryname, "./script.sh");
403+
if let WorkDirItem::Dirent(dirent) = &iwdr.listing[0] {
404+
assert_eq!(dirent.entryname, "./script.sh");
405+
}
397406
} else {
398407
panic!("Not an InitialWorkDirRequirement")
399408
}
@@ -463,6 +472,6 @@ pub fn tool_create_test_network() {
463472

464473
let tool_path = Path::new("workflows/echo/echo.cwl");
465474
let tool = load_tool(tool_path).unwrap();
466-
475+
467476
assert!(tool.get_requirement::<NetworkAccess>().is_some());
468477
}

0 commit comments

Comments
 (0)