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