.. _dart-api: .. default-domain:: dart .. meta:: :description: CFG configuration format Dart API :keywords: CFG, configuration, Dart The CFG API for Dart -------------------- The CFG reference implementation for the Dart language assumes a Dart SDK version of 2.13.1 or later. Installation ++++++++++++ You can install the library for use by adding `cfg_lib` to your list of dependencies in your `pubspec.yaml` file: .. code-block:: yaml dependencies: cfg_lib: ^0.1.1 There's a minimal example of a program that uses CFG `here `__. Exploration +++++++++++ As Dart doesn't currently have a Read-Eval-Print-Loop (REPL) function available, you can follow along with an online setup here: https://replit.com/join/lrrwrhazkb-vsajip Getting Started with CFG in Dart ++++++++++++++++++++++++++++++++ A configuration is accessed through the :dart:class:`Config` class 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: .. code-block:: dart var cfg = Config.fromFile('test0.cfg'); Access elements with keys ~~~~~~~~~~~~~~~~~~~~~~~~~ Accessing elements of the configuration with a simple key is just like using a ``Table``: .. code-block:: dart print('a is "${cfg['a']}"'); print('b is "${cfg['b']}"'); which prints: .. code-block:: shell a is "Hello, " b is "world!" Access elements with paths ~~~~~~~~~~~~~~~~~~~~~~~~~~ As well as simple keys, elements can also be accessed using :term:`path` strings: .. code-block:: dart print('c.d is "${cfg['c.d']}"'); which prints: .. code-block:: shell c.d is "e" .. include:: api-snip-02.inc .. code-block:: dart print('f.g is "${cfg['f.g']}"'); which prints: .. code-block:: shell f.g is "h" .. include:: api-snip-03.inc Access to date/time objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can also get native Dart date/time objects from a configuration, by using an ISO date/time pattern in a :term:`backtick-string`: .. code-block:: dart print('Christmas morning is ${cfg['christmas_morning']} (${cfg['christmas_morning'].runtimeType})'); which prints: .. code-block:: shell Christmas morning is 2019-12-25 08:39:49.000Z (DateTime) As Dart doesn't currently support timezone-aware date/times out of the box, currently the approach used is to compute the offset and add to the UTC time to yield the result. Although there are some third-party timezone-aware libraries around, they don't allow computing an offset and setting it on the date/time - they work from timezone names. Access to environment variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To access an environment variable, use a :term:`backtick-string` of the form ```$VARNAME```: .. code-block:: dart print(cfg['home'] == Platform.environment['HOME']); which prints: .. code-block:: shell true .. include:: api-snip-07.inc .. code-block:: dart print('foo is "${cfg['foo']}"'); which prints: .. code-block:: shell foo is "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. Follow along with this online setup: https://replit.com/join/hewtgzpxgn-vsajip .. code-block:: dart var cfg = Config.fromFile('test0a.cfg'); for (var key in ['header_time', 'steady_time', 'trailer_time', 'log_file']) { print('$key is ${cfg[key]} (${cfg[key].runtimeType})'); } which prints: .. code-block:: shell header_time is 30.0 (double) steady_time is 50.0 (double) trailer_time is 20.0 (double) log_file is /my/app/test.log (String) 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 Follow along with this online setup: https://replit.com/join/dprcppmdox-vsajip .. code-block:: dart var cfg = Config.fromFile('main.cfg'); for (var key in ['logging.layouts.brief.pattern', 'redirects.freeotp.url', 'redirects.freeotp.permanent']) { print('$key is ${cfg[key]} (${cfg[key].runtimeType})'); } which prints: .. code-block:: shell logging.layouts.brief.pattern is %d [%t] %p %c - %m%n (String) redirects.freeotp.url is https://freeotp.github.io/ (String) redirects.freeotp.permanent is false (bool) 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 Follow along with this online setup: https://replit.com/join/qsordhjspi-vsajip .. code-block:: dart var cfg = Config.fromFile('main.cfg'); for (var key in ['logging.appenders.file.append', 'logging.appenders.file.filename', 'logging.appenders.file.level', 'logging.appenders.debug.level', 'logging.appenders.debug.filename', 'logging.appenders.debug.append']) { print('$key is ${cfg[key]} (${cfg[key].runtimeType})'); } which prints: .. code-block:: shell logging.appenders.file.append is false (bool) logging.appenders.file.filename is run/server.log (String) logging.appenders.file.level is INFO (String) logging.appenders.debug.level is DEBUG (String) logging.appenders.debug.filename is run/server-debug.log (String) logging.appenders.debug.append is false (bool) .. 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. Classes +++++++ The ``Location`` class ~~~~~~~~~~~~~~~~~~~~~~ This represents a source location and has two integer attributes, ``line`` and ``column``. The line must be positive and the column non-negative (newlines have an ending column of zero, as column 1 would have the first character of the next line). The ``ConfigException`` class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is an ``Exception`` subclass which has an error message in the ``message`` attribute and a ``Location`` instance in the ``location`` attribute indicating the position in the source of the error, where appropriate. (Some errors don't have a location, and in such cases the ``location`` attribute of the instance will be set to ``null``.) The ``Config`` class ~~~~~~~~~~~~~~~~~~~~ This class implements access to a CFG configuration. You'll generally interface to CFG files using this class. When you pass in a string or file path to the constructor the CFG source in the string or file is parsed and converted into an internal form which can then be accessed in a manner analogous to a Python dictionary. .. index:: single: Config; Dart class .. dart:class:: Config .. cssclass:: class-members-heading Attributes .. dart:attribute:: path 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 :dart:attr:`~Config.includePath`. When a configuration is loaded from a file, this attribute is automatically set. .. dart:attribute:: includePath A list of directories which is searched for included sub-configurations if the parent directory of :dart:attr:`~Config.path` (or the current directory, if `path` isn't set) doesn't contain them. .. dart:attribute:: context A mapping of strings to objects which is used whenever an identifier is encountered when evaluating an expression in a configuration file. .. dart:attribute:: noDuplicates = true Whether keys are allowed to be duplicated in mappings. Usually, a :class:`ConfigException` is raised if a duplicate key is seen. If ``false``, then if a duplicate key is seen, its value silently replaces the value associated with the earlier appearance of the same key. .. dart:attribute:: strictConversions = 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. .. cssclass:: class-members-heading Methods .. index:: single: Config; Config class .. dart:method:: Config() :noindex: Returns a new instance. .. dart:method:: factory Config.fromSource(String source) Returns a new instance initialized with a configuration provided in `source`. .. dart:method:: factory Config.fromFile(String path) Returns a new instance initialized with a configuration provided in the file specified by `path`. .. dart:method:: void loadFile(String path) Overwrite any configuration with a configuration provided in the file specified by `path`. .. dart:method:: HashMap asDict() Return's this instance's data as a `HashMap`. .. dart:method:: dynamic get(String key, {dynamic dv}) Retrieve a value from the instance whose key or path is represented by `key`. If the key or path is absent from the configuration and `dv` is provided, it is returned. Otherwise, a ``ConfigException`` is thrown. .. dart:method:: dynamic operator [](String key) Retrieve a value from the instance whose key or path is represented by `key`. If the key or path is absent from the configuration, a ``ConfigException`` is thrown. .. raw:: html