Skip to content

Commit 713df7a

Browse files
authored
Fix #19 #52; Evaluate defaultValueDesc and defaultValue in help message (#145)
1 parent f684e55 commit 713df7a

3 files changed

Lines changed: 182 additions & 22 deletions

File tree

confutils.nim

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type
5050
terminalWidth: int
5151
namesWidth: int
5252
flags: set[HelpFlag]
53+
defaultValueDescs: seq[string]
5354

5455
CmdInfo = ref object
5556
name: string
@@ -74,7 +75,6 @@ type
7475
idx: int
7576
flags: set[OptFlag]
7677
hasDefault: bool
77-
defaultInHelpText: string
7878
obsoleteMsg: string
7979
case kind: OptKind
8080
of Discriminator:
@@ -194,6 +194,12 @@ template flushOutputAndQuit(exitCode: int) =
194194
flushOutput
195195
quit exitCode
196196

197+
func defaultValueDesc(appInfo: HelpAppInfo, optIdx: int): string =
198+
if optIdx == -1:
199+
""
200+
else:
201+
appInfo.defaultValueDescs[optIdx]
202+
197203
func helpOptDesc(appInfo: HelpAppInfo): string =
198204
result = "Show this help message and exit"
199205
if appInfo.hasDebugOpts:
@@ -367,7 +373,7 @@ proc describeInvocation(
367373
let cliArg = "<" & arg.name & ">"
368374
helpOutput fgArg, styleBright, cliArg
369375
helpOutput padding(cliArg, appInfo.namesWidth)
370-
help.writeDesc appInfo, arg.desc, arg.defaultInHelpText
376+
help.writeDesc appInfo, arg.desc, appInfo.defaultValueDesc(arg.idx)
371377
help.writeLongDesc appInfo, arg.longDesc
372378
helpOutput "\p"
373379

@@ -410,7 +416,7 @@ proc describeOptionsList(
410416
if opt.desc.len > 0:
411417
help.writeDesc appInfo,
412418
opt.desc.replace("%t", opt.typename),
413-
opt.defaultInHelpText
419+
appInfo.defaultValueDesc(opt.idx)
414420
help.writeLongDesc appInfo, opt.longDesc
415421

416422
helpOutput "\p"
@@ -445,14 +451,16 @@ proc describeOptions(
445451
let helpOpt = OptInfo(
446452
kind: CliSwitch,
447453
name: "help",
448-
desc: helpOptDesc(appInfo)
454+
desc: helpOptDesc(appInfo),
455+
idx: -1
449456
)
450457
describeOptionsList(help, [helpOpt], appInfo, excl)
451458
if appInfo.hasVersion:
452459
let versionOpt = OptInfo(
453460
kind: CliSwitch,
454461
name: "version",
455-
desc: "Show program's version and exit"
462+
desc: "Show program's version and exit",
463+
idx: -1
456464
)
457465
describeOptionsList(help, [versionOpt], appInfo, excl)
458466

@@ -828,6 +836,20 @@ proc generateFieldSetters(RecordType: NimNode): NimNode =
828836
defaultValue = field.readPragma"defaultValue"
829837
completerName = ident($field.name & "Complete")
830838
isFieldDiscriminator = newLit field.isDiscriminator
839+
defaultValueDesc = field.readPragma"defaultValueDesc"
840+
defaultValueHelp =
841+
if defaultValueDesc != nil:
842+
defaultValueDesc
843+
elif defaultValue != nil:
844+
defaultValue
845+
else:
846+
newLit("")
847+
defaultValueHelpRepr =
848+
if defaultValueDesc == nil and defaultValue != nil:
849+
newLit(repr defaultValue)
850+
else:
851+
newLit("")
852+
defaultValueHelpName = ident($field.name & "DefaultValueHelp")
831853

832854
if defaultValue == nil:
833855
defaultValue = newCall(makeDefaultValue, newTree(nnkTypeOfExpr, configField))
@@ -838,9 +860,11 @@ proc generateFieldSetters(RecordType: NimNode): NimNode =
838860

839861
settersArray.add newTree(nnkTupleConstr,
840862
newLit($paramName),
841-
setterName, completerName,
863+
setterName,
864+
completerName,
842865
newCall(bindSym"requiresInput", fixedFieldType),
843-
newCall(bindSym"acceptsMultipleValues", fixedFieldType))
866+
newCall(bindSym"acceptsMultipleValues", fixedFieldType),
867+
defaultValueHelpName)
844868

845869
result.add quote do:
846870
{.push hint[XCannotRaiseY]: off.}
@@ -870,6 +894,22 @@ proc generateFieldSetters(RecordType: NimNode): NimNode =
870894
else:
871895
setField(`configField`, val, `defaultValue`)
872896

897+
proc `defaultValueHelpName`(): string {.
898+
nimcall
899+
gcsafe
900+
sideEffect
901+
raises: []
902+
.} =
903+
# defaultValue can be `config.otherField` which won't compile
904+
# `$` may not be defined for this type
905+
when compiles($`defaultValueHelp`):
906+
when typeof($`defaultValueHelp`) is string:
907+
$`defaultValueHelp`
908+
else:
909+
`defaultValueHelpRepr`
910+
else:
911+
`defaultValueHelpRepr`
912+
873913
result.add quote do:
874914
{.pop.}
875915

@@ -901,11 +941,6 @@ func findPath(parent, node: CmdInfo): seq[CmdInfo] =
901941
doAssert validPath(result, parent, node)
902942
result.add parent
903943

904-
func toText(n: NimNode): string =
905-
if n == nil: ""
906-
elif n.kind in {nnkStrLit..nnkTripleStrLit}: n.strVal
907-
else: repr(n)
908-
909944
func readPragmaFlags(field: FieldDescription): set[OptFlag] =
910945
result = {}
911946
if field.readPragma("hidden") != nil:
@@ -927,10 +962,6 @@ proc cmdInfoFromType(T: NimNode): CmdInfo =
927962
let
928963
isImplicitlySelectable = field.readPragma"implicitlySelectable" != nil
929964
defaultValue = field.readPragma"defaultValue"
930-
defaultValueDesc = field.readPragma"defaultValueDesc"
931-
defaultInHelp = if defaultValueDesc != nil: defaultValueDesc
932-
else: defaultValue
933-
defaultInHelpText = toText(defaultInHelp)
934965
obsoleteMsg = field.readPragma"obsolete"
935966
separator = field.readPragma"separator"
936967
longDesc = field.readPragma"longDesc"
@@ -948,7 +979,6 @@ proc cmdInfoFromType(T: NimNode): CmdInfo =
948979
name: $field.name,
949980
flags: optFlags,
950981
hasDefault: defaultValue != nil,
951-
defaultInHelpText: defaultInHelpText,
952982
typename: field.typ.repr)
953983

954984
if desc != nil: opt.desc = desc.strVal
@@ -1290,19 +1320,27 @@ proc loadImpl[C, SecondarySources](
12901320

12911321
return
12921322

1293-
proc lazyHelpAppInfo(flags: set[HelpFlag]): HelpAppInfo =
1323+
proc lazyHelpAppInfo(flags: set[HelpFlag], defaultValueDescs: seq[string]): HelpAppInfo =
12941324
HelpAppInfo(
12951325
copyrightBanner: copyrightBanner,
12961326
appInvocation: appInvocation(),
12971327
terminalWidth: termWidth,
12981328
hasVersion: version.len > 0,
1299-
flags: flags
1329+
flags: flags,
1330+
defaultValueDescs: defaultValueDescs
13001331
)
13011332

1333+
template getDefaultValueDescs(): seq[string] =
1334+
var defaultValueDescs = newSeq[string](fieldSetters.len)
1335+
for i in 0 ..< fieldSetters.len:
1336+
defaultValueDescs[i] = fieldSetters[i][5]()
1337+
defaultValueDescs
1338+
13021339
template processHelpAndVersionOptions(optKey, optVal: string) =
13031340
let key = optKey
13041341
if cmpIgnoreStyle(key, "help") == 0:
1305-
help.showHelp(lazyHelpAppInfo(optVal.toHelpFlags), activeCmds)
1342+
let defaultValueDescs = getDefaultValueDescs()
1343+
help.showHelp(lazyHelpAppInfo(optVal.toHelpFlags, defaultValueDescs), activeCmds)
13061344
elif version.len > 0 and cmpIgnoreStyle(key, "version") == 0:
13071345
help.helpOutput version, "\p"
13081346
flushOutputAndQuit QuitSuccess

tests/help/snapshots/test_default_value_desc.txt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,18 @@ test_default_value_desc [OPTIONS]...
44

55
The following options are available:
66

7-
--help Show this help message and exit.
8-
--opt1 tcp port [=9000].
7+
--help Show this help message and exit.
8+
--opt1 tcp port [=9000].
9+
--opt2 tcp port 2 [=9000].
10+
--opt3 const default value desc [=ok].
11+
--opt4 literal default value desc [=ok].
12+
--opt5 distinct string [=ok].
13+
--opt6 no dollar func defined [=NoDollar("default")].
14+
--opt7 no dollar func defined with default desc [=ok].
15+
--opt8 runtime value [=ok].
16+
--opt9 runtime value [=ok].
17+
--opt10 runtime value [=ok concat].
18+
--opt11 default value can raise but default desc won't [=ok].
19+
--opt12 default is config.opt11 [=config.opt11].
20+
--opt13 default is config.opt11 with default desc [=ok].
21+
--opt14 default enum is TestEnum.OK [=OK].

tests/help/test_default_value_desc.nim

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,46 @@
77
# This file may not be copied, modified, or distributed except according to
88
# those terms.
99

10+
import os
1011
import ../../confutils
1112

1213
const defaultEth2TcpPort = 9000
14+
const defaultDescOverride = "ok"
15+
16+
type TestEnum {.pure.} = enum
17+
BAD
18+
OK
19+
20+
type DisString = distinct string
21+
type NoDollar = distinct string
22+
23+
proc `$`(s: DisString): string =
24+
# this should show in the help message
25+
"ok"
26+
27+
proc completeCmdArg(T: type DisString, val: string): seq[string] =
28+
completeCmdArg(string, val)
29+
30+
proc parseCmdArg(T: type DisString, s: string): T =
31+
parseCmdArg(string, s).DisString
32+
33+
proc completeCmdArg(T: type NoDollar, val: string): seq[string] =
34+
completeCmdArg(string, val)
35+
36+
proc parseCmdArg(T: type NoDollar, s: string): T =
37+
parseCmdArg(string, s).NoDollar
38+
39+
proc runtimeVal(): string =
40+
# do something that's not available at comptime
41+
{.cast(raises: []).}:
42+
discard getCurrentProcessId()
43+
"ok"
44+
45+
proc canRaise(): string {.raises: [ValueError].} =
46+
# do something that's not available at comptime
47+
if false:
48+
raise (ref ValueError)(msg: "never raised")
49+
"bad"
1350

1451
type
1552
TestConf = object
@@ -19,4 +56,76 @@ type
1956
desc: "tcp port"
2057
name: "opt1" }: int
2158

59+
opt2 {.
60+
defaultValue: defaultEth2TcpPort
61+
desc: "tcp port 2"
62+
name: "opt2" }: int
63+
64+
opt3 {.
65+
defaultValue: "bad"
66+
defaultValueDesc: defaultDescOverride
67+
desc: "const default value desc"
68+
name: "opt3" }: string
69+
70+
opt4 {.
71+
defaultValue: "bad"
72+
defaultValueDesc: "ok"
73+
desc: "literal default value desc"
74+
name: "opt4" }: string
75+
76+
opt5 {.
77+
defaultValue: DisString("bad")
78+
desc: "distinct string"
79+
name: "opt5" }: DisString
80+
81+
opt6 {.
82+
defaultValue: NoDollar("default")
83+
desc: "no dollar func defined"
84+
name: "opt6" }: NoDollar
85+
86+
opt7 {.
87+
defaultValue: NoDollar("bad")
88+
defaultValueDesc: "ok"
89+
desc: "no dollar func defined with default desc"
90+
name: "opt7" }: NoDollar
91+
92+
opt8 {.
93+
defaultValue: runtimeVal()
94+
desc: "runtime value"
95+
name: "opt8" }: string
96+
97+
opt9 {.
98+
defaultValue: "bad"
99+
defaultValueDesc: runtimeVal()
100+
desc: "runtime value"
101+
name: "opt9" }: string
102+
103+
opt10 {.
104+
defaultValue: "bad"
105+
defaultValueDesc: runtimeVal() & " concat"
106+
desc: "runtime value"
107+
name: "opt10" }: string
108+
109+
opt11 {.
110+
defaultValue: canRaise()
111+
defaultValueDesc: "ok"
112+
desc: "default value can raise but default desc won't"
113+
name: "opt11" }: string
114+
115+
opt12 {.
116+
defaultValue: config.opt11
117+
desc: "default is config.opt11"
118+
name: "opt12" }: string
119+
120+
opt13 {.
121+
defaultValue: config.opt11
122+
defaultValueDesc: "ok"
123+
desc: "default is config.opt11 with default desc"
124+
name: "opt13" }: string
125+
126+
opt14 {.
127+
defaultValue: TestEnum.OK
128+
desc: "default enum is TestEnum.OK"
129+
name: "opt14" }: TestEnum
130+
22131
let c = TestConf.load(termWidth = int.high)

0 commit comments

Comments
 (0)