Repository: common-workflow-language/cwltool

Issue:
Default value in array stays
## Expected Behavior
cwltool ignores default values when using the argument on the command line

## Actual Behavior
Calling cwltool with an argument that is a string[] in the command line will keep the the default value in the array.
Does not happen when using a yaml file as input.

## Workflow Code
```
#!/usr/bin/env cwl-runner

cwlVersion: v1.2
class: CommandLineTool

baseCommand: [cat]

stdout: "cat_file"

inputs:
  file_paths:
    type: string[]?
    inputBinding:
        position: 1
    default: ["/home/bart/cwl_test/test1"]

outputs:
  output:
    type: stdout
```

## Full Traceback
cwltool cat.cwl --file_paths /home/bart/cwl_test/test2
```pytb
INFO /usr/local/bin/cwltool 3.1.20211004060744
INFO Resolved 'cat.cwl' to 'file:///home/bart/cwl_test/cat.cwl'
DEBUG Parsed job order from command line: {
    "id": "cat.cwl",
    "file_paths": [
        "/home/bart/cwl_test/test1",
        "/home/bart/cwl_test/test2"
    ]
}
DEBUG [job cat.cwl] initializing from file:///home/bart/cwl_test/cat.cwl
DEBUG [job cat.cwl] {
    "file_paths": [
        "/home/bart/cwl_test/test1",
        "/home/bart/cwl_test/test2"
    ]
}
DEBUG [job cat.cwl] path mappings is {}
DEBUG [job cat.cwl] command line bindings is [
    {
        "position": [
            -1000000,
            0
        ],
        "datum": "cat"
    },
    {
        "position": [
            1,
            "file_paths"
        ],
        "datum": [
            "/home/bart/cwl_test/test1",
            "/home/bart/cwl_test/test2"
        ]
    },
    {
        "position": [
            1,
            "file_paths",
            0,
            0,
            "file_paths",
            "file_paths"
        ],
        "datum": "/home/bart/cwl_test/test1"
    },
    {
        "position": [
            1,
            "file_paths",
            1,
            0,
            "file_paths",
            "file_paths"
        ],
        "datum": "/home/bart/cwl_test/test2"
    }
]
DEBUG [job cat.cwl] initial work dir {}
INFO [job cat.cwl] /tmp/k8a7y0cy$ cat \
    /home/bart/cwl_test/test1 \
    /home/bart/cwl_test/test2 > /tmp/k8a7y0cy/cat_file
DEBUG Could not collect memory usage, job ended before monitoring began.
INFO [job cat.cwl] completed success
DEBUG [job cat.cwl] outputs {
    "output": {
        "location": "file:///tmp/k8a7y0cy/cat_file",
        "basename": "cat_file",
        "nameroot": "cat_file",
        "nameext": "",
        "class": "File",
        "checksum": "sha1$2c42895942870df6db4d2a6ee3ebecadc77912d0",
        "size": 8,
        "http://commonwl.org/cwltool#generation": 0
    }
}
DEBUG [job cat.cwl] Removing input staging directory /tmp/fphi2_8e
DEBUG [job cat.cwl] Removing temporary directory /tmp/5iuf8ecf
DEBUG Moving /tmp/k8a7y0cy/cat_file to /home/bart/cwl_test/cat_file
DEBUG Removing intermediate output directory /tmp/k8a7y0cy
{
    "output": {
        "location": "file:///home/bart/cwl_test/cat_file",
        "basename": "cat_file",
        "class": "File",
        "checksum": "sha1$2c42895942870df6db4d2a6ee3ebecadc77912d0",
        "size": 8,
        "path": "/home/bart/cwl_test/cat_file"
    }
}
INFO Final process status is success

```

## Your Environment
* cwltool version: 
/usr/local/bin/cwltool 3.1.20211004060744



Discussion:

Resolving patch:
diff --git a/cwltool/argparser.py b/cwltool/argparser.py
--- a/cwltool/argparser.py
+++ b/cwltool/argparser.py
@@ -817,6 +817,43 @@ class DirectoryAppendAction(FSAppendAction):
     objclass = "Directory"
 
 
