.. _ruby-api: .. default-domain:: ruby .. meta:: :description: CFG configuration format Ruby API :keywords: CFG, configuration, Ruby The CFG API for Ruby -------------------- The CFG reference implementation for the Ruby language assumes a Ruby version of 2.7 or later. Installation ++++++++++++ You can install the gem using ``gem install cfg-config``. There's a minimal example of a program that uses CFG `here `_. Exploration +++++++++++ To explore CFG functionality for Ruby, we the ``irb`` program, which is a Read-Eval-Print-Loop (REPL). You can invoke a shell using .. code-block:: shell $ irb Getting Started with CFG in Ruby ++++++++++++++++++++++++++++++++ A configuration is represented by an instance of the :class:`Config` class. The constructor for this class can be passed a filename or a stream 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:: rb-test0.cfg.txt Loading a configuration ~~~~~~~~~~~~~~~~~~~~~~~ The configuration above can be loaded as shown below. In the REPL shell: .. code-block:: ruby 2.7.1 :001 > require 'CFG/config' => true 2.7.1 :002 > include CFG => Object 2.7.1 :003 > cfg = CFG::Config::new("test0.cfg") Access elements with keys ~~~~~~~~~~~~~~~~~~~~~~~~~ Accessing elements of the configuration with a simple key is just like using a ``Hash``: .. code-block:: ruby 2.7.1 :004 > cfg['a'] => "Hello, " 2.7.1 :005 > cfg['b'] => "world!" Access elements with paths ~~~~~~~~~~~~~~~~~~~~~~~~~~ As well as simple keys, elements can also be accessed using :term:`path` strings: .. code-block:: ruby 2.7.1 :006 > cfg['c.d'] => "e" .. include:: api-snip-02.inc .. code-block:: ruby 2.7.1 :007 > cfg['f.g'] => "h" .. include:: api-snip-03.inc Access to date/time objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can also get native Ruby date/time objects from a configuration, by using an ISO date/time pattern in a :term:`backtick-string`: .. code-block:: ruby 2.7.1 :008 > cfg['christmas_morning'] => # Access to other Ruby objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Access to other Ruby objects is also possible using the :term:`backtick-string` syntax, provided that they are one of: * Environment variables * Public fields of public classes * Public static methods without parameters of public classes .. code-block:: ruby 2.7.1 :009 > require 'date' => false 2.7.1 :010 > DateTime::now - cfg['now'] => (-148657/86400000000000) Accessing the "now" element of the above configuration invokes the ``DateTime::now`` method and returns its value. You can see from the subtraction that the times are practically equivalent (the difference is the time between the two calls to ``DateTime::now`` - once when computing the configured value, and once in the interactive session. Access to environment variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To access an environment variable, use a :term:`backtick-string` of the form ```$VARNAME```: .. code-block:: ruby 2.7.1 :011 > cfg['home'] == ENV['HOME'] => true .. include:: api-snip-07.inc .. code-block:: ruby 2.7.1 :012 > 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:: ruby 2.7.1 :013 > cfg = CFG::Config::new("test0a.cfg") 2.7.1 :014 > cfg['header_time'] => 30.0 2.7.1 :015 > cfg['steady_time'] => 50.0 2.7.1 :016 > cfg['trailer_time'] => 20.0 2.7.1 :017 > 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: { 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' } }, 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 .. code-block:: ruby 2.7.1 :018 > cfg = CFG::Config::new('main.cfg') 2.7.1 :019 > cfg['logging.layouts.brief.pattern'] => "%d [%t] %p %c - %m%n" 2.7.1 :020 > cfg['redirects.freeotp.url'] => "https://freeotp.github.io/" 2.7.1 :021 > cfg['redirects.freeotp.permanent'] => false 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 .. code-block:: ruby 2.7.1 :022 > cfg['logging.appenders.file.append'] => true 2.7.1 :023 > cfg['logging.appenders.file.filename'] => "run/server.log" 2.7.1 :024 > cfg['logging.appenders.file.level'] => "INFO" 2.7.1 :02 > cfg['logging.appenders.debug.level'] => "DEBUG" 2.7.1 :026 > cfg['logging.appenders.debug.filename'] => "run/server-debug.log" 2.7.1 :027 > cfg['logging.appenders.debug.append'] => false .. 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 ``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.load_file` methods, the CFG source in the stream or file is parsed and converted into an internal form that can be queried. .. index:: single: Config; Ruby class .. class:: Config .. cssclass:: class-members-heading Variables .. var:: no_duplicates = true Whether duplicates keys are allowed when parsing a mapping or mapping body. If not allowed and duplicates are seen, a ``ConfigError`` is thrown. .. var:: strict_conversions = true If ``true``, a :class:`ConfigError` is raised if a string can't be converted. It's intended to help catch typos in backtick-strings. .. var:: context: Map = hashMapOf() A mapping within which to look up identifiers during evaluation of AST nodes. .. var:: cached = false If ``true``, an internal cache is used. .. var:: include_path: ArrayList = [] 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 The path to the file from which this instance's configuration has been loaded. .. cssclass:: class-members-heading Constructors .. constructor:: Config() Constructs an instance with no actual configuration loaded. A call to :fun:`load` or :fun:`load_file` would be needed to actually make a usable instance. .. constructor:: Config(stream) Construct this instance with the configuration read from the provided stream. .. constructor:: Config(path) Construct this instance with the configuration read from a file named by the provided path. .. cssclass:: class-members-heading Methods .. fun:: load(stream) Load this instance with the configuration read from the provided stream. :param stream: A stream for the configuration source. :exception ConfigError: If there is an I/O error when reading the source or an error in the configuration syntax. .. fun:: load_file(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 ConfigError: If there is an I/O error when reading the source or an error in the configuration syntax. .. fun:: get(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 InvalidPathError: If `key` is not present in the data and it cannot be parsed as a path. :throws BadIndexError: 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 ConfigError: If a key is not found or some other semantic error occurs (for example, an operation involving incompatible types). .. fun:: get(key, default_value) This works similarly to the method above, except that if a key isn't found, the ``default_value`` is returned. :param key: A string describing a simple key or a path in the configuration. :param default_value: A value to return if the key or path is not available in a configuration. :throws InvalidPathError: If `key` is not present in the data and it cannot be parsed as a path. :throws BadIndexError: 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 ConfigError: If some other semantic error occurs (for example, an operation involving incompatible types).