Reputation: 8184
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
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
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