You can also go crazy and get rid of even more boiler plate (I've added types 
as well):
    
    
    # env.nim
    
    import macros, tables
    import std/envvars, std/options
    export envvars, options
    
    type
      EnvVarInfo = tuple[envType: string, envBody: NimNode]
    # Store all of the compile time info
    var envTable {.compileTime.}: TableRef[string, EnvVarInfo] = 
newTable[string, EnvVarInfo]()
    
    macro defineEnv*(fieldName, fieldType, body: untyped): untyped =
      # create the new field name
      let newName = ident("m_" & fieldName.strVal)
      
      # body of our code
      let body = quote do:
        # Use fieldName and fieldType as per needed.
        # Also, inject Env into the body code so that it can be
        # referenced inside the body of the env code with the envs
        # that were already declared
        proc `fieldName`*(Env {.inject.}: var EnvType): `fieldType` =
          if isNone(Env.`newName`):
            result = `body`
            Env.`newName` = some result
          else:
            result = Env.`newName`.get
      
      envTable[newName.strVal] = (fieldType.strVal, body)
    
    proc createEnvTypeField(envName: string, envType: string): NimNode =
      result = nnkIdentDefs.newTree(
        ident(envName),
        nnkBracketExpr.newTree(
          ident("Option"),
          ident(envType)
        ),
        newEmptyNode()
      )
    
    macro buildEnvType*(): untyped =
      # build the env type and all of the procs and the var declaration
      let recList = nnkRecList.newNimNode()
      result = nnkStmtList.newTree()
      
      let envTy = nnkTypeSection.newTree(
        nnkTypeDef.newTree(
          ident("EnvType"),
          newEmptyNode(),
          nnkObjectTy.newTree(
            newEmptyNode(),
            newEmptyNode(),
            recList
          )
        )
      )
      
      # add the type before the procs
      result.add(envTy)
      
      for envName, env in envTable.pairs:
        recList.add(createEnvTypeField(envName, env.envType))
        result.add(env.envBody)
      
      # This is needed so that nim makes the var name
      # exactly as spelled and doesn't add any hash values
      # to the name. This makes it able to be referenced outside of
      # the macro
      let envVarName = ident("Env")
      
      result.add quote do:
        var `envVarName`*: EnvType
    
    
    Run

Usage:
    
    
    # yourfile.nim
    
    import env
    
    defineEnv user, string:
      "USER".getEnv
    
    defineEnv icloud_dir, string:
      "/Users/" & Env.user & "/Library/CloudStorage/Box-Box"
    
    defineEnv num_things, int:
      42
    
    # Must call this AFTER all defineEnv calls
    buildEnvType()
    
    echo Env.user
    echo Env.icloud_dir
    echo Env.num_things
    
    
    Run

Reply via email to