nim-confutils/confutils/cli_parser.nim

167 lines
5.0 KiB
Nim
Raw Permalink Normal View History

2024-02-12 03:26:05 +00:00
# confutils
# Copyright (c) 2018-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
# Parts taken from Nim's Runtime Library (c) Copyright 2015 Andreas Rumpf
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*: string ## Key and value pair; the key is the option
## or the argument, and the value is not "" if
## the option was given a value
{.push gcsafe, raises: [].}
func 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)
func 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 = ""
result.val = ""
func handleShortOption(p: var OptParser; cmd: string) =
var i = p.pos
p.kind = cmdShortOption
if i < cmd.len:
2023-02-15 08:26:53 +00:00
add(p.key, 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
2023-02-15 08:26:53 +00:00
card(p.shortNoVal) > 0 and p.key[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 = 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
func 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, 0)
setLen(p.val, 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:
2019-11-08 16:28:04 +00:00
if p.allowWhitespaceAfterColon and i >= p.cmds[p.idx].len and
p.idx + 1 < p.cmds.len and p.cmds[p.idx + 1][0] != '-':
inc p.idx
i = 0
if p.idx < p.cmds.len:
p.val = p.cmds[p.idx].substr(i)
elif len(p.longNoVal) > 0 and p.key notin p.longNoVal and p.idx+1 < p.cmds.len:
p.val = p.cmds[p.idx+1]
inc p.idx
else:
p.val = ""
inc p.idx
p.pos = 0
else:
p.pos = i
handleShortOption(p, p.cmds[p.idx])
else:
p.kind = cmdArgument
p.key = p.cmds[p.idx]
inc p.idx
p.pos = 0
iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: string] =
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: string] =
var p = initOptParser(cmds, shortNoVal=shortNoVal, longNoVal=longNoVal)
while true:
next(p)
if p.kind == cmdEnd: break
yield (p.kind, p.key, p.val)
{.pop.}