Insert global parameters before a subcommand argument

Many programs require global parameters before any subcommand name, and reject them if they appear afterwards with the specific parameters. For example, git, or any Python program using Click for argument processing. The general pattern is

program --flag --global=value ... subcommand --fspecific --specific=value arg ...

Does anyone have an elegant pattern for inserting global parameters before a subcommand in a program_subcommand.cwl specification? I’m hoping that this is an ordinary use case and that there is already a clever solution.

I am unhappy with the following:

  1. Specifying the global parameters without an inputBinding and pasting them one-by-one into arguments:
    inputs:
      flag:
        type: boolean
        default: False
        inputBinding:
          prefix: "--flag"
      global:
        type: string
        default: VALUE
      data:
        type: string
        inputBinding:
          position: 1
    baseCommand:
      - "program"
    arguments:
      - $("--global=" + inputs.global)
      - "$(inputs.flag ? '--flag' : '--no-flag')"
      - "subcommand"
    
    This is unsatisfying because none of the inputBinding helpers like prefix are available, and because the global parameters must have a default value since they are unconditionally added to the command line. (Mitigated if arguments is an expression, but that obfuscates the definition.)
  2. Specifying the subcommand as a parameter and relying on position:
    inputs:
      flag:
        type: boolean
        default: False
        inputBinding:
          prefix: "--flag"
      global:
        type: string?
        inputBinding:
          prefix: "--global"
          separator: false
      subcommand:
        type: string
        default: "subcommand"
        inputBinding:
          position: 1
      data:
        type: string
        inputBinding:
          position: 2
    baseCommand:
      - "program"
    arguments: []
    
    This is unsatisfying because it pollutes the API with a useless parameter, and :warning: allows the user to override the subcommand.
  3. Building a shell script to sort the parameters before invocation – inelegant, and even more complicated if specific parameters can override global parameters of the same name.

I’m not sure what the mechanism would be.

  • Splicing arguments at a numbered position instead of the beginning? :slightly_smiling_face:
  • Defining hidden readonly inputs? :neutral_face:
  • Using expressions to post-process the generated command line? :slightly_frowning_face:

Thanks in advance for any suggestions

Any of these should work, which one is best depends on the specifics (how many global/subcommand flags, how often do they change, etc.):
1: use position that sorts before 0 in global flags’ inputBinding. This way they are inserted before arguments which do not specify position (and hence use the default of 0).

inputs:
  global1:
    type: string?
    inputBinding:
      prefix: --global_arg_1
      position: -2
   global2:
    type: string?
    inputBinding:
      prefix: --global_arg_2
      position: -1
  flag1:
    type: boolean?
    inputBinding:
      prefix: --flag1
baseCommand: cli.py
arguments:
- subcommand

2: Specify prefix, etc., for global flags in arguments. Note that you can specify prefix, position, etc., in arguments not only in inputs.

inputs:
  global1:
    type: string?
  global2:
    type: string?
  flag1:
    type: boolean?
    inputBinding:
      prefix: --flag1
baseCommand: cli.py
arguments:
- prefix: --global_flag_1
  valueFrom: $(inputs.global1)
- prefix: --global_flag_2
  valueFrom: $(inputs.global2)
- subcommand

3: Specify all positions in inputs (global flags, subcommand, then subcommand flags), use an enum to restrict accepted subcommands:

inputs:
  global1:
    type: string?
    inputBinding:
      position: 1
      prefix: --global_flag_1
  global2:
    type: string?
    inputBinding:
      position: 2
      prefix: --global_flag_2
  subcommand:
    type:
      type: enum
      symbols: [ command1, command2, command3 ]
    default: command1
    inputBinding:
      position: 10
  flag1:
    type: boolean?
    inputBinding:
      position: 21
      prefix: --flag1
baseCommand: cli.py
2 Likes

Great answer, thanks for your help.
I think default should be outdented in the third example, it is a property of the input parameter not of its enum type.

Keywords for anyone arriving here by search: in our case typical global parameters are --loglevel=DEBUG and --verbose

You are right about outdenting default… Thanks for pointing it out. :slight_smile: I have now corrected this in my previous post.

1 Like