.. default-domain:: kotlin
.. _dlang-api:
.. meta::
:description: CFG configuration format D language API
:keywords: CFG, configuration, DLang, D
The CFG API for D
-----------------
The CFG reference implementation for D is written and tested using DMD >=
2.086.0. It's implemented in a module called ``config``.
Installation
++++++++++++
The package can be installed for use from the `D package registry
`_ using the package name ``cfg-lib``.
There's a minimal example of a program and project that uses CFG `here
`__.
Exploration
+++++++++++
To explore CFG functionality for D, we use the `drepl` Read-Eval-Print-Loop
(REPL), which is available from `here `_.
Once installed, you can invoke a shell using
.. code-block:: shell
$ drepl
Getting Started with CFG in D
+++++++++++++++++++++++++++++
.. include:: api-snip-01.inc
.. include:: go-test0.cfg.txt
Loading a configuration
~~~~~~~~~~~~~~~~~~~~~~~
The configuration above can be loaded as shown below. In the REPL shell:
.. code-block:: cpp
D> import config;
config
D> Config cfg; shared static this() { cfg = null; }
cfg
D> cfg = new Config("tmp/test0.cfg")
Config(test0.cfg)
The one-time-per-session dance with ``shared static this()`` is currently
needed due to a limitation of `drepl`.
Access elements with keys
~~~~~~~~~~~~~~~~~~~~~~~~~
Accessing elements of the configuration with a simple key is just like using
an associative array:
.. code-block:: cpp
D> cfg["a"]
Hello,
D> cfg["b"]
world!
Access elements with paths
~~~~~~~~~~~~~~~~~~~~~~~~~~
As well as simple keys, elements can also be accessed using :term:`path`
strings:
.. code-block:: cpp
D> cfg["c.d"]
e
.. include:: api-snip-02.inc
.. code-block:: cpp
D> cfg["f.g"]
h
.. include:: api-snip-03.inc
Access to date/time objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can also get native date/time objects from a configuration, by using an
ISO date/time pattern in a :term:`backtick-string`:
.. code-block:: cpp
D> cfg["christmas_morning"]
2019-Dec-25 08:39:49
Access to environment variables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To access an environment variable, use a :term:`backtick-string` of the form
```$VARNAME```:
.. code-block:: cpp
D> cfg["home"]
/home/vinay
.. include:: api-snip-07.inc
.. code-block:: cpp
D> cfg["foo"]
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:: cpp
D> cfg["header_time"]
30
D> cfg["steady_time"]
50
D> cfg["trailer_time"]
20
D> cfg["log_file"]
/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: {
layout: 'brief',
append: false,
charset: 'UTF-8'
level: 'INFO',
filename: 'run/server.log',
append: true,
},
error: {
layout: 'brief',
append: false,
charset: 'UTF-8'
level: 'ERROR',
filename: 'run/server-errors.log',
},
debug: {
layout: 'brief',
append: false,
charset: 'UTF-8'
level: 'DEBUG',
filename: 'run/server-debug.log',
}
}
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
},
connection: 'postgres+pool://db_user:db_pwd@localhost:5432/db_name',
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:: cpp
D> cfg["logging.appenders.file.filename"]
run/server.log
D> cfg["redirects.freeotp.url"]
https://freeotp.github.io/
D> cfg["redirects.freeotp.permanent"]
false
Avoiding unnecessary repetition
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. include:: api-snip-08.inc
.. code-block:: cfg-body
:caption: logging.cfg (partial)
appenders: {
file: {
layout: 'brief',
append: false,
charset: 'UTF-8'
level: 'INFO',
filename: 'run/server.log',
append: true,
},
error: {
layout: 'brief',
append: false,
charset: 'UTF-8'
level: 'ERROR',
filename: 'run/server-errors.log',
},
debug: {
layout: 'brief',
append: false,
charset: 'UTF-8'
level: 'DEBUG',
filename: 'run/server-debug.log',
}
}
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:: cpp
D> cfg["logging.appenders.file.level"]
INFO
D> cfg["logging.appenders.file.layout"]
brief
D> cfg["logging.appenders.file.append"]
true
D> cfg["logging.appenders.file.filename"]
run/server.log
D> cfg["logging.appenders.error.append"]
false
D> cfg["logging.appenders.error.filename"]
run/server-errors.log
.. 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/',
},
layouts: {
brief: {
pattern: '%d [%t] %p %c - %m%n'
}
},
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.
Classes
+++++++
The ``Config`` class
~~~~~~~~~~~~~~~~~~~~
This class implements a CFG configuration. You'll generally interface to CFG
files using this class. When you pass in a stream or file path to the
constructor or the :fun:`Config.load` / :fun:`Config.loadFile` methods, the
CFG source in the stream or file is parsed and converted into an internal form
that can be queried.
.. index::
single: Config; D class
.. class:: Config
.. cssclass:: class-members-heading
Variables
.. var:: noDuplicates: bool = true
Whether duplicates keys are allowed when parsing a mapping or mapping
body. If not allowed and duplicates are seen, a ``ConfigException`` is
thrown.
.. var:: strictConversions: bool = true
If ``true``, a :class:`ConfigException` is thrown if a string can't be
converted. It's intended to help catch typos in backtick-strings.
.. var:: context: Variant[string]
A mapping within which to look up identifiers during evaluation of AST
nodes.
.. var:: cached: bool = false
If ``true``, an internal cache is used.
.. var:: includePath: string[]
A list of directories which is searched for included sub-configurations if the
parent directory of :var:`path` (or the current directory, if `path` isn't set)
doesn't contain them.
.. var:: path: string = null
The path to the file from which the configuration has been read. You won't
usually need to set this, unless you want to load a configuration from text that
references included files. In that case, set the path to a value whose parent
directory contains the included files, or specify relevant directories using
:var:`includePath`. When a configuration is loaded from a file, this attribute
is automatically set.
.. cssclass:: class-members-heading
Constructors
.. constructor:: this()
Constructs an instance with no actual configuration loaded. A call to
:fun:`load` or :fun:`loadFile` would be needed to actually make a
usable instance.
.. constructor:: this(InputRange!(ubyte) r)
Construct this instance with the configuration read from the provided
range.
.. constructor:: this(string path)
Construct this instance with the configuration read from a file named by
the provided path.
.. cssclass:: class-members-heading
Methods
.. fun:: void load(InputRange!(ubyte) r)
Load this instance with the configuration read from the provided reader.
:param r: A range from which to read the bytes of the configuration.
:exception ConfigException: If there is an I/O error when reading the
source or an error in the configuration
syntax.
.. fun:: void loadFile(string path)
Load this instance with the configuration read from the file at the
provided path.
:param path: A string specifying the location of the file which contains
the configuration.
:exception ConfigException: If there is an I/O error when reading the
source or an error in the configuration
syntax.
.. fun:: Variant get(string key)
This method implements the key access operator, e.g. ``mapping[key]``.
The value of `key` can be a simple key (identifier) or a valid path
string.
:param key: A string describing a simple key or a path in the
configuration.
:throws InvalidPathException: If `key` is not present in the data and it
cannot be parsed as a path.
:throws BadIndexException: If, while traversing a path, a key or index is
not of the appropriate type for its container,
or if it isn't in the required range.
:throws ConfigException: If a key is not found or some other semantic
error occurs (for example, an operation
involving incompatible types).