bli
bli

Reputation: 8184

Testing if a docopt command-line option is set in nim

I'm trying to write a nim program that can read either from the standard input or from a file given as a command-line option. I use docopt to parse the command line.

import docopt

const doc = """
This program takes input from a file or from stdin.

Usage:
  testinput [-i <filename> | --input <filename>]

-h --help              Show this help message and exit.
-i --input <filename>  File to use as input.
"""

when isMainModule:
  let args = docopt(doc)
  var inFilename: string
  for opt, val in args.pairs():
    case opt
    of "-i", "--input":
      inFilename = $args[opt]
    else:
      echo "Unknown option" & opt
      quit(QuitFailure)
  let inputSource =
    if inFilename.isNil:
      stdin
    else:
      echo "We have inFilename: " & inFilename
      open(inFilename)

The program compiles.

It doesn't crash when I give it a file on the command line:

$ ./testinput -i testinput.nim
We have inFilename: testinput.nim

But I get an IOError if I try to feed it from its stdin:

$ ./testinput < testinput.nim
We have inFilename: nil
testinput.nim(28)        testinput
system.nim(2833)         sysFatal
Error: unhandled exception: cannot open: nil [IOError]

How come inFilename.isNil is false, and yet the execution of the else branch tells me that inFilename "is" nil?

Is there a correct and elegant way to do this, using docopt?

Upvotes: 1

Views: 295

Answers (2)

bli
bli

Reputation: 8184

Instead of transforming the value of the option into a string with $, one can keep it as a Value, which is the type returned by docopt.

According to the documentation:

vkNone (No Value)

This kind of Value appears when there is an option which hasn't been set and has no default. It is false when converted toBool

One can apparently use the value of the option in a boolean expression, and it seems to be automatically interpreted as a bool:

import docopt

const doc = """
This program takes input from a file or from stdin.

Usage:
  testinput [-i <filename> | --input <filename>]

-h --help              Show this help message and exit.
-i --input <filename>  File to use as input.
"""

when isMainModule:
  let args = docopt(doc)
  var inFilename: Value
  for opt, val in args.pairs():
    case opt
    of "-i", "--input":
      inFilename = val
    else:
      echo "Unknown option" & opt
      quit(QuitFailure)
  let inputSource =
    if not bool(inFilename):
      stdin
    else:
      echo "We have inFilename: " & $inFilename
      open($inFilename)

Another usage of this behaviour is given in this other anwser, and avoids setting the variable, therefore keeping it nil.

Upvotes: 1

Andrew Penkrat
Andrew Penkrat

Reputation: 457

I'm not familiar with docopt, but it seems to create an entry for each option in the doc, not for the options specified by user so your code's been getting args == {"--input": nil} and stringifying the nil.

The following will work correctly:

import docopt

const doc = """
This program takes input from a file or from stdin.

Usage:
  testinput [-i <filename> | --input <filename>]

-h --help              Show this help message and exit.
-i --input <filename>  File to use as input.
"""

when isMainModule:
  let args = docopt(doc)
  var inFilename: string
  if args["--input"]:
    inFilename = $args["--input"]
  if not inFilename.isNil:
    echo "We have inFilename: " & inFilename
  let inputSource =
    if inFilename.isNil:
      stdin
    else:
      open(inFilename)

Also note that you don't have to check for "-i" option as docopt knows it's an alias to "--input".

Upvotes: 1

Related Questions