A corner-case issue with invoking CLI modules

Hi,

TL;DR: There’s a little clash between Slicer CLI module invocation and Python’s argparse, in a corner case; there’s a non-obvious workaround; maybe there should be a fix.

I’m developing some functionality in Slicer as a pair of modules – a UI module which invokes a CLI module. The CLI module is, in fact, just a wrapper around an existing, non-Slicer-related executable, so I wrote it in Python, using the stdlib’s argparse to parse parameters.

One of the parameters I want to pass from the UI to the CLI module is a float-vector indicating spatial direction. So, in typical use, it looks like 1.0,0.0,0.0. But it can also look like -1.0,0.5,0.5. For clarity of description, let’s say it’s called “vector”.

My UI module invokes the CLI module as exemplified in documentation, using
slicer.cli.runSync(slicer.modules.<my-module>, None, cliParams).
This builds a command line with the parameters as defined in the module’s XML file, and that all seems fine.

The issue is that argparse, when it sees an argument which starts with the flag prefix character (-), and doesn’t “look like a negative number”, thinks it is seeing an option. So when I tried to invoke the module with something like
--vector -1.0,0.5,0.5
I got exactly the error in the last example in the argparse documentation:
error: argument --vector: expected one argument

The obvious fix would be to use something unambiguous like
--vector=-1.0,0.5,0,5
but there is no way to tell Slicer to do that. The next thing I hoped for was to try to change the option prefix character (argparse lets you say that options should be prefixed by e.g. + instead of -), but alas, the - as prefix is also hard-coded in Slicer.

The workaround I found was to format the vector as string myself, and add a space in its front. Then it doesn’t start with a -, but whoever needs to parse floats out of it still manages to do so.

In case anyone seeks to improve the code handling this, a word of warning: The function which actually does the invocation, as far as I can see, is vtkSlicerCLIModuleLogic::ApplyTask(), in Slicer/Base/QTCLI/vtkSlicerCLIModuleLogic.cxx; at least in version 5.6.2, this function seems to be >1500 lines long.