1414 std/ [enumutils, options, strutils, wordwrap],
1515 results,
1616 stew/ shims/ macros,
17- confutils/ [defs, cli_parser, config_file]
17+ confutils/ [defs, cli_parser, config_file, utils ]
1818
1919export
2020 options, results, defs, config_file
@@ -817,24 +817,132 @@ template debugMacroResult(macroName: string) {.dirty.} =
817817 echo " \n -------- " , macroName, " ----------------------"
818818 echo result .repr
819819
820+ type
821+ ConfFieldDescRef = ref ConfFieldDesc
822+ ConfFieldDesc = object
823+ field: FieldDescription
824+ parent: ConfFieldDescRef
825+
826+ proc newConfFieldDesc (
827+ field: FieldDescription , parent: ConfFieldDescRef
828+ ): ConfFieldDescRef =
829+ ConfFieldDescRef (field: field, parent: parent)
830+
831+ proc fieldCaseBranch (cf: ConfFieldDesc ): NimNode =
832+ if cf.field.caseBranch != nil :
833+ cf.field.caseBranch
834+ elif cf.parent != nil :
835+ fieldCaseBranch (cf.parent[])
836+ else :
837+ nil
838+
839+ proc fieldCaseField (cf: ConfFieldDesc ): NimNode =
840+ if cf.field.caseField != nil :
841+ cf.field.caseField
842+ elif cf.parent != nil :
843+ fieldCaseField (cf.parent[])
844+ else :
845+ nil
846+
847+ proc validateFlattenOptions (field: FieldDescription , children: openArray [ConfFieldDesc ]) =
848+ # # Validate flatten tuple options exist
849+ doAssert field.readPragma " flatten" != nil
850+ let ftn = field.readPragma " flatten"
851+ if ftn.kind == nnkTupleConstr:
852+ if ftn.len > 0 and ftn[0 ].kind != nnkExprColonExpr:
853+ error " invalid flatten options, expected tuple of (k: v)" , ftn
854+ var found = newSeq [NimNode ]()
855+ for cf in children:
856+ for x in ftn:
857+ if eqIdent (x[0 ], cf.field.name):
858+ found.add x[0 ]
859+ for x in ftn:
860+ if x[0 ] notin found:
861+ error " invalid flatten option: " & $ x[0 ], ftn
862+
863+ proc confFields (typeImpl: NimNode , parent: ConfFieldDescRef = nil ): seq [ConfFieldDesc ] =
864+ result = newSeq [ConfFieldDesc ]()
865+ for field in recordFields (typeImpl):
866+ let ftn = field.readPragma " flatten"
867+ if ftn != nil :
868+ let firstChildIdx = result .len
869+ for cf in confFields (getImpl (field.typ), newConfFieldDesc (field, parent)):
870+ result .add cf
871+ validateFlattenOptions (field, toOpenArray (result , firstChildIdx, result .len- 1 ))
872+ else :
873+ result .add ConfFieldDesc (field: field, parent: parent)
874+
875+ proc genFieldDotExpr (cf: ConfFieldDesc ): NimNode =
876+ if cf.parent != nil :
877+ dotExpr (genFieldDotExpr (cf.parent[]), cf.field.name)
878+ else :
879+ cf.field.name
880+
881+ proc fullFieldName (cf: ConfFieldDesc ): string =
882+ if cf.parent != nil :
883+ $ fullFieldName (cf.parent[]) & " Dot" & $ cf.field.name
884+ else :
885+ $ cf.field.name
886+
887+ proc fieldCaseFieldFullName (cf: ConfFieldDesc ): string =
888+ if cf.field.caseField != nil :
889+ if cf.parent != nil :
890+ fullFieldName (cf.parent[]) & " Dot" & $ cf.field.caseField.getFieldName
891+ else :
892+ $ cf.field.caseField.getFieldName
893+ else :
894+ doAssert cf.parent != nil , " caseField not found"
895+ fieldCaseFieldFullName (cf.parent[])
896+
897+ proc flattenDefaultValue (field: FieldDescription , parent: ConfFieldDescRef ): NimNode =
898+ if parent == nil :
899+ return nil
900+ let ancestorVal = flattenDefaultValue (field, parent[].parent)
901+ if ancestorVal != nil :
902+ return ancestorVal
903+ let ftn = parent[].field.readPragma " flatten"
904+ case ftn.kind
905+ of nnkSym: nil
906+ of nnkTupleConstr:
907+ var ret: NimNode = nil
908+ for x in ftn:
909+ if eqIdent (x[0 ], field.name):
910+ ret = x[1 ]
911+ ret
912+ else :
913+ error " Bad flatten pragma" , ftn
914+ nil
915+
916+ proc readDefaultValueOverride (cf: ConfFieldDesc ): NimNode =
917+ flattenDefaultValue (cf.field, cf.parent)
918+
919+ proc readDefaultValue (cf: ConfFieldDesc ): NimNode =
920+ result = cf.readDefaultValueOverride ()
921+ if result == nil :
922+ result = cf.field.readPragma " defaultValue"
923+
820924proc generateFieldSetters (RecordType: NimNode ): NimNode =
821925 var recordDef = getImpl (RecordType )
822926 let makeDefaultValue = bindSym " makeDefaultValue"
823927
824928 result = newTree (nnkStmtListExpr)
825929 var settersArray = newTree (nnkBracket)
826930
827- for field in recordFields (recordDef):
931+ for cf in confFields (recordDef):
932+ let field = cf.field
828933 var
829- setterName = ident ($ field.name & " Setter" )
934+ setterName = ident (cf. fullFieldName () & " Setter" )
830935 fieldName = field.name
831936 namePragma = field.readPragma " name"
832- paramName = if namePragma != nil : namePragma
833- else : fieldName
937+ paramName =
938+ if namePragma != nil :
939+ namePragma
940+ else :
941+ fieldName
834942 configVar = ident " config"
835- configField = newTree (nnkDotExpr, configVar, fieldName )
836- defaultValue = field. readPragma " defaultValue "
837- completerName = ident ($ field.name & " Complete" )
943+ configField = dotExpr ( configVar, genFieldDotExpr (cf) )
944+ defaultValue = cf. readDefaultValue ()
945+ completerName = ident (cf. fullFieldName () & " Complete" )
838946 isFieldDiscriminator = newLit field.isDiscriminator
839947 defaultValueDesc = field.readPragma " defaultValueDesc"
840948 defaultValueHelp =
@@ -849,7 +957,7 @@ proc generateFieldSetters(RecordType: NimNode): NimNode =
849957 newLit (repr defaultValue)
850958 else :
851959 newLit (" " )
852- defaultValueHelpName = ident ($ field.name & " DefaultValueHelp" )
960+ defaultValueHelpName = ident (cf. fullFieldName () & " DefaultValueHelp" )
853961
854962 if defaultValue == nil :
855963 defaultValue = newCall (makeDefaultValue, newTree (nnkTypeOfExpr, configField))
@@ -917,6 +1025,8 @@ proc generateFieldSetters(RecordType: NimNode): NimNode =
9171025 debugMacroResult " Field Setters"
9181026
9191027func checkDuplicate (cmd: CmdInfo , opt: OptInfo , fieldName: NimNode ) =
1028+ if opt.kind == Discriminator and opt.isCommand:
1029+ return
9201030 for x in cmd.opts:
9211031 if opt.name == x.name:
9221032 error " duplicate name detected: " & opt.name, fieldName
@@ -955,13 +1065,14 @@ proc cmdInfoFromType(T: NimNode): CmdInfo =
9551065
9561066 var
9571067 recordDef = getImpl (T)
958- discriminatorFields = newSeq [OptInfo ]()
1068+ discriminatorFields = newSeq [( string , OptInfo ) ]()
9591069 fieldIdx = 0
9601070
961- for field in recordFields (recordDef):
1071+ for cf in confFields (recordDef):
9621072 let
1073+ field = cf.field
9631074 isImplicitlySelectable = field.readPragma " implicitlySelectable" != nil
964- defaultValue = field. readPragma " defaultValue "
1075+ defaultValue = cf. readDefaultValue ()
9651076 obsoleteMsg = field.readPragma " obsolete"
9661077 separator = field.readPragma " separator"
9671078 longDesc = field.readPragma " longDesc"
@@ -992,7 +1103,7 @@ proc cmdInfoFromType(T: NimNode): CmdInfo =
9921103 inc fieldIdx
9931104
9941105 if field.isDiscriminator:
995- discriminatorFields.add opt
1106+ discriminatorFields.add (cf. fullFieldName (), opt)
9961107 let cmdType = field.typ.getImpl[^ 1 ]
9971108 if cmdType.kind != nnkEnumTy:
9981109 error " Only enums are supported as case object discriminators" , field.name
@@ -1018,20 +1129,25 @@ proc cmdInfoFromType(T: NimNode): CmdInfo =
10181129 if opt.defaultSubCmd == - 1 :
10191130 error " The default value is not a valid enum value" , defaultValue
10201131
1021- if field.caseField != nil and field.caseBranch != nil :
1022- let fieldName = field.caseField.getFieldName
1023- var discriminator = findOpt (discriminatorFields, $ fieldName)
1132+ let caseField = cf.fieldCaseField ()
1133+ let caseBranch = cf.fieldCaseBranch ()
1134+ if caseField != nil and caseBranch != nil :
1135+ let fieldName = cf.fieldCaseFieldFullName ()
1136+ var discriminator: OptInfo
1137+ for (name, opt) in discriminatorFields:
1138+ if fieldName == name:
1139+ discriminator = opt
10241140
10251141 if discriminator == nil :
1026- error " Unable to find " & $ fieldName
1142+ error " Unable to find " & $ caseField.getFieldName
10271143
1028- if field. caseBranch.kind == nnkElse:
1144+ if caseBranch.kind == nnkElse:
10291145 error " Sub-command parameters cannot appear in an else branch. " &
1030- " Please specify the sub-command branch precisely" , field. caseBranch[0 ]
1146+ " Please specify the sub-command branch precisely" , caseBranch[0 ]
10311147
10321148 # support multiple subcommands in the same branch; skip branch body
1033- for enumValIdx in 0 .. field. caseBranch.len - 2 :
1034- var branchEnumVal = field. caseBranch[enumValIdx]
1149+ for enumValIdx in 0 .. caseBranch.len - 2 :
1150+ var branchEnumVal = caseBranch[enumValIdx]
10351151 if branchEnumVal.kind == nnkDotExpr:
10361152 branchEnumVal = branchEnumVal[1 ]
10371153 let cmd = findCmd (discriminator.subCmds, $ branchEnumVal)
@@ -1060,6 +1176,8 @@ macro configurationRtti(RecordType: type): untyped =
10601176
10611177 result = newTree (nnkPar, newLitFixed cmdInfo, fieldSetters)
10621178
1179+ debugMacroResult " configurationRtti"
1180+
10631181when hasSerialization:
10641182 template addConfigFileImpl (
10651183 secondarySources: auto ,
0 commit comments