kuddle_config

Kuddle Config Provider or just general config helpers


Keywords
config, configprovider, elixir, kdl
License
MIT

Documentation

Kuddle Config

An Elixir Config.Provider using kuddle, also general configuration helpers using Kuddle.

Installation

To add kuddle_config to your project:

def deps do
  [
    {:kuddle_config, "~> 0.1.0"}
  ]
end

Usage

Kuddle provides 2 different config providers:

# The default Config.Provider which will load a kdl file as config
Kuddle.Config.Provider

# Format
defp releases do
  [
    application: [
      config_providers: [
        {Kuddle.Config.Provider, [
          path: config_path()
        ]}
      ]
    ]
  ]
end

# Example
defp releases do
  [
    application: [
      config_providers: [
        {Kuddle.Config.Provider, [
          path: {:system, "PATH_TO_CONFIG", "/default/path/to/kdl/config"}
        ]}
      ]
    ]
  ]
end
# A special config provider that will load every kdl file in a directory as config
Kuddle.Config.DirectoryProvider

# Format
defp releases do
  [
    application: [
      config_providers: [
        {Kuddle.Config.DirectoryProvider, [
          paths: [config_path()],
          extensions: [String.t()]
        ]}
      ]
    ]
  ]
end

# Example
defp releases do
  [
    application: [
      config_providers: [
        {Kuddle.Config.Provider, [
          paths: [
            {:system, "PATH_TO_CONFIG", "/default/path/to/kdl/config"}
          ],
          extensions: [".kdl", ".kuddle"]
        ]}
      ]
    ]
  ]
end

Despite the original purpose of this library being the config providers, the config module itself is quite useful even without the providers:

# Have a KDL blob you wish to load?
{:ok, config} =
  Kuddle.Config.load_config_blob("""
  application {
    node "value"
  }
  """)

[
  application: [
    node: "value"
  ]
] = config

# Have a KDL file you'd like to load?
{:ok, config} = Kuddle.Config.load_config_file("my_kdl_config.kdl")

[
  application: [
    node2: "some_other_value"
  ]
] = config

# Have a directory filled with KDL files you'd like to load into one config?
{:ok, config} = Kuddle.Config.load_config_directory("/my/kdl/configs", [".kdl", ".kuddle"])

[
  data: [
    {MyRepo, [
      database: "database",
      host: "127.0.0.1",
      port: 5432,
    ]},
  ],
  web: [
    http: [
      port: 4000
    ]
  ],
  workers: [
    amqp: [
      host: "127.0.0.1",
      port: 5732,
      virtual_host: "my_workers",
    ]
  ]
] = config

# Have the kuddle document already?
{:ok, config} = Kuddle.Config.load_config_document(document)

[
  logger: [
    console: [
      level: :debug
    ]
  ]
] = config

Config Format

Root nodes are application level config, while subsequent sub nodes will be one level deeper config

application {
  key "value"

  key2 {
    subkey "value"
  }
}

Is equivalent to:

config :application,
  key: "value",
  key2: [
    subkey: "value"
  ]

Config is extracted from both attributes and sub nodes:

application {
  food {
    bacon "1"
    eggs "2"
  }
}

Is equivalent to:

application {
  food bacon="1" eggs="2"
}

Either can be mixed and matched to achieve a comfortable format:

application {
  food bacon="1" {
    eggs="2"
  }
}

There is one tiny gotcha in regards to lists:

application {
  node "value"
}

Would evaluate to:

[
  application: [
    node: "value"
  ]
]

As one would expect, however:

application {
  node "value" "value2"
}

Would evaluate to:

[
  application: [
    node: ["value", "value2"]
  ]
]

Assuming the configuration requires a list, it can be coerced using the (list) annotation on the node:

application {
  (list)node "value"
}
[
  application: [
    node: ["value"]
  ]
]

Sometimes it is useful to set a tuple, which is something most available configuration languages struggle with for elixir:

application {
  (tuple)thing "left" "right"
}
[
  application: [
    thing: {"left", "right"}
  ]
]

You can also cast values into atoms:

logger {
  console {
    level (atom)"debug"
  }
}
[
  logger: [
    console: [
      level: :debug
    ]
  ]
]

A list of all available types and more information can be found in the Kuddle.Config.Types module, or the table below.

Type Annotation Example
date start_date (date)"2021-09-14"
utc_datetime inserted_at (utc_datetime)"2021-09-14T18:00:00.000000Z"
naive_datetime deleted_at (naive_datetime)"2021-09-14T18:00:00.000000Z"
time start_time (time)"18:00:23"
decimal cost (decimal)"0.002500"
atom level (atom)"debug"
boolean enable_polling (boolean)"YES"
tuple (tuple)call_pair "New York" "12003004000"
list (list)allow_list "117.27.222.122"

Additional types can be registered using kuddle_config :types config:

config :kuddle_config,
  types: [
    geopoint: {MyGeoPoint, :cast},
  ]
defmodule MyGeoPoint do
  def cast(value) do
    {:ok, String.split(value, ",", parts: 2) |> Enum.map(&Decimal.new/1) |> List.to_tuple()}
  end
end
application {
  point (geopoint)"15.27,265.27"
}
[
  application: [
    point: {%Decimal{coef: "1527", exp: -2, sign: 1}, %Decimal{coef: "26527", exp: -2, sign: 1}}
  ]
]