.. _go-api: .. default-domain:: go .. package:: config .. currentpackage:: config .. meta:: :description: CFG configuration format Go API :keywords: CFG, configuration, Go, Golang The CFG API for Go ------------------ The CFG reference implementation for Go is written and tested using Go >= 1.13. It's implemented in a module called ``config``. Installation ++++++++++++ You can use this package using ``go get github.com/vsajip/go-cfg-lib/config`` and then importing ``github.com/vsajip/go-cfg-lib/config`` in your code. There's a minimal example of a program that uses CFG `here `__. Exploration +++++++++++ To explore CFG functionality for Go, we use the `gore` Read-Eval-Print-Loop (REPL), which is available from `here `_. Once installed, you can invoke a shell using .. code-block:: shell $ gore Note that `gore` requires a version of Go that supports Go modules. `Note: We use a slightly modified version for this documentation, which prints the type of the objects explored in addition to printing their values.` Getting Started with CFG in Go ++++++++++++++++++++++++++++++ A configuration is represented by an instance of the :type:`Config` struct. A reference to one can be obtained using either the :func:`~config.NewConfig` or :func:`~config.FromFile` functions. The former creates an instance with no configuration loaded, while the latter initialises using a configuration in a specified file: 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. In the REPL shell: .. code-block:: go gore> :import os gore> :import config gore> os.Chdir(os.Getenv("PWD")) () gore> cfg, err := config.FromFile("test0.cfg") *config.Config(Config("test0.cfg" [7 items])) () The ``os.Chdir(os.Getenv("PWD"))`` dance is needed because of the way ``gore`` works, and it isn't relevant to this example. The ``()`` printed is just the error returned from the function. Access elements with keys ~~~~~~~~~~~~~~~~~~~~~~~~~ Accessing elements of the configuration with a simple key is not much harder than using a ``map[string]Any``: .. code-block:: go gore> cfg.Get("a") string(Hello, ) () gore> cfg.Get("b") string(world!) () You can see the types and values of the returned objects are as expected. Access elements with paths ~~~~~~~~~~~~~~~~~~~~~~~~~~ As well as simple keys, elements can also be accessed using :term:`path` strings: .. code-block:: go gore> cfg.Get("c.d") string(e) () .. include:: api-snip-02.inc .. code-block:: go gore> cfg.Get("f.g") string(h) () .. include:: api-snip-03.inc Access to date/time objects ~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can also get native Go date/time objects from a configuration, by using an ISO date/time pattern in a :term:`backtick-string`: .. code-block:: go gore> cfg.Get("christmas_morning") time.Time(2019-12-25 08:39:49 +0000 UTC) () Access to environment variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To access an environment variable, use a :term:`backtick-string` of the form ```$VARNAME```: .. code-block:: go gore> cfg.Get("home") string(/home/vinay) () .. include:: api-snip-07.inc .. code-block:: go gore> cfg.Get("foo") string(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:: go gore> cfg.Get("header_time") float64(30) () gore> cfg.Get("steady_time") float64(50) () gore> cfg.Get("trailer_time") float64(20) () gore> cfg.Get("log_file") string(/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:: go gore> cfg.Get("logging.appenders.file.filename") string(run/server.log) () gore> cfg.Get("redirects.freeotp.url") string(https://freeotp.github.io/) () gore> cfg.Get("redirects.freeotp.permanent") bool(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:: go gore> cfg.Get("logging.appenders.file.level") string(INFO) () gore> cfg.Get("logging.appenders.file.layout") string(brief) () gore> cfg.Get("logging.appenders.file.append") bool(true) () gore> cfg.Get("logging.appenders.file.filename") string(run/server.log) () gore> cfg.Get("logging.appenders.error.append") bool(false) () gore> cfg.Get("logging.appenders.error.filename") string(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. Types +++++ .. type:: Any This is a type alias for ``interface{}``. .. type:: Sequence This is a type alias for ``[]Any``. .. type:: Mapping This is a type alias for ``map[string]Any``. .. type:: Config This struct represents a configuration. .. cssclass:: class-members-heading Fields .. var:: NoDuplicates This is a ``bool`` which should be true (the default) if no duplicate keys are allowed when loading a configuration. If not allowed and duplicates are seen, an error will be returned. If duplicates are allowed, values seen later will overwrite values seen earlier for any keys that are duplicated. .. var:: StrictConversions This is a ``bool`` which should be true (the default) if special string conversions should always succeed. If ``true`` and a conversion can't be done, an error is returned. .. var:: IncludePath This is a ``[]string`` 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. .. var:: Path This is a ``string`` which should be the path to the file where the configuration was loaded from. .. var:: Context This is a ``*Mapping`` which can hold a mapping between identifiers and values. It's used to look up identifiers during evaluation of AST nodes. .. cssclass:: class-members-heading Methods .. function:: func (self *Config) Load(reader *io.Reader) error This loads the configuration from the specified stream. :param reader: The stream to read from. .. function:: func (self *Config) LoadFile(path string) error This loads the configuration from the specified file. :param path: The path to the file to read from. .. function:: func (self *Config) Get(key string) (Any, error) :param key: The particular configuration entry which is being sought. If the key is not in the data, an attempt will be made to parse it as a path. :returns: The value at the specified key or path, if present. .. function:: func (self *Config) GetWithDefault(key string, defaultValue Any) (Any, error) This gets the value at a specified key. If the key is not in the data, an attempt will be made to parse it as a path. If a key isn't found, the `defaultValue` is returned. .. function:: func (self *Config) AsDict() (map[string]Any, error) This converts the configuration into a map. Functions +++++++++ .. function:: func NewConfig() *Config Returns a new instance of :type:`Config` with default values for all fields. .. function:: func FromFile(path string) (*Config, error) Returns a configuration loaded from the specified file. :param path: The path to the configuration file to be loaded. :returns: A reference to the loaded configuration.