Skip to content

Commit 66fd952

Browse files
committed
Fix #112; add results.Opt support
1 parent bede84f commit 66fd952

6 files changed

Lines changed: 112 additions & 24 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ template required* {.pragma.}
269269
```
270270

271271
By default, all options without default values are considered required.
272-
An exception to this rule are all `seq[T]` or `Option[T]` options for
272+
An exception to this rule are all `seq[T]` or `Opt[T]` or `Option[T]` options for
273273
which the "empty" value can be considered a reasonable default. You can
274274
also extend this behavior to other user-defined types by providing the
275275
following overloads:

confutils.nim

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
import
1313
os,
1414
std/[enumutils, options, strutils, wordwrap],
15+
results,
1516
stew/shims/macros,
1617
confutils/[defs, cli_parser, config_file]
1718

1819
export
19-
options, defs, config_file
20+
options, results, defs, config_file
2021

2122
const
2223
hasSerialization = not defined(nimscript)
@@ -633,6 +634,9 @@ proc parseCmdArg*[T](
633634
_: type Option[T], s: string): Option[T] {.raises: [ValueError].} =
634635
some(parseCmdArg(T, s))
635636

637+
proc parseCmdArg*[T](_: type Opt[T], s: string): Opt[T] {.raises: [ValueError].} =
638+
Opt.some(parseCmdArg(T, s))
639+
636640
template parseCmdArg*(T: type string, s: string): string =
637641
s
638642

@@ -734,29 +738,33 @@ proc completeCmdArg*[T](_: type Option[T], val: string): seq[string] =
734738
mixin completeCmdArg
735739
return completeCmdArg(type(T), val)
736740

741+
proc completeCmdArg*[T](_: type Opt[T], val: string): seq[string] =
742+
mixin completeCmdArg
743+
return completeCmdArg(type(T), val)
744+
737745
proc completeCmdArgAux(T: type, val: string): seq[string] =
738746
mixin completeCmdArg
739747
return completeCmdArg(T, val)
740748

741-
template setField[T](
742-
loc: var T, val: Option[string], defaultVal: untyped): untyped =
749+
template setField[T](loc: var T, val: Opt[string], defaultVal: untyped): untyped =
743750
type FieldType = type(loc)
744-
loc = if isSome(val): parseCmdArgAux(FieldType, val.get)
745-
else: FieldType(defaultVal)
751+
loc = if val.isOk:
752+
parseCmdArgAux(FieldType, val.get)
753+
else:
754+
FieldType(defaultVal)
746755

747-
template setField[T](
748-
loc: var seq[T], val: Option[string], defaultVal: untyped): untyped =
749-
if val.isSome:
756+
template setField[T](loc: var seq[T], val: Opt[string], defaultVal: untyped): untyped =
757+
type FieldType = type(loc)
758+
if val.isOk:
750759
loc.add parseCmdArgAux(type(loc[0]), val.get)
751760
else:
752-
type FieldType = type(loc)
753761
loc = FieldType(defaultVal)
754762

755763
func makeDefaultValue*(T: type): T =
756764
default(T)
757765

758766
func requiresInput*(T: type): bool =
759-
not ((T is seq) or (T is Option) or (T is bool))
767+
not ((T is seq) or (T is Option) or (T is Opt) or (T is bool))
760768

761769
func acceptsMultipleValues*(T: type): bool =
762770
T is seq
@@ -811,7 +819,7 @@ proc generateFieldSetters(RecordType: NimNode): NimNode =
811819
.} =
812820
return completeCmdArgAux(`fixedFieldType`, val)
813821

814-
proc `setterName`(`configVar`: var `RecordType`, val: Option[string]) {.
822+
proc `setterName`(`configVar`: var `RecordType`, val: Opt[string]) {.
815823
nimcall
816824
gcsafe
817825
sideEffect
@@ -1070,7 +1078,7 @@ proc loadImpl[C, SecondarySources](
10701078
{.push warning[BareExcept]:off.}
10711079

10721080
try:
1073-
fieldSetters[setterIdx][1](conf, some(cmdLineVal))
1081+
fieldSetters[setterIdx][1](conf, Opt.some(cmdLineVal))
10741082
inc fieldCounters[setterIdx]
10751083
except:
10761084
fail("Error while processing the ",
@@ -1293,7 +1301,7 @@ proc loadImpl[C, SecondarySources](
12931301
# there is nothing left to do here.
12941302
discard
12951303
elif opt.hasDefault:
1296-
fieldSetters[opt.idx][1](conf, none[string]())
1304+
fieldSetters[opt.idx][1](conf, Opt.none(string))
12971305
elif opt.required:
12981306
fail "The required option '" & opt.name & "' was not specified"
12991307
except ValueError as err:

confutils.nimble

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ skipDirs = @["tests"]
1919

2020
requires "nim >= 1.6.0",
2121
"stew",
22-
"serialization"
22+
"serialization",
23+
"results"
2324

2425
let nimc = getEnv("NIMC", "nim") # Which nim compiler to use
2526
let lang = getEnv("NIMLANG", "c") # Which backend (c/cpp/js)
@@ -44,6 +45,7 @@ task test, "Run all tests":
4445
for threads in ["--threads:off", "--threads:on"]:
4546
run threads, "tests/test_all"
4647
build threads, "tests/test_duplicates"
48+
run threads, "confutils/shell_completion"
4749

4850
#Also iterate over every test in tests/fail, and verify they fail to compile.
4951
echo "\r\nTest Fail to Compile:"

confutils/shell_completion.nim

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@
88
# those terms.
99

1010
## A simple lexer meant to tokenize an input string as a shell would do.
11-
import lexbase
12-
import options
13-
import streams
14-
import os
15-
import strutils
11+
import
12+
std/[lexbase, streams, os, strutils],
13+
results
1614

1715
type
1816
ShellLexer = object of BaseLexer
@@ -70,7 +68,7 @@ proc parseQuoted(l: var ShellLexer,
7068
inc(pos)
7169
return pos
7270

73-
proc getTok(l: var ShellLexer): Option[string] {.gcsafe, raises: [IOError, OSError].} =
71+
proc getTok(l: var ShellLexer): Opt[string] {.gcsafe, raises: [IOError, OSError].} =
7472
var pos = l.bufpos
7573

7674
# Skip the initial whitespace
@@ -87,8 +85,8 @@ proc getTok(l: var ShellLexer): Option[string] {.gcsafe, raises: [IOError, OSErr
8785
# to find out if the string ends with whitespace.
8886
if l.preserveTrailingWs and l.bufpos != pos:
8987
l.bufpos = pos
90-
return some("")
91-
return none(string)
88+
return Opt.some("")
89+
return Opt.none(string)
9290
of ' ', '\t':
9391
inc(pos)
9492
else:
@@ -133,7 +131,7 @@ proc getTok(l: var ShellLexer): Option[string] {.gcsafe, raises: [IOError, OSErr
133131
break
134132

135133
l.bufpos = pos
136-
return some(tokLit)
134+
return Opt.some(tokLit)
137135

138136
proc splitCompletionLine*(): seq[string] =
139137
let comp_line = os.getEnv("COMP_LINE")

tests/test_all.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import
1818
test_parsecmdarg,
1919
test_pragma,
2020
test_qualified_ident,
21+
test_results_opt,
2122
test_help
2223

2324
when defined(windows):

tests/test_results_opt.nim

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# confutils
2+
# Copyright (c) 2018-2025 Status Research & Development GmbH
3+
# Licensed under either of
4+
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
5+
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
6+
# at your option.
7+
# This file may not be copied, modified, or distributed except according to
8+
# those terms.
9+
10+
import unittest2, ../confutils
11+
12+
type CustomString = distinct string
13+
14+
proc `==`(a, b: CustomString): bool =
15+
a.string == b.string
16+
17+
proc parseCmdArg(T: type CustomString, s: string): T {.raises: [ValueError].} =
18+
T(s & "_cs_overridden")
19+
20+
func completeCmdArg(T: type CustomString, val: string): seq[string] =
21+
@[]
22+
23+
type
24+
Lvl1Cmd = enum
25+
lvl1Cmd1
26+
27+
Lvl2Cmd = enum
28+
lvl2Cmd1
29+
30+
TestConf = object
31+
opt1 {.
32+
desc: "opt1 desc"
33+
name: "opt1" }: Opt[string]
34+
opt2 {.
35+
desc: "opt2 desc"
36+
name: "opt2" }: Opt[CustomString]
37+
38+
case cmd {.command.}: Lvl1Cmd
39+
of Lvl1Cmd.lvl1Cmd1:
40+
lvl1Opt1 {.
41+
desc: "lvl1Opt1 desc"
42+
name: "lvl1-opt1" }: Opt[int]
43+
44+
case cmd2 {.command.}: Lvl2Cmd
45+
of Lvl2Cmd.lvl2Cmd1:
46+
lvl2Opt1 {.
47+
desc: "lvl2Opt1 desc"
48+
name: "lvl2-opt1" }: Opt[int]
49+
50+
suite "test results Opt":
51+
test "defaults":
52+
let conf = TestConf.load(cmdLine = @[
53+
"lvl1Cmd1",
54+
"lvl2Cmd1"
55+
])
56+
check:
57+
conf.cmd == Lvl1Cmd.lvl1Cmd1
58+
conf.cmd2 == Lvl2Cmd.lvl2Cmd1
59+
not conf.opt1.isOk
60+
not conf.opt2.isOk
61+
not conf.lvl1Opt1.isOk
62+
not conf.lvl2Opt1.isOk
63+
64+
test "all opts":
65+
let conf = TestConf.load(cmdLine = @[
66+
"--opt1=foo",
67+
"--opt2=bar",
68+
"lvl1Cmd1",
69+
"--lvl1-opt1=123",
70+
"lvl2Cmd1",
71+
"--lvl2-opt1=456"
72+
])
73+
check:
74+
conf.cmd == Lvl1Cmd.lvl1Cmd1
75+
conf.cmd2 == Lvl2Cmd.lvl2Cmd1
76+
conf.opt1.get == "foo"
77+
conf.opt2.get == "bar_cs_overridden".CustomString
78+
conf.lvl1Opt1.get == 123
79+
conf.lvl2Opt1.get == 456

0 commit comments

Comments
 (0)