Repository: common-workflow-language/cwltool

Issue:
Trailing newlines stripped from InitialWorkDirRequirement-generated file
I try to do http://www.commonwl.org/v1.0/UserGuide.html#Creating_files_at_runtime with `cwltool`, but I get the output:

> CONFIGVAR=Hello world!Final process status is success

The trailing newline is missing from the generated `example.conf`.

This is even if I use the [YAML block chomping](http://www.yaml.org/spec/1.2/spec.html#id2794534) indicator `|+` to preserve newline.  Is the `entry` being stripped of the trailing newline.



Discussion:
I would expect the YAML block chomping to work, so that you would need to use `|-` to not get a trailing newline at all (e.g. for files with no line feeds at all) or `|+` to get all trailing empty lines, and the default `|` to include the final newline, ignoring empty lines.
Current behavior is to explicitly strip leading and trailing whitespace, even after YAML parsing.  "\n" might work to get an explicit newline, but I'm not sure.  YAML parsing does have relatively sensible rules about handling leading and trailing whitespace, so maybe extra whitespace stripping is unnecessary.
You still probably want to trim at either end for all Non-Content tag, it
does not make sense to for instance have a /n in the Docker image name.

On 9 Dec 2016 6:54 pm, "Peter Amstutz" <notifications@github.com> wrote:

> Current behavior is to explicitly strip leading and trailing whitespace,
> even after YAML parsing. "\n" might work to get an explicit newline, but
> I'm not sure. YAML parsing does have relatively sensible rules about
> handling leading and trailing whitespace, so maybe extra whitespace
> stripping is unnecessary.
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/common-workflow-language/cwltool/issues/247#issuecomment-266092049>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAPd5X75q8gLiYT39yKocX-92jngE4POks5rGaP7gaJpZM4LHmXa>
> .
>


Resolving patch:
diff --git a/cwltool/builder.py b/cwltool/builder.py
--- a/cwltool/builder.py
+++ b/cwltool/builder.py
@@ -252,8 +252,8 @@ def generate_arg(self, binding):  # type: (Dict[Text,Any]) -> List[Text]
 
         return [a for a in args if a is not None]
 
-    def do_eval(self, ex, context=None, pull_image=True, recursive=False):
-        # type: (Union[Dict[Text, Text], Text], Any, bool, bool) -> Any
+    def do_eval(self, ex, context=None, pull_image=True, recursive=False, strip_whitespace=True):
+        # type: (Union[Dict[Text, Text], Text], Any, bool, bool, bool) -> Any
         if recursive:
             if isinstance(ex, dict):
                 return {k: self.do_eval(v, context, pull_image, recursive) for k, v in iteritems(ex)}
@@ -268,4 +268,5 @@ def do_eval(self, ex, context=None, pull_image=True, recursive=False):
                                   timeout=self.timeout,
                                   debug=self.debug,
                                   js_console=self.js_console,
-                                  force_docker_pull=self.force_docker_pull)
+                                  force_docker_pull=self.force_docker_pull,
+                                  strip_whitespace=strip_whitespace)
diff --git a/cwltool/draft2tool.py b/cwltool/draft2tool.py
--- a/cwltool/draft2tool.py
+++ b/cwltool/draft2tool.py
@@ -383,7 +383,7 @@ def rm_pending_output_callback(output_callbacks, jobcachepending,
             else:
                 for t in initialWorkdir["listing"]:
                     if "entry" in t:
-                        et = {u"entry": builder.do_eval(t["entry"])}
+                        et = {u"entry": builder.do_eval(t["entry"], strip_whitespace=False)}
                         if "entryname" in t:
                             et["entryname"] = builder.do_eval(t["entryname"])
                         else:
diff --git a/cwltool/expression.py b/cwltool/expression.py
--- a/cwltool/expression.py
+++ b/cwltool/expression.py
@@ -173,9 +173,10 @@ def evaluator(ex, jslib, obj, fullJS=False, timeout=None, force_docker_pull=Fals
 
 def interpolate(scan, rootvars,
                 timeout=None, fullJS=None, jslib="", force_docker_pull=False,
-                debug=False, js_console=False):
-    # type: (Text, Dict[Text, Any], int, bool, Union[str, Text], bool, bool, bool) -> JSON
-    scan = scan.strip()
+                debug=False, js_console=False, strip_whitespace=True):
+    # type: (Text, Dict[Text, Any], int, bool, Union[str, Text], bool, bool, bool, bool) -> JSON
+    if strip_whitespace:
+        scan = scan.strip()
     parts = []
     w = scanner(scan)
     while w:
@@ -202,8 +203,9 @@ def interpolate(scan, rootvars,
 
 
 def do_eval(ex, jobinput, requirements, outdir, tmpdir, resources,
-            context=None, pull_image=True, timeout=None, force_docker_pull=False, debug=False, js_console=False):
-    # type: (Union[dict, AnyStr], Dict[Text, Union[Dict, List, Text]], List[Dict[Text, Any]], Text, Text, Dict[Text, Union[int, Text]], Any, bool, int, bool, bool, bool) -> Any
+            context=None, pull_image=True, timeout=None, force_docker_pull=False,
+            debug=False, js_console=False, strip_whitespace=True):
+    # type: (Union[dict, AnyStr], Dict[Text, Union[Dict, List, Text]], List[Dict[Text, Any]], Text, Text, Dict[Text, Union[int, Text]], Any, bool, int, bool, bool, bool, bool) -> Any
 
     runtime = copy.copy(resources)
     runtime["tmpdir"] = docker_windows_path_adjust(tmpdir)
@@ -231,7 +233,8 @@ def do_eval(ex, jobinput, requirements, outdir, tmpdir, resources,
                                jslib=jslib,
                                force_docker_pull=force_docker_pull,
                                debug=debug,
-                               js_console=js_console)
+                               js_console=js_console,
+                               strip_whitespace=strip_whitespace)
 
         except Exception as e:
             raise WorkflowException("Expression evaluation error:\n%s" % e)


Test patch:
diff --git a/tests/test_iwdr.py b/tests/test_iwdr.py
new file mode 100644
--- /dev/null
+++ b/tests/test_iwdr.py
@@ -0,0 +1,16 @@
+import unittest
+
+import cwltool
+import cwltool.factory
+from .util import get_data
+
+
+class TestInitialWorkDir(unittest.TestCase):
+
+    def test_newline_in_entry(self):
+        """
+        test that files in InitialWorkingDirectory are created with a newline character
+        """
+        f = cwltool.factory.Factory()
+        echo = f.make(get_data("tests/wf/iwdr-entry.cwl"))
+        self.assertEqual(echo(message="hello"), {"out": "CONFIGVAR=hello\n"})
diff --git a/tests/wf/iwdr-entry.cwl b/tests/wf/iwdr-entry.cwl
new file mode 100644
--- /dev/null
+++ b/tests/wf/iwdr-entry.cwl
@@ -0,0 +1,22 @@
+#!/usr/bin/env cwl-runner
+
+class: CommandLineTool
+cwlVersion: v1.0
+baseCommand: ["cat", "example.conf"]
+
+requirements:
+  InitialWorkDirRequirement:
+    listing:
+      - entryname: example.conf
+        entry: |
+          CONFIGVAR=$(inputs.message)
+
+inputs:
+  message: string
+outputs:
+  out:
+    type: string
+    outputBinding:
+      glob: example.conf
+      loadContents: true
+      outputEval: $(self[0].contents)
\ No newline at end of file