.. 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).