.. default-domain:: js .. meta:: :description: CFG configuration format JavaScript API :keywords: CFG, configuration, JavaScript .. _js-api: The CFG API for JavaScript -------------------------- The CFG reference implementation for JavaScript is written and tested using Node v12 or later. It's implemented in a module called ``config``. Installation ++++++++++++ The package can be installed for use from the `NPM package registry `_ using the package name ``cfg-lib``: .. code-block:: shell $ npm install cfg-lib There's a minimal example of a program and project that uses CFG `here `_. Exploration +++++++++++ To explore CFG functionality for JavaScript, we can just use the `node` Read-Eval-Print-Loop (REPL). You can invoke it using .. code-block:: shell $ node Getting Started with CFG in JavaScript ++++++++++++++++++++++++++++++++++++++ .. 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:: js > const config = require('cfg-lib'); undefined > let cfg = new config.Config("test0.cfg"); undefined Usage in a browser ~~~~~~~~~~~~~~~~~~ In a browser, API elements are exposed in the ``CFG`` namespace: .. code-block:: js let stream = CFG.makeStream('abc: 1'); let cfg = new CFG.Config(stream); let d = cfg.asDict(); As you have no access to a filesystem in the browser, a stream needs to be constructed from a string value and a configuration created from that. You can use the :func:`makeStream` function to do this. The above code would result in ``d`` having a value of the object ``{ abc: 1}``, as you might expect. You'll need to include the browser-specific version of the library from a CDN using a URL such as https://cdn.jsdelivr.net/npm/cfg-lib@0.1.1-4/dist/config.min.js - search the CDN for the latest version, which will be of the form X.Y.Z-N, where -N is optional and relates to versions updated due to packaging problems rather than any change to the actual functionality of the package. Access elements with keys ~~~~~~~~~~~~~~~~~~~~~~~~~ Accessing elements of the configuration is done using the `get` method: .. code-block:: js > cfg.get('a') 'Hello, ' > cfg.get('b') 'world!' Access elements with paths ~~~~~~~~~~~~~~~~~~~~~~~~~~ As well as simple keys, elements can also be accessed using :term:`path` strings: .. code-block:: js > cfg.get('c.d') 'e' .. include:: api-snip-02.inc .. code-block:: js > cfg.get('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:: js > cfg.get('christmas_morning') 2019-12-25T08:39:49.000Z Access to environment variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To access an environment variable, use a :term:`backtick-string` of the form ```$VARNAME```: .. code-block:: js > cfg.get('home') '/home/vinay' .. include:: api-snip-07.inc .. code-block:: js > cfg.get('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:: js > cfg.get('header_time') 30 > cfg.get('steady_time') 50 > cfg.get('trailer_time') 20 > cfg.get('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:: js > cfg.get('logging.appenders.file.filename') 'run/server.log' > cfg.get('redirects.freeotp.url') 'https://freeotp.github.io/' > cfg.get('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:: js > cfg.get('logging.appenders.file.level') 'INFO' > cfg.get('logging.appenders.file.layout') 'brief' > cfg.get('logging.appenders.file.append') true > cfg.get('logging.appenders.file.filename') 'run/server.log' > cfg.get('logging.appenders.error.append') false > cfg.get('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 :func:`Config.load` / :func:`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; JavaScript class .. class:: Config .. cssclass:: class-members-heading Variables .. attribute:: 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. .. attribute:: strictConversions: bool = true If ``true``, an exception is thrown if a :term:`backtick-string` can't be converted. This defaults to ``true`` if not provided. It's intended to help catch typos in backtick-strings. .. attribute:: context: object A mapping within which to look up identifiers during evaluation of AST nodes. .. attribute:: cached: bool = false If ``true``, an internal cache is used. .. attribute:: includePath: string[] A list of directories which is searched for included sub-configurations if the parent directory of `path` (or the current directory, if `path` isn't set) doesn't contain them. .. attribute:: path: string = null The path to the file from which this instance's configuration has been loaded. .. cssclass:: class-members-heading Constructors .. method:: Config(pathOrStream, options) Constructs an instance. :param pathOrStream: An optional path or stream. If undefined, an instance is created with no actual configuration loaded. A call to :meth:`load` or :meth:`loadFile` would be needed to actually make a usable instance. If a string is provided, it is assumed to be a path to a configuration file, and that file is used to initialize the configuration. If a stream is provided, it is used to obtain the configuration text. :param options: An optional object with attributes as shown above under the heading of Attributes. The values for the following keys are read and copied to the instance: * ``noDuplicates`` * ``strictConversions`` * ``includePath`` * ``cached`` .. cssclass:: class-members-heading Methods .. method:: load(stream) Load this instance with the configuration read from the provided reader. :param stream: 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. .. method:: loadFile(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. .. method:: 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). Functions +++++++++ .. function:: makeStream(s) Make a stream from a string. This is useful when constructing configurations in a browser, where you have no access to a filesystem. The string could be a literal, a computed value or obtained across the network via an AJAX request. :param str s: The string, which must be valid CFG source. :return: A stream object. The stream can be passed to the :class:`Config` constructor.