.. _nim-api: .. default-domain:: nim .. meta:: :description: CFG configuration format Nim API :keywords: CFG, configuration, Nim The CFG API for Nim ------------------- The CFG reference implementation for the Nim language assumes an Nim version of 1.6.0 or later. Installation ++++++++++++ You can install the library for use by adding `config` to your list of dependencies in your `project.nimble` file: .. code-block:: nim requires "config >= 0.1.1" There's a minimal example of a program that uses CFG `here `__. Exploration +++++++++++ To explore CFG functionality for Nim, we use the ``inim`` program (from `here `_), which is a Read-Eval-Print-Loop (REPL). You can install it using .. code-block:: shell $ nimble install inim and then just run `inim` in the shell to start the REPL. Getting Started with CFG in Nim +++++++++++++++++++++++++++++++ A configuration is accessed through the :type:`Config` type. This has functions which can be passed a filename or a string which contains the text for the configuration. The text is read in, parsed and converted to an object that you can then query. A simple example: .. include:: go-test0.cfg.txt Loading a configuration ~~~~~~~~~~~~~~~~~~~~~~~ The configuration above can be loaded as shown below. In the REPL shell: .. code-block:: shell nim> import config var cfg = fromFile("test0.cfg") Access elements with keys ~~~~~~~~~~~~~~~~~~~~~~~~~ Accessing elements of the configuration with a simple key is just like using a ``Table``: .. code-block:: shell nim> echo cfg["a"] (kind: StringValue, stringValue: "Hello, ") nim> echo cfg["b"] (kind: StringValue, stringValue: "world!") As Nim is strongly and statically typed, configuration values are returned in a tagged variant of type ``ConfigValue``, with the `kind` field as the discriminant. Access elements with paths ~~~~~~~~~~~~~~~~~~~~~~~~~~ As well as simple keys, elements can also be accessed using :term:`path` strings: .. code-block:: shell nim> echo cfg["c.d"] (kind: StringValue, stringValue: "e") .. include:: api-snip-02.inc .. code-block:: shell nim> echo cfg["f.g"] (kind: StringValue, stringValue: "h") .. include:: api-snip-03.inc Access to date/time objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can also get native Nim date/time objects from a configuration, by using an ISO date/time pattern in a :term:`backtick-string`: .. code-block:: shell nim> echo cfg["christmas_morning"] (kind: DateTimeValue, dateTimeValue: (nanosecond: 0, second: 49, minute: 39, hour: 8, monthdayZero: 25, monthZero: 12, year: 2019, weekday: Wednesday, yearday: 358, isDst: false, timezone: ..., utcOffset: 0)) Access to environment variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To access an environment variable, use a :term:`backtick-string` of the form ```$VARNAME```: .. code-block:: shell nim> import os nim> echo cfg["home"].stringValue == getEnv("HOME") true .. include:: api-snip-07.inc .. code-block:: shell nim> echo cfg["foo"] (kind: StringValue, stringValue: "bar") Access to computed values ~~~~~~~~~~~~~~~~~~~~~~~~~ .. include:: api-snip-04.inc .. code-block:: cfg-body :caption: test0a.cfg total_period : 100 header_time: 0.3 * ${total_period} steady_time: 0.5 * ${total_period} trailer_time: 0.2 * ${total_period} base_prefix: '/my/app/' log_file: ${base_prefix} + 'test.log' When this file is read in, the computed values can be accessed directly: .. code-block:: shell nim> cfg = fromFile("test0a.cfg") nim> echo cfg["header_time"] (kind: FloatValue, floatValue: 30.0) nim> echo cfg["steady_time"] (kind: FloatValue, floatValue: 50.0) nim> echo cfg["trailer_time"] (kind: FloatValue, floatValue: 20.0) nim> echo cfg["log_file"] (kind: StringValue, stringValue: "/my/app/test.log") Including one configuration inside another ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are times when it's useful to include one configuration inside another. For example, consider the following configuration files: .. code-block:: cfg-body :caption: logging.cfg layouts: { brief: { pattern: '%d [%t] %p %c - %m%n' } }, appenders: { file: { level: 'INFO', layout: 'brief', filename: 'run/server.log', append: false, charset: 'UTF-8' }, error: { level: 'ERROR', layout: 'brief', filename: 'run/server-errors.log', append: false, charset: 'UTF-8' }, debug: { level: 'DEBUG', layout: 'brief', filename: 'run/server-debug.log', append: false, charset: 'UTF-8' } }, loggers: { mylib: { level: 'INFO' } 'mylib.detail': { level: 'DEBUG' } }, root: { handlers: ['file', 'error', 'debug'], level: 'WARNING' } .. code-block:: cfg-body :caption: redirects.cfg cookies: { url: 'http://www.allaboutcookies.org/', permanent: false }, freeotp: { url: 'https://freeotp.github.io/', permanent: false }, 'google-auth': { url: 'https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2', permanent: false } .. code-block:: cfg-body :caption: main.cfg secret: 'some random application secret', port: 8000, sitename: 'My Test Site', default_access: 'public', ignore_trailing_slashes: true, site_options: { want_ipinfo: false, show_form: true, cookie_bar: true }, debug: true, captcha_length: 4, captcha_timeout: 5, session_timeout: 7 * 24 * 60 * 60, # 7 days in seconds redirects: @'redirects.cfg', email: { sender: 'no-reply@my-domain.com', host: 'smtp.my-domain.com:587', user: 'smtp-user', password: 'smtp-pwd' } logging: @'logging.cfg' .. include:: api-snip-05.inc .. code-block:: shell nim> cfg = fromFile("main.cfg") nim> echo cfg["logging.layouts.brief.pattern"] (kind: StringValue, stringValue: "%d [%t] %p %c - %m%n") nim> echo cfg["redirects.freeotp.url"] (kind: StringValue, stringValue: "https://freeotp.github.io/") nim> echo cfg["redirects.freeotp.permanent"] (kind: BoolValue, boolValue: false) Avoiding unnecessary repetition ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. include:: api-snip-08.inc .. code-block:: cfg-body :caption: logging.cfg (partial) appenders: { file: { level: 'INFO', layout: 'brief', filename: 'run/server.log', append: true, charset: 'UTF-8' }, error: { level: 'ERROR', layout: 'brief', filename: 'run/server-errors.log', append: false, charset: 'UTF-8' }, debug: { level: 'DEBUG', layout: 'brief', filename: 'run/server-debug.log', append: false, charset: 'UTF-8' } }, This portion could be rewritten as: .. code-block:: cfg-body :caption: logging.cfg (partial) defs: { base_appender: { layout: 'brief', append: false, charset: 'UTF-8' } }, appenders: { file: ${defs.base_appender} + { level: 'INFO', filename: 'run/server.log', append: true, }, error: ${defs.base_appender} + { level: 'ERROR', filename: 'run/server-errors.log', }, debug: ${defs.base_appender} + { level: 'DEBUG', filename: 'run/server-debug.log', } }, .. include:: api-snip-06.inc .. code-block:: shell nim> echo cfg["logging.appenders.file.append"] (kind: BoolValue, boolValue: false) nim> echo cfg["logging.appenders.file.filename"] (kind: StringValue, stringValue: "run/server.log") nim> echo cfg["logging.appenders.file.level"] (kind: StringValue, stringValue: "INFO") nim> echo cfg["logging.appenders.debug.level"] (kind: StringValue, stringValue: "DEBUG") nim> echo cfg["logging.appenders.debug.filename"] (kind: StringValue, stringValue: "run/server-debug.log") nim> echo cfg["logging.appenders.debug.append"] (kind: BoolValue, boolValue: false) .. include:: api-snip-09.inc .. code-block:: cfg-body :caption: logging.cfg (partial) defs: { base_appender: { layout: 'brief', append: false, charset: 'UTF-8' } log_prefix: 'run/', }, appenders: { file: ${defs.base_appender} + { level: 'INFO', filename: ${defs.log_prefix} + 'server.log', append: true, }, error: ${defs.base_appender} + { level: 'ERROR', filename: ${defs.log_prefix} + 'server-errors.log', }, debug: ${defs.base_appender} + { level: 'DEBUG', filename: ${defs.log_prefix} + 'server-debug.log', } } with the same result as before. It *is* slightly more verbose than before, but the location of all files can be changed in just one place now, as opposed to three, as it was before. Types +++++ The main objects used to interface with CFG configurations are :type:`Config` and :type:`ConfigValue`. The former represents an entire CFG configuration; the latter represents a value read from it. The `ConfigValue` is a tagged variant structure using field `kind`, of type :type:`ValueKind`, as the discriminant. You may also have to deal with :type:`ConfigError`, an exception which can be raised due to errors in configurations, and :type:`Location`, which relates to a location in CFG source. .. index:: single: ValueKind; Nim enum ValueKind (enum) ~~~~~~~~~~~~~~~~ This represents the kinds of values that can be returned from a configuration. It has the following members: .. cssclass:: table table-bordered table-striped prop-table .. table:: :widths: auto +-------------------+--------------------------------------------+--------------------------------------+ | Name | Significance | Corresponding field in `ConfigValue` | +===================+============================================+======================================+ | IntegerValue | A 64-bit signed integer value | intValue | +-------------------+--------------------------------------------+--------------------------------------+ | FloatValue | A 64-bit floating-point value | floatValue | +-------------------+--------------------------------------------+--------------------------------------+ | StringValue | A string value | stringValue | +-------------------+--------------------------------------------+--------------------------------------+ | BoolValue | A Boolean value | boolValue | +-------------------+--------------------------------------------+--------------------------------------+ | ComplexValue | A 64-bit complex number | complexValue | +-------------------+--------------------------------------------+--------------------------------------+ | NoneValue | A ``null`` value | | +-------------------+--------------------------------------------+--------------------------------------+ | DateTimeValue | A date/time value with timezone offset | dateTimeValue | +-------------------+--------------------------------------------+--------------------------------------+ | ListValue | A list of values | listValue | +-------------------+--------------------------------------------+--------------------------------------+ | MappingValue | A mapping of strings to values | mappingValue | +-------------------+--------------------------------------------+--------------------------------------+ | NestedConfigValue | A nested configuration (sub-configuration) | configValue | +-------------------+--------------------------------------------+--------------------------------------+ .. index:: single: ConfigValue; Nim object ConfigValue (object) ~~~~~~~~~~~~~~~~~~~~ This represents values that can be returned from a configuration. It has the following properties (note that `kind` is always available, and then just *one* of the others depending on the value of `kind`): .. cssclass:: table table-bordered table-striped prop-table .. table:: :widths: auto +---------------+----------------------------+--------+ | Name | Type | Access | +===============+============================+========+ | kind | ValueKind | RW | +---------------+----------------------------+--------+ | intValue | int64 | RW | +---------------+----------------------------+--------+ | floatValue | float64 | RW | +---------------+----------------------------+--------+ | stringValue | string | RW | +---------------+----------------------------+--------+ | boolValue | bool | RW | +---------------+----------------------------+--------+ | complexValue | Complex64 | RW | +---------------+----------------------------+--------+ | dateTimeValue | DateTime | RW | +---------------+----------------------------+--------+ | listValue | seq[ConfigValue] | RW | +---------------+----------------------------+--------+ | mappingValue | Table[string, ConfigValue] | RW | +---------------+----------------------------+--------+ | configValue | Config | RW | +---------------+----------------------------+--------+ .. index:: single: Location; Nim type Location (object) ~~~~~~~~~~~~~~~~~ This represents a location in CFG source. It has the following properties: .. cssclass:: table table-bordered table-striped prop-table .. table:: :widths: auto +--------+------+--------+ | Name | Type | Access | +========+======+========+ | line | int | RW | +--------+------+--------+ | column | int | RW | +--------+------+--------+ .. index:: single: ConfigError; Nim type ConfigError (object) ~~~~~~~~~~~~~~~~~~~~ This represents an error encountered when working with CFG. It has the following properties: .. cssclass:: table table-bordered table-striped prop-table .. table:: :widths: auto +----------+----------+--------+ | Name | Type | Access | +==========+==========+========+ | msg | string | RW | +----------+----------+--------+ | location | Location | RW | +----------+----------+--------+ .. index:: single: Config; Nim type Config (ref object) ~~~~~~~~~~~~~~~~~~~ This represents a CFG configuration. It has the following properties: .. cssclass:: table table-bordered table-striped prop-table .. table:: :widths: auto +-----------------+--------------------------+---------------+--------+---------------------------------------------------------------------+ | Name | Type | Default value | Access | Purpose | +=================+==========================+===============+========+=====================================================================+ |noDuplicates |bool |true | RW | Whether duplicate keys are allowed. | +-----------------+--------------------------+---------------+--------+---------------------------------------------------------------------+ |strictConversions|bool |true | RW | Whether exceptions are raised if a conversion fails. | +-----------------+--------------------------+---------------+--------+---------------------------------------------------------------------+ |path |string | | RW | Path from which a configuration was loaded. This is set | | | | | | automatically when a configuration is read from a file, and only | | | | | | needs to be called if initializing the configuration from source | | | | | | and you need to specify a location for included configurations | | | | | | which are in files. | +-----------------+--------------------------+---------------+--------+---------------------------------------------------------------------+ |includePath |seq[string] |@[] | RW | Directories to search for included configurations (searched | | | | | | after the parent directory of `path`, or the current directory if | | | | | | that isn't set. | +-----------------+--------------------------+---------------+--------+---------------------------------------------------------------------+ |context |Table[string, ConfigValue]|(empty) | RW | A lookup table for any variables to be used. | +-----------------+--------------------------+---------------+--------+---------------------------------------------------------------------+ |cached |bool |false | RW | If true, values fetched using :proc:`get` or :proc:`[]` are cached. | +-----------------+--------------------------+---------------+--------+---------------------------------------------------------------------+ .. cssclass:: class-members-heading Procedures .. proc:: newConfig*(): Config Creates a new configuration object, without any actual configuration. .. proc:: fromSource*(source: string): Config Creates a new configuration object, with the configuration specified in `source`. .. proc:: fromFile*(path: string): Config Creates a new configuration object, with the configuration read from the file specified in `path`. .. proc:: loadFile*(cfg: Config, string: path) Load or reload the configuration `cfg` from the specified `path`. .. proc:: load*(cfg: Config, stream: Stream) Load or reload the configuration `cfg` from the specified `stream`. .. proc:: get*(cfg: Config, key: string, default: ConfigValue): ConfigValue Get a value from the configuration `cfg` using `key`, and returning `default` if not present in the configuration. The `key` can be a path as well as a simple key. .. proc:: `[]`*(cfg: Config, key: string): ConfigValue Get a value from the configuration `cfg` using the indexing idiom, with `key` as the index. The `key` can be a path as well as a simple key. .. proc:: asDict*(cfg: Config): Table[string, ConfigValue] Get the configuration `cfg` as a `Table`. This will recurse into included configurations. .. proc:: getSubConfig*(cfg: Config, key: string): Config Get an included configuration (sub-configuration) from `cfg` via `key`. This can be a path as well as a simple key. .. raw:: html