Skip to content

Commit 3e5b9f0

Browse files
committed
wip
1 parent fd40a96 commit 3e5b9f0

File tree

10 files changed

+682
-62
lines changed

10 files changed

+682
-62
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ vendor/
77
tests/test_all
88
tests/test_nested_cmd
99
tests/test_help
10+
tests/test_flatten_pragma

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,15 @@ Network Options: # this is a separator
311311
-c, --opt3 desc
312312
```
313313

314+
-----------------
315+
316+
```nim
317+
template flatten* {.pragma.}
318+
```
319+
320+
Apply it to an object field to traverse the object options as if they were "top-level".
321+
This allows the object options to be reused in various configurations.
322+
314323
## Configuration field types
315324

316325
The `confutils/defs` module provides a number of types frequently used

confutils.nim

Lines changed: 105 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import
1313
os,
1414
std/[enumutils, options, strutils, wordwrap],
1515
stew/shims/macros,
16-
confutils/[defs, cli_parser, config_file]
16+
confutils/[defs, cli_parser, config_file, utils]
1717

1818
export
1919
options, defs, config_file
@@ -693,24 +693,83 @@ template debugMacroResult(macroName: string) {.dirty.} =
693693
echo "\n-------- ", macroName, " ----------------------"
694694
echo result.repr
695695

696+
type
697+
ConfFieldDescRef = ref ConfFieldDesc
698+
ConfFieldDesc = object
699+
field: FieldDescription
700+
parent: ConfFieldDescRef
701+
702+
proc newConfFieldDesc(
703+
field: FieldDescription, parent: ConfFieldDescRef
704+
): ConfFieldDescRef =
705+
ConfFieldDescRef(field: field, parent: parent)
706+
707+
proc fieldCaseBranch(cf: ConfFieldDesc): NimNode =
708+
if cf.field.caseBranch != nil:
709+
cf.field.caseBranch
710+
elif cf.parent != nil:
711+
fieldCaseBranch(cf.parent[])
712+
else:
713+
nil
714+
715+
proc fieldCaseField(cf: ConfFieldDesc): NimNode =
716+
if cf.field.caseField != nil:
717+
cf.field.caseField
718+
elif cf.parent != nil:
719+
fieldCaseField(cf.parent[])
720+
else:
721+
nil
722+
723+
proc confFields(typeImpl: NimNode, parent: ConfFieldDescRef = nil): seq[ConfFieldDesc] =
724+
result = newSeq[ConfFieldDesc]()
725+
for field in recordFields(typeImpl):
726+
if field.readPragma"flatten" != nil:
727+
for cf in confFields(getImpl(field.typ), newConfFieldDesc(field, parent)):
728+
result.add cf
729+
else:
730+
result.add ConfFieldDesc(field: field, parent: parent)
731+
732+
proc genFieldDotExpr(cf: ConfFieldDesc): NimNode =
733+
if cf.parent != nil:
734+
dotExpr(genFieldDotExpr(cf.parent[]), cf.field.name)
735+
else:
736+
cf.field.name
737+
738+
proc fullFieldName(cf: ConfFieldDesc): string =
739+
if cf.parent != nil:
740+
$fullFieldName(cf.parent[]) & "Dot" & $cf.field.name
741+
else:
742+
$cf.field.name
743+
744+
proc fieldCaseFieldFullName(cf: ConfFieldDesc): string =
745+
if cf.field.caseField != nil:
746+
if cf.parent != nil:
747+
fullFieldName(cf.parent[]) & "Dot" & $cf.field.caseField.getFieldName
748+
else:
749+
$cf.field.caseField.getFieldName
750+
else:
751+
doAssert cf.parent != nil, "caseField not found"
752+
fieldCaseFieldFullName(cf.parent[])
753+
696754
proc generateFieldSetters(RecordType: NimNode): NimNode =
697755
var recordDef = getImpl(RecordType)
698756
let makeDefaultValue = bindSym"makeDefaultValue"
699757

700758
result = newTree(nnkStmtListExpr)
701759
var settersArray = newTree(nnkBracket)
702760

703-
for field in recordFields(recordDef):
761+
for cf in confFields(recordDef):
762+
let field = cf.field
704763
var
705-
setterName = ident($field.name & "Setter")
764+
setterName = ident(cf.fullFieldName() & "Setter")
706765
fieldName = field.name
707766
namePragma = field.readPragma"name"
708767
paramName = if namePragma != nil: namePragma
709768
else: fieldName
710769
configVar = ident "config"
711-
configField = newTree(nnkDotExpr, configVar, fieldName)
770+
configField = dotExpr(configVar, genFieldDotExpr(cf))
712771
defaultValue = field.readPragma"defaultValue"
713-
completerName = ident($field.name & "Complete")
772+
completerName = ident(cf.fullFieldName() & "Complete")
714773
isFieldDiscriminator = newLit field.isDiscriminator
715774

716775
if defaultValue == nil:
@@ -761,6 +820,8 @@ proc generateFieldSetters(RecordType: NimNode): NimNode =
761820
debugMacroResult "Field Setters"
762821

763822
func checkDuplicate(cmd: CmdInfo, opt: OptInfo, fieldName: NimNode) =
823+
if opt.kind == Discriminator and opt.isCommand:
824+
return
764825
for x in cmd.opts:
765826
if opt.name == x.name:
766827
error "duplicate name detected: " & opt.name, fieldName
@@ -795,11 +856,12 @@ proc cmdInfoFromType(T: NimNode): CmdInfo =
795856

796857
var
797858
recordDef = getImpl(T)
798-
discriminatorFields = newSeq[OptInfo]()
859+
discriminatorFields = newSeq[(string, OptInfo)]()
799860
fieldIdx = 0
800861

801-
for field in recordFields(recordDef):
862+
for cf in confFields(recordDef):
802863
let
864+
field = cf.field
803865
isImplicitlySelectable = field.readPragma"implicitlySelectable" != nil
804866
defaultValue = field.readPragma"defaultValue"
805867
defaultValueDesc = field.readPragma"defaultValueDesc"
@@ -834,7 +896,7 @@ proc cmdInfoFromType(T: NimNode): CmdInfo =
834896
inc fieldIdx
835897

836898
if field.isDiscriminator:
837-
discriminatorFields.add opt
899+
discriminatorFields.add (cf.fullFieldName(), opt)
838900
let cmdType = field.typ.getImpl[^1]
839901
if cmdType.kind != nnkEnumTy:
840902
error "Only enums are supported as case object discriminators", field.name
@@ -860,18 +922,23 @@ proc cmdInfoFromType(T: NimNode): CmdInfo =
860922
if opt.defaultSubCmd == -1:
861923
error "The default value is not a valid enum value", defaultValue
862924

863-
if field.caseField != nil and field.caseBranch != nil:
864-
let fieldName = field.caseField.getFieldName
865-
var discriminator = findOpt(discriminatorFields, $fieldName)
925+
let caseField = cf.fieldCaseField()
926+
let caseBranch = cf.fieldCaseBranch()
927+
if caseField != nil and caseBranch != nil:
928+
let fieldName = cf.fieldCaseFieldFullName()
929+
var discriminator: OptInfo
930+
for (name, opt) in discriminatorFields:
931+
if fieldName == name:
932+
discriminator = opt
866933

867934
if discriminator == nil:
868-
error "Unable to find " & $fieldName
935+
error "Unable to find " & $caseField.getFieldName
869936

870-
if field.caseBranch.kind == nnkElse:
937+
if caseBranch.kind == nnkElse:
871938
error "Sub-command parameters cannot appear in an else branch. " &
872-
"Please specify the sub-command branch precisely", field.caseBranch[0]
939+
"Please specify the sub-command branch precisely", caseBranch[0]
873940

874-
var branchEnumVal = field.caseBranch[0]
941+
var branchEnumVal = caseBranch[0]
875942
if branchEnumVal.kind == nnkDotExpr:
876943
branchEnumVal = branchEnumVal[1]
877944
var cmd = findCmd(discriminator.subCmds, $branchEnumVal)
@@ -899,6 +966,8 @@ macro configurationRtti(RecordType: type): untyped =
899966

900967
result = newTree(nnkPar, newLitFixed cmdInfo, fieldSetters)
901968

969+
debugMacroResult "configurationRtti"
970+
902971
when hasSerialization:
903972
proc addConfigFile*(secondarySources: auto,
904973
Format: type,
@@ -1331,4 +1400,25 @@ func load*(f: TypedInputFile): f.ContentType =
13311400
mixin loadFile
13321401
loadFile(f.Format, f.string, f.ContentType)
13331402

1403+
proc flattenedAccessorsImpl(RecordType: NimNode): NimNode =
1404+
result = newTree(nnkStmtListExpr)
1405+
let recordDef = getImpl(RecordType.getType[1])
1406+
for cf in confFields(recordDef):
1407+
if cf.parent != nil:
1408+
let
1409+
configVar = ident "config"
1410+
configField = dotExpr(configVar, genFieldDotExpr(cf))
1411+
accessorName = if cf.field.isPublic:
1412+
newTree(nnkPostfix, ident("*"), cf.field.name)
1413+
else:
1414+
ident $cf.field.name
1415+
result.add quote do:
1416+
template `accessorName`(`configVar`: `RecordType`): untyped =
1417+
`configField`
1418+
1419+
debugMacroResult "Flattened Accessors"
1420+
1421+
macro flattenedAccessors*(Configuration: type, public = false): untyped =
1422+
flattenedAccessorsImpl(Configuration)
1423+
13341424
{.pop.}

0 commit comments

Comments
 (0)