+class AppendAction(argparse.Action):
+    """An argparse action that clears the default values if any value is provided.
+
+    Attributes:
+        _called (bool): Initially set to ``False``, changed if any value is appended.
+    """
+
+    def __init__(
+        self,
+        option_strings: List[str],
+        dest: str,
+        nargs: Any = None,
+        **kwargs: Any,
+    ) -> None:
+        """Intialize."""
+        super().__init__(option_strings, dest, **kwargs)
+        self._called = False
+
+    def __call__(
+        self,
+        parser: argparse.ArgumentParser,
+        namespace: argparse.Namespace,
+        values: Union[str, Sequence[Any], None],
+        option_string: Optional[str] = None,
+    ) -> None:
+        g = getattr(namespace, self.dest, None)
+        if g is None:
+            g = []
+        if self.default is not None and not self._called:
+            # If any value was specified, we then clear the list of options before appending.
+            # We cannot always clear the ``default`` attribute since it collects the ``values`` appended.
+            self.default.clear()
+            self._called = True
+        g.append(values)
+        setattr(namespace, self.dest, g)
+
+
 def add_argument(
     toolparser: argparse.ArgumentParser,
     name: str,
@@ -864,7 +901,7 @@ def add_argument(
         elif inptype["items"] == "Directory":
             action = DirectoryAppendAction
         else:
-            action = "append"
+            action = AppendAction
     elif isinstance(inptype, MutableMapping) and inptype["type"] == "enum":
         atype = str
     elif isinstance(inptype, MutableMapping) and inptype["type"] == "record":


Test patch:
diff --git a/tests/default_values_list.cwl b/tests/default_values_list.cwl
new file mode 100644
--- /dev/null
+++ b/tests/default_values_list.cwl
@@ -0,0 +1,20 @@
+#!/usr/bin/env cwl-runner
+# From https://github.com/common-workflow-language/cwltool/issues/1632
+
+cwlVersion: v1.2
+class: CommandLineTool
+
+baseCommand: [cat]
+
+stdout: "cat_file"
+
+inputs:
+  file_paths:
+    type: string[]?
+    inputBinding:
+        position: 1
+    default: ["/home/bart/cwl_test/test1"]
+
+outputs:
+  output:
+    type: stdout
diff --git a/tests/test_toolargparse.py b/tests/test_toolargparse.py
--- a/tests/test_toolargparse.py
+++ b/tests/test_toolargparse.py
@@ -1,7 +1,7 @@
 import argparse
 from io import StringIO
 from pathlib import Path
-from typing import Callable
+from typing import Any, Callable, Dict, List
 
 import pytest
 
@@ -195,3 +195,35 @@ def test_argparser_without_doc() -> None:
     p = argparse.ArgumentParser()
     parser = generate_parser(p, tool, {}, [], False)
     assert parser.description is None
+
+
+@pytest.mark.parametrize(
+    "job_order,expected_values",
+    [
+        # no arguments, so we expect the default value
+        ([], ["/home/bart/cwl_test/test1"]),
+        # arguments, provided, one or many, meaning that the default value is not expected
+        (["--file_paths", "/home/bart/cwl_test/test2"], ["/home/bart/cwl_test/test2"]),
+        (
+            [
+                "--file_paths",
+                "/home/bart/cwl_test/test2",
+                "--file_paths",
+                "/home/bart/cwl_test/test3",
+            ],
+            ["/home/bart/cwl_test/test2", "/home/bart/cwl_test/test3"],
+        ),
+    ],
+)
+def test_argparse_append_with_default(
+    job_order: List[str], expected_values: List[str]
+) -> None:
+    """The appended arguments must not include the default. But if no appended argument, then the default is used."""
+    loadingContext = LoadingContext()
+    tool = load_tool(get_data("tests/default_values_list.cwl"), loadingContext)
+    toolparser = generate_parser(
+        argparse.ArgumentParser(prog="test"), tool, {}, [], False
+    )
+    cmd_line = vars(toolparser.parse_args(job_order))
+    file_paths = list(cmd_line["file_paths"])
+    assert expected_values == file_paths