Some steps towards enabling the use of confutils in nimscript
So far, a show-stopped Nim bug was discovered: https://github.com/nim-lang/Nim/issues/11502
This commit is contained in:
parent
da24be1a9d
commit
30309748a0
154
confutils.nim
154
confutils.nim
|
@ -1,10 +1,13 @@
|
|||
import
|
||||
os, parseopt, strutils, options, std_shims/macros_shim, typetraits, terminal,
|
||||
confutils/defs
|
||||
strutils, options, std_shims/macros_shim, typetraits,
|
||||
confutils/[defs, cli_parser]
|
||||
|
||||
export
|
||||
defs
|
||||
|
||||
when not defined(nimscript):
|
||||
import os, terminal
|
||||
|
||||
type
|
||||
CommandDesc = object
|
||||
name: string
|
||||
|
@ -21,29 +24,65 @@ type
|
|||
fieldIdx: int
|
||||
desc: string
|
||||
|
||||
template appName: string =
|
||||
getAppFilename().splitFile.name
|
||||
CommandPtr = ptr CommandDesc
|
||||
OptionPtr = ptr OptionDesc
|
||||
|
||||
when not defined(confutils_no_colors):
|
||||
when defined(nimscript):
|
||||
proc appInvocation: string =
|
||||
"nim " & (if paramCount() > 1: paramStr(1) else: "<nims-script>")
|
||||
|
||||
type stderr = object
|
||||
|
||||
template writeLine(T: type stderr, msg: string) =
|
||||
echo msg
|
||||
|
||||
proc commandLineParams(): seq[string] =
|
||||
for i in 2 .. paramCount():
|
||||
result.add paramStr(i)
|
||||
|
||||
# TODO: Why isn't this available in NimScript?
|
||||
proc getCurrentExceptionMsg(): string =
|
||||
""
|
||||
|
||||
else:
|
||||
template appInvocation: string =
|
||||
getAppFilename().splitFile.name
|
||||
|
||||
when defined(nimscript):
|
||||
const styleBright = ""
|
||||
|
||||
# Deal with the issue that `stdout` is not defined in nimscript
|
||||
var buffer = ""
|
||||
proc write(args: varargs[string, `$`]) =
|
||||
for arg in args:
|
||||
buffer.add arg
|
||||
if args[^1][^1] == '\n':
|
||||
buffer.setLen(buffer.len - 1)
|
||||
echo buffer
|
||||
buffer = ""
|
||||
|
||||
elif not defined(confutils_no_colors):
|
||||
template write(args: varargs[untyped]) =
|
||||
stdout.styledWrite(args)
|
||||
|
||||
else:
|
||||
const
|
||||
styleBright = ""
|
||||
const styleBright = ""
|
||||
|
||||
template write(args: varargs[untyped]) =
|
||||
stdout.write(args)
|
||||
|
||||
when defined(debugCmdTree):
|
||||
proc printCmdTree(cmd: CommandDesc, indent = 0) =
|
||||
let blanks = repeat(' ', indent)
|
||||
echo blanks, "> ", cmd.name
|
||||
for opt in cmd.options:
|
||||
echo blanks, " - ", opt.name, ": ", opt.typename
|
||||
for subcmd in cmd.subCommands:
|
||||
printCmdTree(subcmd, indent + 2)
|
||||
else:
|
||||
template printCmdTree(cmd: CommandDesc) = discard
|
||||
template hasArguments(cmd: CommandPtr): bool =
|
||||
cmd.argumentsFieldIdx != -1
|
||||
|
||||
template isSubCommand(cmd: CommandPtr): bool =
|
||||
cmd.name.len > 0
|
||||
|
||||
proc noMoreArgumentsError(cmd: CommandPtr): string =
|
||||
result = if cmd.isSubCommand: "The command '$1'" % [cmd.name]
|
||||
else: appInvocation()
|
||||
result.add " does not accept"
|
||||
if cmd.hasArguments: result.add " additional"
|
||||
result.add " arguments"
|
||||
|
||||
proc describeCmdOptions(cmd: CommandDesc) =
|
||||
for opt in cmd.options:
|
||||
|
@ -53,7 +92,7 @@ proc describeCmdOptions(cmd: CommandDesc) =
|
|||
write "\n"
|
||||
|
||||
proc showHelp(version: string, cmd: CommandDesc) =
|
||||
let app = appName
|
||||
let app = appInvocation()
|
||||
|
||||
write "Usage: ", styleBright, app
|
||||
if cmd.name.len > 0: write " ", cmd.name
|
||||
|
@ -78,6 +117,33 @@ proc showHelp(version: string, cmd: CommandDesc) =
|
|||
write "\n"
|
||||
quit(0)
|
||||
|
||||
proc findOption(cmds: seq[CommandPtr], name: TaintedString): OptionPtr =
|
||||
for i in countdown(cmds.len - 1, 0):
|
||||
for o in cmds[i].options.mitems:
|
||||
if cmpIgnoreStyle(o.name, string(name)) == 0 or
|
||||
cmpIgnoreStyle(o.shortform, string(name)) == 0:
|
||||
return addr(o)
|
||||
|
||||
return nil
|
||||
|
||||
proc findSubcommand(cmd: CommandPtr, name: TaintedString): CommandPtr =
|
||||
for subCmd in cmd.subCommands.mitems:
|
||||
if cmpIgnoreStyle(subCmd.name, string(name)) == 0:
|
||||
return addr(subCmd)
|
||||
|
||||
return nil
|
||||
|
||||
when defined(debugCmdTree):
|
||||
proc printCmdTree(cmd: CommandDesc, indent = 0) =
|
||||
let blanks = repeat(' ', indent)
|
||||
echo blanks, "> ", cmd.name
|
||||
for opt in cmd.options:
|
||||
echo blanks, " - ", opt.name, ": ", opt.typename
|
||||
for subcmd in cmd.subCommands:
|
||||
printCmdTree(subcmd, indent + 2)
|
||||
else:
|
||||
template printCmdTree(cmd: CommandDesc) = discard
|
||||
|
||||
# TODO remove the overloads here to get better "missing overload" error message
|
||||
proc parseCmdArg*(T: type InputDir, p: TaintedString): T =
|
||||
if not dirExists(p.string):
|
||||
|
@ -92,11 +158,12 @@ proc parseCmdArg*(T: type InputFile, p: TaintedString): T =
|
|||
if not fileExists(p.string):
|
||||
raise newException(ValueError, "File doesn't exist")
|
||||
|
||||
try:
|
||||
let f = open(p.string, fmRead)
|
||||
close f
|
||||
except IOError:
|
||||
raise newException(ValueError, "File not accessible")
|
||||
when not defined(nimscript):
|
||||
try:
|
||||
let f = open(p.string, fmRead)
|
||||
close f
|
||||
except IOError:
|
||||
raise newException(ValueError, "File not accessible")
|
||||
|
||||
result = T(p.string)
|
||||
|
||||
|
@ -108,11 +175,12 @@ proc parseCmdArg*(T: type TypedInputFile, p: TaintedString): T =
|
|||
if not fileExists(path):
|
||||
raise newException(ValueError, "File doesn't exist")
|
||||
|
||||
try:
|
||||
let f = open(path, fmRead)
|
||||
close f
|
||||
except IOError:
|
||||
raise newException(ValueError, "File not accessible")
|
||||
when not defined(nimscript):
|
||||
try:
|
||||
let f = open(path, fmRead)
|
||||
close f
|
||||
except IOError:
|
||||
raise newException(ValueError, "File not accessible")
|
||||
|
||||
result = T(path)
|
||||
|
||||
|
@ -317,27 +385,11 @@ proc load*(Configuration: type,
|
|||
proc fail(msg: string) =
|
||||
if quitOnFailure:
|
||||
stderr.writeLine(msg)
|
||||
stderr.writeLine("Try '$1 --help' for more information" % appName)
|
||||
stderr.writeLine("Try '$1 --help' for more information" % appInvocation())
|
||||
quit 1
|
||||
else:
|
||||
raise newException(ConfigurationError, msg)
|
||||
|
||||
proc findOption(cmds: seq[ptr CommandDesc], name: TaintedString): ptr OptionDesc =
|
||||
for i in countdown(cmds.len - 1, 0):
|
||||
for o in cmds[i].options.mitems:
|
||||
if cmpIgnoreStyle(o.name, string(name)) == 0 or
|
||||
cmpIgnoreStyle(o.shortform, string(name)) == 0:
|
||||
return addr(o)
|
||||
|
||||
return nil
|
||||
|
||||
proc findSubcommand(cmd: ptr CommandDesc, name: TaintedString): ptr CommandDesc =
|
||||
for subCmd in cmd.subCommands.mitems:
|
||||
if cmpIgnoreStyle(subCmd.name, string(name)) == 0:
|
||||
return addr(subCmd)
|
||||
|
||||
return nil
|
||||
|
||||
template applySetter(setterIdx: int, cmdLineVal: TaintedString): bool =
|
||||
var r: bool
|
||||
try:
|
||||
|
@ -350,7 +402,7 @@ proc load*(Configuration: type,
|
|||
template required(opt: OptionDesc): bool =
|
||||
fieldSetters[opt.fieldIdx][2] and not opt.hasDefault
|
||||
|
||||
proc processMissingOptions(conf: var Configuration, cmd: ptr CommandDesc) =
|
||||
proc processMissingOptions(conf: var Configuration, cmd: CommandPtr) =
|
||||
for o in cmd.options:
|
||||
if o.rejectNext == false:
|
||||
if o.required:
|
||||
|
@ -358,12 +410,12 @@ proc load*(Configuration: type,
|
|||
elif o.hasDefault:
|
||||
discard fieldSetters[o.fieldIdx][1](conf, TaintedString(""))
|
||||
|
||||
template activateCmd(activatedCmd: ptr CommandDesc, key: TaintedString) =
|
||||
template activateCmd(activatedCmd: CommandPtr, key: TaintedString) =
|
||||
let cmd = activatedCmd
|
||||
discard applySetter(cmd.fieldIdx, key)
|
||||
lastCmd.defaultSubCommand = -1
|
||||
activeCmds.add cmd
|
||||
rejectNextArgument = cmd.argumentsFieldIdx == -1
|
||||
rejectNextArgument = not cmd.hasArguments
|
||||
|
||||
for kind, key, val in getopt(cmdLine):
|
||||
case kind
|
||||
|
@ -397,7 +449,8 @@ proc load*(Configuration: type,
|
|||
activateCmd(subCmd, key)
|
||||
else:
|
||||
if rejectNextArgument:
|
||||
fail "The command '$1' does not accept additional arguments" % [lastCmd.name]
|
||||
fail lastCmd.noMoreArgumentsError
|
||||
|
||||
let argumentIdx = lastCmd.argumentsFieldIdx
|
||||
doAssert argumentIdx != -1
|
||||
rejectNextArgument = applySetter(argumentIdx, key)
|
||||
|
@ -415,8 +468,7 @@ proc defaults*(Configuration: type): Configuration =
|
|||
|
||||
proc dispatchImpl(cliProcSym, cliArgs, loadArgs: NimNode): NimNode =
|
||||
# Here, we'll create a configuration object with fields matching
|
||||
# the CLI proc params. We'll also generate a call to the designated
|
||||
# p
|
||||
# the CLI proc params. We'll also generate a call to the designated proc
|
||||
let configType = genSym(nskType, "CliConfig")
|
||||
let configFields = newTree(nnkRecList)
|
||||
let configVar = genSym(nskLet, "config")
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
# Copyright 2018 Status Research & Development GmbH
|
||||
# Parts taken from Nim's Runtime Library (c) Copyright 2015 Andreas Rumpf
|
||||
|
||||
import
|
||||
strutils
|
||||
|
||||
type
|
||||
CmdLineKind* = enum ## The detected command line token.
|
||||
cmdEnd, ## End of command line reached
|
||||
cmdArgument, ## An argument such as a filename
|
||||
cmdLongOption, ## A long option such as --option
|
||||
cmdShortOption ## A short option such as -c
|
||||
|
||||
OptParser* = object of RootObj ## Implementation of the command line parser.
|
||||
pos*: int
|
||||
inShortState: bool
|
||||
allowWhitespaceAfterColon: bool
|
||||
shortNoVal: set[char]
|
||||
longNoVal: seq[string]
|
||||
cmds: seq[string]
|
||||
idx: int
|
||||
kind*: CmdLineKind ## The detected command line token
|
||||
key*, val*: TaintedString ## Key and value pair; the key is the option
|
||||
## or the argument, and the value is not "" if
|
||||
## the option was given a value
|
||||
|
||||
proc parseWord(s: string, i: int, w: var string,
|
||||
delim: set[char] = {'\t', ' '}): int =
|
||||
result = i
|
||||
if result < s.len and s[result] == '\"':
|
||||
inc(result)
|
||||
while result < s.len:
|
||||
if s[result] == '"':
|
||||
inc result
|
||||
break
|
||||
add(w, s[result])
|
||||
inc(result)
|
||||
else:
|
||||
while result < s.len and s[result] notin delim:
|
||||
add(w, s[result])
|
||||
inc(result)
|
||||
|
||||
proc initOptParser*(cmds: seq[string], shortNoVal: set[char]={},
|
||||
longNoVal: seq[string] = @[];
|
||||
allowWhitespaceAfterColon = true): OptParser =
|
||||
result.pos = 0
|
||||
result.idx = 0
|
||||
result.inShortState = false
|
||||
result.shortNoVal = shortNoVal
|
||||
result.longNoVal = longNoVal
|
||||
result.allowWhitespaceAfterColon = allowWhitespaceAfterColon
|
||||
result.cmds = cmds
|
||||
result.kind = cmdEnd
|
||||
result.key = TaintedString""
|
||||
result.val = TaintedString""
|
||||
|
||||
proc handleShortOption(p: var OptParser; cmd: string) =
|
||||
var i = p.pos
|
||||
p.kind = cmdShortOption
|
||||
if i < cmd.len:
|
||||
add(p.key.string, cmd[i])
|
||||
inc(i)
|
||||
p.inShortState = true
|
||||
while i < cmd.len and cmd[i] in {'\t', ' '}:
|
||||
inc(i)
|
||||
p.inShortState = false
|
||||
if i < cmd.len and cmd[i] in {':', '='} or
|
||||
card(p.shortNoVal) > 0 and p.key.string[0] notin p.shortNoVal:
|
||||
if i < cmd.len and cmd[i] in {':', '='}:
|
||||
inc(i)
|
||||
p.inShortState = false
|
||||
while i < cmd.len and cmd[i] in {'\t', ' '}: inc(i)
|
||||
p.val = TaintedString substr(cmd, i)
|
||||
p.pos = 0
|
||||
inc p.idx
|
||||
else:
|
||||
p.pos = i
|
||||
if i >= cmd.len:
|
||||
p.inShortState = false
|
||||
p.pos = 0
|
||||
inc p.idx
|
||||
|
||||
proc next*(p: var OptParser) =
|
||||
## Parses the next token.
|
||||
##
|
||||
## ``p.kind`` describes what kind of token has been parsed. ``p.key`` and
|
||||
## ``p.val`` are set accordingly.
|
||||
if p.idx >= p.cmds.len:
|
||||
p.kind = cmdEnd
|
||||
return
|
||||
|
||||
var i = p.pos
|
||||
while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i)
|
||||
p.pos = i
|
||||
setLen(p.key.string, 0)
|
||||
setLen(p.val.string, 0)
|
||||
if p.inShortState:
|
||||
p.inShortState = false
|
||||
if i >= p.cmds[p.idx].len:
|
||||
inc(p.idx)
|
||||
p.pos = 0
|
||||
if p.idx >= p.cmds.len:
|
||||
p.kind = cmdEnd
|
||||
return
|
||||
else:
|
||||
handleShortOption(p, p.cmds[p.idx])
|
||||
return
|
||||
|
||||
if i < p.cmds[p.idx].len and p.cmds[p.idx][i] == '-':
|
||||
inc(i)
|
||||
if i < p.cmds[p.idx].len and p.cmds[p.idx][i] == '-':
|
||||
p.kind = cmdLongOption
|
||||
inc(i)
|
||||
i = parseWord(p.cmds[p.idx], i, p.key, {' ', '\t', ':', '='})
|
||||
while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i)
|
||||
if i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {':', '='}:
|
||||
inc(i)
|
||||
while i < p.cmds[p.idx].len and p.cmds[p.idx][i] in {'\t', ' '}: inc(i)
|
||||
# if we're at the end, use the next command line option:
|
||||
if i >= p.cmds[p.idx].len and p.idx < p.cmds.len and p.allowWhitespaceAfterColon:
|
||||
inc p.idx
|
||||
i = 0
|
||||
if p.idx < p.cmds.len:
|
||||
p.val = TaintedString p.cmds[p.idx].substr(i)
|
||||
elif len(p.longNoVal) > 0 and p.key.string notin p.longNoVal and p.idx+1 < p.cmds.len:
|
||||
p.val = TaintedString p.cmds[p.idx+1]
|
||||
inc p.idx
|
||||
else:
|
||||
p.val = TaintedString""
|
||||
inc p.idx
|
||||
p.pos = 0
|
||||
else:
|
||||
p.pos = i
|
||||
handleShortOption(p, p.cmds[p.idx])
|
||||
else:
|
||||
p.kind = cmdArgument
|
||||
p.key = TaintedString p.cmds[p.idx]
|
||||
inc p.idx
|
||||
p.pos = 0
|
||||
|
||||
iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedString] =
|
||||
p.pos = 0
|
||||
p.idx = 0
|
||||
while true:
|
||||
next(p)
|
||||
if p.kind == cmdEnd: break
|
||||
yield (p.kind, p.key, p.val)
|
||||
|
||||
iterator getopt*(cmds: seq[string],
|
||||
shortNoVal: set[char]={}, longNoVal: seq[string] = @[]):
|
||||
tuple[kind: CmdLineKind, key, val: TaintedString] =
|
||||
var p = initOptParser(cmds, shortNoVal=shortNoVal, longNoVal=longNoVal)
|
||||
while true:
|
||||
next(p)
|
||||
if p.kind == cmdEnd: break
|
||||
yield (p.kind, p.key, p.val)
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
import os
|
||||
|
||||
type
|
||||
ConfigurationError* = object of CatchableError
|
||||
|
||||
|
|
Loading…
Reference in New Issue