summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Pentchev <roam@debian.org>2019-01-12 23:09:28 +0200
committerPeter Pentchev <roam@debian.org>2019-01-12 23:09:28 +0200
commitf7ac332038f9dcfcd55612215c02e44fa5601405 (patch)
treeeda1d3fc693e8d693cbebdb7b5abfe098b7e8e52
parent47724c33371ce9125ae4511feb42bfb07d939378 (diff)
parent7515de39b089395b94d54869466dcefd08742c18 (diff)
Merge confget-2.2.0pre1 into the Debian branch.
-rw-r--r--python/README.md81
-rw-r--r--python/confget/__init__.py107
-rwxr-xr-xpython/confget/__main__.py295
-rw-r--r--python/confget/backend/__init__.py42
-rw-r--r--python/confget/backend/abstract.py55
-rw-r--r--python/confget/backend/http_get.py110
-rw-r--r--python/confget/backend/ini.py176
-rw-r--r--python/confget/defs.py59
-rw-r--r--python/confget/format.py221
-rw-r--r--python/confget/py.typed0
-rw-r--r--python/setup.cfg2
-rw-r--r--python/setup.py132
-rw-r--r--python/stubs/urllib/__init__.py0
-rw-r--r--python/stubs/urllib/__init__.pyi27
-rw-r--r--python/stubs/urllib/parse.pyi27
-rw-r--r--python/stubs/urllib/py.typed0
-rw-r--r--python/tox.ini143
-rw-r--r--python/unit_tests/__init__.py0
-rw-r--r--python/unit_tests/data/__init__.py31
-rw-r--r--python/unit_tests/data/defs.py295
-rw-r--r--python/unit_tests/data/load.py106
-rw-r--r--python/unit_tests/data/util.py45
-rw-r--r--python/unit_tests/test_run.py136
-rw-r--r--t/01-get-values.t88
-rw-r--r--t/02-check-values.t78
-rw-r--r--t/03-list-all.t53
-rw-r--r--t/04-bespoke-test-manpage.t (renamed from t/04-test-manpage.t)0
-rw-r--r--t/05-match-names.t32
-rw-r--r--t/06-get-default.t35
-rw-r--r--t/07-match-values.t45
-rw-r--r--t/08-bespoke-shell-quote.t (renamed from t/08-shell-quote.t)0
-rw-r--r--t/09-bespoke-regexp.t (renamed from t/09-regexp.t)0
-rw-r--r--t/10-bespoke-qsections.t (renamed from t/10-qsections.t)0
-rw-r--r--t/11-no-default.t53
-rw-r--r--t/12-last-value.t59
-rw-r--r--t/13-bespoke-features.t (renamed from t/13-features.t)0
-rw-r--r--t/14-bespoke-too-many.t (renamed from t/14-too-many.t)7
-rw-r--r--t/defs/schema/test-1.0.json176
-rw-r--r--t/defs/tests/01-get-values.json293
-rw-r--r--t/defs/tests/02-check-values.json258
-rw-r--r--t/defs/tests/03-list-all.json118
-rw-r--r--t/defs/tests/05-match-names.json81
-rw-r--r--t/defs/tests/06-get-default.json87
-rw-r--r--t/defs/tests/07-match-values.json166
-rw-r--r--t/defs/tests/11-no-default.json173
-rw-r--r--t/defs/tests/12-last-value.json239
-rw-r--r--t/defs/tools/encode.py105
-rwxr-xr-xt/defs/tools/generate.py146
48 files changed, 4189 insertions, 193 deletions
diff --git a/python/README.md b/python/README.md
new file mode 100644
index 0000000..890f138
--- /dev/null
+++ b/python/README.md
@@ -0,0 +1,81 @@
+confget - parse configuration files
+===================================
+
+The `confget` library parses configuration files (currently INI-style
+files and CGI `QUERY_STRING` environment variable) and allows a program
+to use the values defined in them. It provides various options for
+selecting the variable names and values to return and the configuration
+file sections to fetch them from.
+
+The `confget` library may also be used as a command-line tool with
+the same interface as the C implementation.
+
+The `confget` library is fully typed.
+
+Specifying configuration values for the backends
+------------------------------------------------
+
+The `confget.defs` module defines the `Config` class that is used to
+control the behavior of the various `confget` backends. Its main
+purpose is to specify the filename and, optionally, the section name for
+INI-style files, but other backends may use its fields in different ways.
+
+A `Config` object is created using the following parameters:
+- a list of variable names to query (may be empty)
+- `filename` (str, optional): the name of the file to open
+- `section` (str, default ""): the name of the section within the file
+- `section_specified` (bool, default false): if `section` is an empty
+ string, only fetch variables from the unnamed section at the start of
+ the file instead of defaulting to the first section in the file
+
+Parsing INI-style configuration files
+-------------------------------------
+
+The `confget` library's "ini" backend parses an INI-style configuration
+file. Its `read_file()` method parses the file and returns a dictionary
+of sections and the variables and their values within them:
+
+ import confget
+
+ cfg = confget.Config([], filename='config.ini')
+ ini = confget.BACKENDS['ini'](cfg)
+ data = ini.read_file()
+ print('Section names: {names}'.format(names=sorted(data.keys())))
+ print(data['server']['address'])
+
+Letting variables in a section override the default ones
+--------------------------------------------------------
+
+In some cases it is useful to have default values before the first
+named section in a file and then override some values in various
+sections. This may be useful for e.g. host-specific configuration
+kept in a section with the same name as the host.
+
+The `format` module in the `confget` library allows, among other
+filtering modes, to get the list of variables with a section
+overriding the default ones:
+
+ from confget import backend, format
+
+ cfg = format.FormatConfig(['foo'], filename='config.ini', section='first',
+ section_override=True)
+ ini = backend.BACKENDS['ini'](cfg)
+ data = ini.read_file()
+ res = format.filter_vars(cfg, data)
+ assert len(res) == 1, repr(res)
+ print(res[0].output_full)
+
+ cfg = format.FormatConfig(['foo'], filename='config.ini', section='second',
+ section_override=True)
+ ini = backend.BACKENDS['ini'](cfg)
+ data = ini.read_file()
+ res = format.filter_vars(cfg, data)
+ assert len(res) == 1, repr(res)
+ print(res[0].output_full)
+
+See the documentation of the `FormatConfig` class and the `filter_vars()`
+function in the `confget.format` module for more information and for
+a list of the various other filtering modes, all supported when
+the library is used as a command-line tool.
+
+Comments: Peter Pentchev <roam@ringlet.net>
diff --git a/python/confget/__init__.py b/python/confget/__init__.py
new file mode 100644
index 0000000..cbd8ee9
--- /dev/null
+++ b/python/confget/__init__.py
@@ -0,0 +1,107 @@
+# Copyright (c) 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+confget - a library for parsing configuration files
+
+The `confget` library parses configuration files (currently INI-style
+files and CGI `QUERY_STRING` environment variable) and allows a program
+to use the values defined in them. It provides various options for
+selecting the variable names and values to return and the configuration
+file sections to fetch them from.
+
+The `confget` library may also be used as a command-line tool with
+the same interface as the C implementation.
+
+The `confget` library is fully typed.
+
+Specifying configuration values for the backends
+------------------------------------------------
+
+The `confget.defs` module defines the `Config` class that is used to
+control the behavior of the various `confget` backends. Its main
+purpose is to specify the filename and, optionally, the section name for
+INI-style files, but other backends may use its fields in different ways.
+
+A `Config` object is created using the following parameters:
+- a list of variable names to query (may be empty)
+- `filename` (str, optional): the name of the file to open
+- `section` (str, default ""): the name of the section within the file
+- `section_specified` (bool, default false): if `section` is an empty
+ string, only fetch variables from the unnamed section at the start of
+ the file instead of defaulting to the first section in the file
+
+Parsing INI-style configuration files
+-------------------------------------
+
+The `confget` library's "ini" backend parses an INI-style configuration
+file. Its `read_file()` method parses the file and returns a dictionary
+of sections and the variables and their values within them:
+
+ import confget
+
+ cfg = confget.Config([], filename='config.ini')
+ ini = confget.BACKENDS['ini'](cfg)
+ data = ini.read_file()
+ print('Section names: {names}'.format(names=sorted(data.keys())))
+ print(data['server']['address'])
+
+Letting variables in a section override the default ones
+--------------------------------------------------------
+
+In some cases it is useful to have default values before the first
+named section in a file and then override some values in various
+sections. This may be useful for e.g. host-specific configuration
+kept in a section with the same name as the host.
+
+The `format` module in the `confget` library allows, among other
+filtering modes, to get the list of variables with a section
+overriding the default ones:
+
+ from confget import backend, format
+
+ cfg = format.FormatConfig(['foo'], filename='config.ini', section='first',
+ section_override=True)
+ ini = backend.BACKENDS['ini'](cfg)
+ data = ini.read_file()
+ res = format.filter_vars(cfg, data)
+ assert len(res) == 1, repr(res)
+ print(res[0].output_full)
+
+ cfg = format.FormatConfig(['foo'], filename='config.ini', section='second',
+ section_override=True)
+ ini = backend.BACKENDS['ini'](cfg)
+ data = ini.read_file()
+ res = format.filter_vars(cfg, data)
+ assert len(res) == 1, repr(res)
+ print(res[0].output_full)
+
+See the documentation of the `FormatConfig` class and the `filter_vars()`
+function in the `confget.format` module for more information and for
+a list of the various other filtering modes, all supported when
+the library is used as a command-line tool.
+"""
+
+from .defs import Config, VERSION_STRING # noqa: F401
+from .backend import BACKENDS # noqa: F401
diff --git a/python/confget/__main__.py b/python/confget/__main__.py
new file mode 100755
index 0000000..919fcae
--- /dev/null
+++ b/python/confget/__main__.py
@@ -0,0 +1,295 @@
+# Copyright (c) 2018, 2019 Peter Pentchev
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+The command-line interface to the confget module: specify some parameters
+through command-line options, then display variable values or names.
+"""
+
+from __future__ import print_function
+
+import argparse
+import sys
+
+from . import backend
+from . import defs
+from . import format as fmt
+
+try:
+ from typing import List, Optional
+
+ _TYPING_USED = (defs, List, Optional)
+except ImportError:
+ pass
+
+
+class MainConfig(fmt.FormatConfig):
+ # pylint: disable=too-few-public-methods,too-many-instance-attributes
+ """
+ Extend the format config class with some output settings.
+
+ Add the following settings:
+ - check_only (boolean): only check whether a variable is defined
+ - query_sections (boolean): only display the section names
+ """
+
+ def __init__(self, # type: MainConfig
+ check_only, # type: bool
+ filename, # type: Optional[str]
+ list_all, # type: bool
+ match_regex, # type: bool
+ match_var_names, # type: bool
+ match_var_values, # type: Optional[str]
+ name_prefix, # type: Optional[str]
+ query_sections, # type: bool
+ section, # type: str
+ section_override, # type: bool
+ section_specified, # type: bool
+ show_var_name, # type: bool
+ shell_escape, # type: bool
+ varnames # type: List[str]
+ ): # type: (...) -> None
+ # pylint: disable=too-many-arguments,too-many-locals
+ """ Store the specified configuration values. """
+ super(MainConfig, self).__init__(
+ filename=filename,
+ list_all=list_all,
+ match_regex=match_regex,
+ match_var_names=match_var_names,
+ match_var_values=match_var_values,
+ name_prefix=name_prefix,
+ section=section,
+ section_specified=section_specified,
+ section_override=section_override,
+ show_var_name=show_var_name,
+ shell_escape=shell_escape,
+ varnames=varnames,
+ )
+ self.check_only = check_only
+ self.query_sections = query_sections
+
+
+def version():
+ # type: () -> None
+ """
+ Display program version information.
+ """
+ print('confget ' + defs.VERSION_STRING)
+
+
+def features(name):
+ # type: (Optional[str]) -> None
+ """
+ Display a list of the features supported by the program.
+ """
+ if name is None:
+ print(' '.join([
+ '{name}={version}'.format(name=item[0], version=item[1])
+ for item in defs.FEATURES
+ ]))
+ else:
+ ver = dict(defs.FEATURES).get(name, None)
+ if ver is None:
+ sys.exit(1)
+ print(ver)
+
+
+def output_check_only(cfg, data):
+ # type: (MainConfig, defs.ConfigData) -> None
+ """ Check whether the variable is present. """
+ if cfg.section not in data:
+ sys.exit(1)
+ elif cfg.varnames[0] not in data[cfg.section]:
+ sys.exit(1)
+ sys.exit(0)
+
+
+def output_vars(cfg, data):
+ # type: (MainConfig, defs.ConfigData) -> None
+ """ Output the variable values. """
+ for vitem in fmt.filter_vars(cfg, data):
+ print(vitem.output_full)
+
+
+def output_sections(data):
+ # type: (defs.ConfigData) -> None
+ """ Output the section names. """
+ for name in sorted(data.keys()):
+ if name != '':
+ print(name)
+
+
+def validate_options(args, backend_name):
+ # type: (argparse.Namespace, str) -> None
+ """
+ Make sure the command-line options are not used in an invalid combination.
+ """
+ query_sections = args.query == 'sections'
+
+ if args.list_all or query_sections:
+ if args.varnames:
+ sys.exit('Only a single query at a time, please!')
+ elif args.match_var_names:
+ if not args.varnames:
+ sys.exit('No patterns to match against')
+ elif args.check_only and len(args.varnames) > 1:
+ sys.exit('Only a single query at a time, please!')
+ elif not args.varnames:
+ sys.exit('No variables specified to query')
+
+ if query_sections and backend_name != 'ini':
+ sys.exit("The query for sections is only supported for "
+ "the 'ini' backend for the present")
+
+
+def check_option_conflicts(args):
+ # type: (argparse.Namespace) -> None
+ """ Make sure that the command-line options do not conflict. """
+ total = int(args.query is not None) + int(args.match_var_names) + \
+ int(args.list_all) + \
+ int(bool(args.varnames) and
+ not (args.match_var_names or args.query == 'feature'))
+ if total > 1:
+ sys.exit('Only a single query at a time, please!')
+
+
+def main():
+ # type: () -> None
+ """
+ The main program: parse arguments, do things.
+ """
+ parser = argparse.ArgumentParser(
+ prog='confget',
+ usage='''
+ confget [-t ini] -f filename [-s section] varname...
+ confget -V | -h | --help | --version
+ confget -q features''')
+ parser.add_argument('-c', action='store_true', dest='check_only',
+ help='check whether the variables are defined in '
+ 'the file')
+ parser.add_argument('-f', type=str, dest='filename',
+ help='specify the configuration file name')
+ parser.add_argument('-L', action='store_true', dest='match_var_names',
+ help='specify which variables to display')
+ parser.add_argument('-l', action='store_true', dest='list_all',
+ help='list all variables in the specified section')
+ parser.add_argument('-m', type=str, dest='match_var_values',
+ help='only display variables with values that match '
+ 'the specified pattern')
+ parser.add_argument('-N', action='store_true', dest='show_var_name',
+ help='always display the variable name')
+ parser.add_argument('-n', action='store_true', dest='hide_var_name',
+ help='never display the variable name')
+ parser.add_argument('-O', action='store_true', dest='section_override',
+ help='allow variables in the specified section to '
+ 'override those placed before any '
+ 'section definitions')
+ parser.add_argument('-p', type=str, dest='name_prefix',
+ help='display this string before the variable name')
+ parser.add_argument('-q', type=str, dest='query', choices=[
+ 'feature',
+ 'features',
+ 'sections'
+ ], help='query for a specific type of information, e.g. the list of '
+ 'sections defined in ' 'the configuration file')
+ parser.add_argument('-S', action='store_true', dest='shell_quote',
+ help='quote the values suitably for the Bourne shell')
+ parser.add_argument('-s', type=str, dest='section',
+ help='specify the configuration file section')
+ parser.add_argument('-t', type=str, default='ini', dest='backend',
+ help='specify the configuration file type')
+ parser.add_argument('-V', '--version', action='store_true',
+ help='display program version information and exit')
+ parser.add_argument('-x', action='store_true', dest='match_regex',
+ help='treat the match patterns as regular expressions')
+ parser.add_argument('varnames', nargs='*',
+ help='the variable names to query')
+
+ args = parser.parse_args()
+ if args.version:
+ version()
+ return
+
+ check_option_conflicts(args)
+
+ if args.query == 'features':
+ if args.varnames:
+ sys.exit('No arguments to -q features')
+ features(None)
+ return
+ if args.query == 'feature':
+ if len(args.varnames) != 1:
+ sys.exit('Only a single feature name expected')
+ features(args.varnames[0])
+ return
+
+ query_sections = args.query == 'sections'
+
+ cfg = MainConfig(
+ check_only=args.check_only,
+ filename=args.filename,
+ list_all=args.list_all,
+ match_regex=args.match_regex,
+ match_var_names=args.match_var_names,
+ match_var_values=args.match_var_values,
+ name_prefix=args.name_prefix,
+ query_sections=query_sections,
+ section=args.section if args.section is not None else '',
+ section_override=args.section_override,
+ section_specified=args.section is not None,
+ shell_escape=args.shell_quote,
+ show_var_name=args.show_var_name or
+ ((args.match_var_names or args.list_all or len(args.varnames) > 1) and
+ not args.hide_var_name),
+ varnames=args.varnames,
+ )
+
+ matched_backends = [name for name in sorted(backend.BACKENDS.keys())
+ if name.startswith(args.backend)]
+ if not matched_backends:
+ sys.exit('Unknown backend "{name}", use "list" for a list'
+ .format(name=args.backend))
+ elif len(matched_backends) > 1:
+ sys.exit('Ambiguous backend "{name}": {lst}'
+ .format(name=args.backend, lst=' '.join(matched_backends)))
+ back = backend.BACKENDS[matched_backends[0]]
+
+ validate_options(args, matched_backends[0])
+
+ try:
+ cfgp = back(cfg)
+ except Exception as exc: # pylint: disable=broad-except
+ sys.exit(str(exc))
+ data = cfgp.read_file()
+
+ if cfg.check_only:
+ output_check_only(cfg, data)
+ elif query_sections:
+ output_sections(data)
+ else:
+ output_vars(cfg, data)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/python/confget/backend/__init__.py b/python/confget/backend/__init__.py
new file mode 100644
index 0000000..51be726
--- /dev/null
+++ b/python/confget/backend/__init__.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+Provide an interface to all the configuration format backends.
+"""
+
+from . import abstract, http_get, ini
+
+try:
+ from typing import Dict, Type
+
+ _TYPING_USED = (Dict, Type, abstract)
+except ImportError:
+ pass
+
+
+BACKENDS = {
+ 'http_get': http_get.HTTPGetBackend,
+ 'ini': ini.INIBackend,
+} # type: Dict[str, Type[abstract.Backend]]
diff --git a/python/confget/backend/abstract.py b/python/confget/backend/abstract.py
new file mode 100644
index 0000000..1ee2c8d
--- /dev/null
+++ b/python/confget/backend/abstract.py
@@ -0,0 +1,55 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+An abstract metaclass for confget backends.
+"""
+
+import abc
+
+import six
+
+from .. import defs
+
+try:
+ from typing import Dict
+
+ _TYPING_USED = (defs, Dict)
+except ImportError:
+ pass
+
+
+@six.add_metaclass(abc.ABCMeta)
+class Backend(object):
+ # pylint: disable=too-few-public-methods
+ """ An abstract confget parser backend. """
+ def __init__(self, cfg):
+ # type: (Backend, defs.Config) -> None
+ self._cfg = cfg
+
+ @abc.abstractmethod
+ def read_file(self):
+ # type: (Backend) -> defs.ConfigData
+ """ Read and parse the configuration file, invoke the callbacks. """
+ raise NotImplementedError('Backend.read_file')
diff --git a/python/confget/backend/http_get.py b/python/confget/backend/http_get.py
new file mode 100644
index 0000000..a6f1ba4
--- /dev/null
+++ b/python/confget/backend/http_get.py
@@ -0,0 +1,110 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+A confget backend for reading INI-style files.
+"""
+
+import os
+import re
+import urllib
+
+from .. import defs
+
+from . import abstract
+
+try:
+ from typing import Dict, List
+
+ _TYPING_USED = (defs, Dict, List)
+except ImportError:
+ pass
+
+
+try:
+ import urllib.parse # pylint: disable=ungrouped-imports
+
+ urllib_unquote = urllib.parse.unquote # pylint: disable=invalid-name
+except ImportError:
+ urllib_unquote = urllib.unquote # pylint: disable=invalid-name,no-member
+
+
+RE_ENTITY = re.compile(r'^ (?P<full> [a-zA-Z0-9_]+ ; )', re.X)
+
+
+class HTTPGetBackend(abstract.Backend):
+ # pylint: disable=too-few-public-methods
+ """ Parse INI-style configuration files. """
+
+ def __init__(self, cfg):
+ # type: (HTTPGetBackend, defs.Config) -> None
+ super(HTTPGetBackend, self).__init__(cfg)
+
+ if self._cfg.filename is not None:
+ raise ValueError('No config filename expected')
+
+ qname = 'QUERY_STRING' if self._cfg.section == '' \
+ else self._cfg.section
+ qval = os.environ.get(qname, None)
+ if qval is None:
+ raise ValueError('No "{qname}" variable in the environment'
+ .format(qname=qname))
+ self.query_string = qval
+
+ def read_file(self):
+ # type: (HTTPGetBackend) -> defs.ConfigData
+ def split_by_amp(line):
+ # type: (str) -> List[str]
+ """ Split a line by "&" or "&amp;" tokens. """
+ if not line:
+ return []
+
+ start = end = 0
+ while True:
+ pos = line[start:].find('&')
+ if pos == -1:
+ return [line]
+ if line[pos + 1:].startswith('amp;'):
+ end = pos + 4
+ break
+ entity = RE_ENTITY.match(line[pos + 1:])
+ if entity is None:
+ end = pos
+ break
+ start = pos + len(entity.group('full'))
+
+ return [line[:pos]] + split_by_amp(line[end + 1:])
+
+ data = {} # type: Dict[str, str]
+ fragments = split_by_amp(self.query_string)
+ for varval in fragments:
+ fields = varval.split('=')
+ if len(fields) == 1:
+ fields.append('')
+ elif len(fields) != 2:
+ raise ValueError('Invalid query string component: "{varval}"'
+ .format(varval=varval))
+ data[urllib_unquote(fields[0])] = urllib_unquote(fields[1])
+
+ return {self._cfg.section: data}
diff --git a/python/confget/backend/ini.py b/python/confget/backend/ini.py
new file mode 100644
index 0000000..44b9470
--- /dev/null
+++ b/python/confget/backend/ini.py
@@ -0,0 +1,176 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+A confget backend for reading INI-style files.
+"""
+
+import re
+import sys
+
+from .. import defs
+
+from . import abstract
+
+try:
+ from typing import Callable, Dict, Match, NamedTuple, Pattern
+
+ _TYPING_USED = (defs, Dict)
+
+ MatcherType = NamedTuple('MatcherType', [
+ ('regex', Pattern[str]),
+ ('handle', Callable[[
+ Match[str],
+ Dict[str, str],
+ defs.Config,
+ defs.ConfigData,
+ ], None]),
+ ])
+except ImportError:
+ import collections
+
+ MatcherType = collections.namedtuple('MatcherType', [ # type: ignore
+ 'regex',
+ 'handle',
+ ])
+
+
+class INIBackend(abstract.Backend):
+ # pylint: disable=too-few-public-methods
+ """ Parse INI-style configuration files. """
+
+ def __init__(self, cfg, encoding='UTF-8'):
+ # type: (INIBackend, defs.Config, str) -> None
+ super(INIBackend, self).__init__(cfg)
+
+ if self._cfg.filename is None:
+ raise ValueError('No config filename specified')
+ elif self._cfg.filename == '-':
+ infile = sys.stdin
+ else:
+ infile = open(self._cfg.filename, mode='r')
+
+ reconfigure = getattr(infile, 'reconfigure', None)
+ if reconfigure is not None:
+ reconfigure(encoding=encoding)
+
+ self.infile = infile
+
+ def read_file(self):
+ # type: (INIBackend) -> defs.ConfigData
+ state = {
+ 'section': '',
+ 'name': '',
+ 'value': '',
+ 'cont': '',
+ 'found': '',
+ }
+ res = {'': {}} # type: defs.ConfigData
+
+ def handle_section(match, # type: Match[str]
+ state, # type: Dict[str, str]
+ cfg, # type: defs.Config
+ res # type: defs.ConfigData
+ ): # type: (...) -> None
+ """ Handle a section heading: store the name. """
+ state['section'] = match.group('name')
+ if state['section'] not in res:
+ res[state['section']] = {}
+ if not (cfg.section_specified or cfg.section or state['found']):
+ cfg.section = state['section']
+ state['found'] = state['section']
+
+ def handle_comment(_match, # type: Match[str]
+ _state, # type: Dict[str, str]
+ _cfg, # type: defs.Config
+ _res # type: defs.ConfigData
+ ): # type: (...) -> None
+ """ Handle a comment line: ignore it. """
+ pass # pylint: disable=unnecessary-pass
+
+ def handle_variable(match, # type: Match[str]
+ state, # type: Dict[str, str]
+ _cfg, # type: defs.Config
+ res # type: defs.ConfigData
+ ): # type: (...) -> None
+ """ Handle an assignment: store, check for a continuation. """
+ state['name'] = match.group('name')
+ state['value'] = match.group('value')
+ state['cont'] = match.group('cont')
+ state['found'] = state['name']
+ if not state['cont']:
+ res[state['section']][state['name']] = state['value']
+
+ matches = [
+ MatcherType(
+ regex=re.compile(r'^ \s* (?: [#;] .* )? $', re.X),
+ handle=handle_comment,
+ ),
+ MatcherType(
+ regex=re.compile(r'''
+ ^
+ \s* \[ \s*
+ (?P<name> [^\]]+? )
+ \s* \] \s*
+ $''',
+ re.X),
+ handle=handle_section,
+ ),
+ MatcherType(
+ regex=re.compile(r'''
+ ^
+ \s*
+ (?P<name> \S+ )
+ \s* = \s*
+ (?P<value> .*? )
+ \s*
+ (?P<cont> [\\] )?
+ $''', re.X),
+ handle=handle_variable,
+ ),
+ ]
+
+ for line in self.infile.readlines():
+ line = line.rstrip('\r\n')
+ if state['cont']:
+ if line.endswith('\\'):
+ line, state['cont'] = line[:-1], line[-1]
+ else:
+ state['cont'] = ''
+ state['value'] += line
+ if not state['cont']:
+ res[state['section']][state['name']] = state['value']
+ continue
+
+ for data in matches:
+ match = data.regex.match(line)
+ if match is None:
+ continue
+ data.handle(match, state, self._cfg, res)
+ break
+ else:
+ raise ValueError('Unexpected line in {fname}: {line}'
+ .format(fname=self._cfg.filename, line=line))
+
+ return res
diff --git a/python/confget/defs.py b/python/confget/defs.py
new file mode 100644
index 0000000..21ae7c5
--- /dev/null
+++ b/python/confget/defs.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+Common definitions for the confget configuration parsing library.
+"""
+
+try:
+ from typing import Dict, List, Optional
+
+ _TYPING_USED = (List, Optional)
+
+ ConfigData = Dict[str, Dict[str, str]]
+except ImportError:
+ pass
+
+
+VERSION_STRING = '2.2.0'
+FEATURES = [
+ ('BASE', VERSION_STRING),
+]
+
+
+class Config(object):
+ # pylint: disable=too-few-public-methods
+ """ Base class for the internal confget configuration. """
+
+ def __init__(self, # type: Config
+ varnames, # type: List[str]
+ filename=None, # type: Optional[str]
+ section='', # type: str
+ section_specified=False, # type: bool
+ ): # type: (...) -> None
+ """ Store the specified configuration values. """
+ self.filename = filename
+ self.section = section
+ self.section_specified = section_specified
+ self.varnames = varnames
diff --git a/python/confget/format.py b/python/confget/format.py
new file mode 100644
index 0000000..d2265b5
--- /dev/null
+++ b/python/confget/format.py
@@ -0,0 +1,221 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+Filter and format a subset of the configuration variables.
+"""
+
+import fnmatch
+import re
+
+try:
+ from typing import Callable, Dict, Iterable, NamedTuple, List, Optional
+
+ _TYPING_USED = (Callable, Dict, Iterable, List, Optional)
+
+ FormatOutput = NamedTuple('FormatOutput', [
+ ('name', str),
+ ('value', str),
+ ('output_name', str),
+ ('output_value', str),
+ ('output_full', str),
+ ])
+except ImportError:
+ import collections
+
+ FormatOutput = collections.namedtuple('FormatOutput', [ # type: ignore
+ 'name',
+ 'value',
+ 'output_name',
+ 'output_value',
+ 'output_full',
+ ])
+
+from . import defs
+
+
+class FormatConfig(defs.Config):
+ # pylint: disable=too-few-public-methods,too-many-instance-attributes
+ """
+ Extend the config class with some output settings.
+
+ Add the following settings:
+ - list_all (boolean): list all variables, not just a subset
+ - match_regex (boolean): for match_var_names and match_var_values,
+ perform regular expression matches instead of filename pattern ones
+ - match_var_names (boolean): treat the variable names specified as
+ patterns and display all variables that match those
+ - match_var_values (string): display only the variables with values
+ that match this pattern
+ - name_prefix (string): when displaying variable names, prefix them
+ with this string
+ - show_var_name (boolean): display the variable names, not just
+ the values
+ - shell_escape (boolean): format the values in a manner suitable for
+ the Bourne shell
+ """
+
+ def __init__(self, # type: FormatConfig
+ varnames, # type: List[str]
+ filename=None, # type: Optional[str]
+ list_all=False, # type: bool
+ match_regex=False, # type: bool
+ match_var_names=False, # type: bool
+ match_var_values=None, # type: Optional[str]
+ name_prefix=None, # type: Optional[str]
+ section='', # type: str
+ section_override=False, # type: bool
+ section_specified=False, # type: bool
+ show_var_name=False, # type: bool
+ shell_escape=False, # type: bool
+ ): # type: (...) -> None
+ # pylint: disable=too-many-arguments
+ """ Store the specified configuration values. """
+ super(FormatConfig, self).__init__(
+ filename=filename,
+ section=section,
+ section_specified=section_specified,
+ varnames=varnames,
+ )
+ self.list_all = list_all
+ self.match_regex = match_regex
+ self.match_var_names = match_var_names
+ self.match_var_values = match_var_values
+ self.name_prefix = name_prefix
+ self.section_override = section_override
+ self.shell_escape = shell_escape
+ self.show_var_name = show_var_name
+
+ def __repr__(self):
+ # type: (FormatConfig) -> str
+ return '{tname}({varnames}, {attrs})'.format(
+ tname=type(self).__name__,
+ varnames=repr(self.varnames),
+ attrs=', '.join([
+ '{name}={value}'.format(
+ name=name, value=repr(getattr(self, name)))
+ for name in [
+ 'filename',
+ 'list_all',
+ 'match_regex',
+ 'match_var_names',
+ 'match_var_values',
+ 'name_prefix',
+ 'section',
+ 'section_override',
+ 'section_specified',
+ 'show_var_name',
+ 'shell_escape',
+ ]
+ ]))
+
+
+def get_check_function(cfg, patterns):
+ # type: (FormatConfig, List[str]) -> Callable[[str], bool]
+ """
+ Get a function that determines whether a variable name should be
+ included in the displayed subset.
+ """
+ if cfg.match_regex:
+ re_vars = [re.compile(name) for name in patterns]
+
+ def check_re_vars(key):
+ # type: (str) -> bool
+ """ Check that the key matches any of the specified regexes. """
+ return any(rex.search(key) for rex in re_vars)
+
+ return check_re_vars
+
+ def check_fn_vars(key):
+ # type: (str) -> bool
+ """ Check that the key matches any of the specified patterns. """
+ return any(fnmatch.fnmatch(key, pattern)
+ for pattern in patterns)
+
+ return check_fn_vars
+
+
+def get_varnames(cfg, sect_data):
+ # type: (FormatConfig, Dict[str, str]) -> Iterable[str]
+ """ Get the variable names that match the configuration requirements. """
+ if cfg.list_all:
+ varnames = sect_data.keys() # type: Iterable[str]
+ elif cfg.match_var_names:
+ check_var = get_check_function(cfg, cfg.varnames)
+ varnames = [name for name in sect_data.keys() if check_var(name)]
+ else:
+ varnames = [name for name in cfg.varnames if name in sect_data]
+
+ if not cfg.match_var_values:
+ return varnames
+
+ check_value = get_check_function(cfg, [cfg.match_var_values])
+ return [name for name in varnames if check_value(sect_data[name])]
+
+
+def filter_vars(cfg, data):
+ # type: (FormatConfig, defs.ConfigData) -> Iterable[FormatOutput]
+ """
+ Filter the variables in the configuration file according to
+ the various criteria specified in the configuration.
+ Return an iterable of FormatOutput structures allowing the caller to
+ process the variable names and values in various ways.
+ """
+ if cfg.section_override:
+ sect_data = data['']
+ else:
+ sect_data = {}
+ if cfg.section in data:
+ sect_data.update(data[cfg.section])
+
+ varnames = get_varnames(cfg, sect_data)
+ res = [] # type: List[FormatOutput]
+ for name in sorted(varnames):
+ if cfg.name_prefix:
+ output_name = cfg.name_prefix + name
+ else:
+ output_name = name
+
+ value = sect_data[name]
+ if cfg.shell_escape:
+ output_value = "'{esc}'".format(
+ esc="'\"'\"'".join(value.split("'")))
+ else:
+ output_value = value
+
+ if cfg.show_var_name:
+ output_full = '{name}={value}'.format(name=output_name,
+ value=output_value)
+ else:
+ output_full = output_value
+
+ res.append(FormatOutput(
+ name=name,
+ value=value,
+ output_name=output_name,
+ output_value=output_value,
+ output_full=output_full,
+ ))
+
+ return res
diff --git a/python/confget/py.typed b/python/confget/py.typed
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/confget/py.typed
diff --git a/python/setup.cfg b/python/setup.cfg
new file mode 100644
index 0000000..2a9acf1
--- /dev/null
+++ b/python/setup.cfg
@@ -0,0 +1,2 @@
+[bdist_wheel]
+universal = 1
diff --git a/python/setup.py b/python/setup.py
new file mode 100644
index 0000000..2d9f620
--- /dev/null
+++ b/python/setup.py
@@ -0,0 +1,132 @@
+#!/usr/bin/python3
+#
+# Copyright (c) 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+Setup infrastructure for confget, the configuration file parser.
+"""
+
+import re
+import setuptools # type: ignore
+
+
+RE_VERSION = r'''^
+ \s* VERSION_STRING \s* = \s* '
+ (?P<version>
+ (?: 0 | [1-9][0-9]* ) # major
+ \. (?: 0 | [1-9][0-9]* ) # minor
+ \. (?: 0 | [1-9][0-9]* ) # patchlevel
+ (?: \. [a-zA-Z0-9]+ )? # optional addendum (dev1, beta3, etc.)
+ )
+ ' \s*
+ $'''
+
+
+def get_version():
+ # type: () -> str
+ """ Get the version string from the module's __init__ file. """
+ found = None
+ re_semver = re.compile(RE_VERSION, re.X)
+ with open('confget/defs.py') as init:
+ for line in init.readlines():
+ match = re_semver.match(line)
+ if not match:
+ continue
+ assert found is None
+ found = match.group('version')
+
+ assert found is not None
+ return found
+
+
+def get_long_description():
+ # type: () -> str
+ """ Get the package long description from the README file. """
+ with open('README.md') as readme:
+ return readme.read()
+
+
+setuptools.setup(
+ name='confget',
+ version=get_version(),
+
+ description='Parse configuration files and extract values from them',
+ long_description=get_long_description(),
+ long_description_content_type='text/markdown',
+
+ author='Peter Pentchev',
+ author_email='roam@ringlet.net',
+ url='https://devel.ringlet.net/textproc/confget/',
+
+ packages=['confget', 'confget.backend'],
+ package_data={
+ 'confget': [
+ # The typed module marker
+ 'py.typed',
+ ],
+ },
+
+ install_requires=[
+ 'six',
+ ],
+
+ license='BSD-2',
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+
+ 'Environment :: Console',
+
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: System Administrators',
+
+ 'License :: DFSG approved',
+ 'License :: Freely Distributable',
+ 'License :: OSI Approved :: BSD License',
+
+ 'Operating System :: POSIX',
+ 'Operating System :: Unix',
+
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+
+ 'Topic :: Software Development :: Libraries',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Topic :: Utilities',
+ ],
+
+ entry_points={
+ 'console_scripts': [
+ 'confget=confget.__main__:main',
+ ],
+ },
+
+ zip_safe=True,
+)
diff --git a/python/stubs/urllib/__init__.py b/python/stubs/urllib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/stubs/urllib/__init__.py
diff --git a/python/stubs/urllib/__init__.pyi b/python/stubs/urllib/__init__.pyi
new file mode 100644
index 0000000..d86965b
--- /dev/null
+++ b/python/stubs/urllib/__init__.pyi
@@ -0,0 +1,27 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+def unquote(name):
+ # type: (str) -> str
+ ...
diff --git a/python/stubs/urllib/parse.pyi b/python/stubs/urllib/parse.pyi
new file mode 100644
index 0000000..d86965b
--- /dev/null
+++ b/python/stubs/urllib/parse.pyi
@@ -0,0 +1,27 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+def unquote(name):
+ # type: (str) -> str
+ ...
diff --git a/python/stubs/urllib/py.typed b/python/stubs/urllib/py.typed
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/stubs/urllib/py.typed
diff --git a/python/tox.ini b/python/tox.ini
new file mode 100644
index 0000000..57f11d4
--- /dev/null
+++ b/python/tox.ini
@@ -0,0 +1,143 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+[tox]
+envlist = pep8,mypy2,mypy3,prove_2,prove_2_nt,prove_3,unit_tests_2,unit_tests_2_nt,unit_tests_3,pylint
+skipsdist = True
+
+[testenv:pep8]
+basepython = python3
+deps =
+ flake8
+commands =
+ flake8 confget setup.py unit_tests ../t/defs/tools/generate.py
+
+[testenv:mypy2]
+basepython = python3
+deps =
+ mypy
+setenv =
+ MYPYPATH={toxinidir}/stubs
+commands =
+ mypy --strict --py2 confget setup.py
+ mypy --strict --py2 --allow-untyped-decorators unit_tests
+
+[testenv:mypy3]
+basepython = python3
+deps =
+ mypy
+setenv =
+ MYPYPATH={toxinidir}/stubs
+commands =
+ mypy --strict confget setup.py ../t/defs/tools/generate.py
+ mypy --strict --allow-untyped-decorators unit_tests
+
+[testenv:pylint]
+basepython = python3
+deps =
+ ddt
+ pylint
+ pytest
+commands =
+ pylint --disable=useless-object-inheritance,duplicate-code confget setup.py unit_tests ../t/defs/tools/generate.py
+
+[testenv:unit_tests_2]
+basepython = python2
+deps =
+ ddt
+ pytest
+ typing
+setenv =
+ TESTDIR={toxinidir}/../t
+commands =
+ pytest -s -vv unit_tests
+
+[testenv:unit_tests_2_nt]
+basepython = python2
+deps =
+ ddt
+ pytest
+setenv =
+ TESTDIR={toxinidir}/../t
+commands =
+ pytest -s -vv unit_tests
+
+[testenv:unit_tests_3]
+basepython = python3
+deps =
+ ddt
+ pytest
+setenv =
+ TESTDIR={toxinidir}/../t
+commands =
+ pytest -s -vv unit_tests
+
+[testenv:generate]
+basepython = python3
+deps =
+ six
+whitelist_externals =
+ sh
+commands =
+ sh -c 'cd ../t && env PYTHONPATH={toxinidir} python defs/tools/generate.py'
+
+[testenv:prove_2]
+basepython = python2
+deps =
+ six
+ typing
+setenv =
+ CONFGET=python -m confget
+ MANPAGE={toxinidir}/../confget.1
+ TESTDIR={toxinidir}/../t
+whitelist_externals =
+ prove
+commands =
+ prove ../t
+
+[testenv:prove_2_nt]
+basepython = python2
+deps =
+ six
+setenv =
+ CONFGET=python -m confget
+ MANPAGE={toxinidir}/../confget.1
+ TESTDIR={toxinidir}/../t
+whitelist_externals =
+ prove
+commands =
+ prove ../t
+
+[testenv:prove_3]
+basepython = python3
+deps =
+ six
+setenv =
+ CONFGET=python -m confget
+ MANPAGE={toxinidir}/../confget.1
+ TESTDIR={toxinidir}/../t
+whitelist_externals =
+ prove
+commands =
+ prove ../t
diff --git a/python/unit_tests/__init__.py b/python/unit_tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/unit_tests/__init__.py
diff --git a/python/unit_tests/data/__init__.py b/python/unit_tests/data/__init__.py
new file mode 100644
index 0000000..fbf1975
--- /dev/null
+++ b/python/unit_tests/data/__init__.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+Data structures and definitions for the confget unit tests.
+"""
+
+from .defs import CMDLINE_OPTIONS, XFORM, TestDef # noqa: F401
+from .load import load_all_tests # noqa: F401
+from .util import shell_escape # noqa: F401
diff --git a/python/unit_tests/data/defs.py b/python/unit_tests/data/defs.py
new file mode 100644
index 0000000..759487a
--- /dev/null
+++ b/python/unit_tests/data/defs.py
@@ -0,0 +1,295 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+Class definitions for the confget test suite.
+"""
+
+import abc
+import os
+
+import six
+
+from confget import backend as cbackend
+from confget import format as cformat
+
+from . import util
+
+
+try:
+ from typing import Any, Dict, Iterable, List, Optional, Type
+
+ _TYPING_USED = (Any, Dict, Iterable, List, Optional, Type)
+except ImportError:
+ pass
+
+
+CMDLINE_OPTIONS = {
+ 'check_only': ('-c', False),
+ 'filename': ('-f', True),
+ 'hide_var_name': ('-n', False),
+ 'list_all': ('-l', False),
+ 'match_var_names': ('-L', False),
+ 'match_var_values': ('-m', True),
+ 'section': ('-s', True),
+ 'section_override': ('-O', False),
+ 'section_specified': ('', False),
+ 'show_var_name': ('-N', False),
+}
+
+
+@six.add_metaclass(abc.ABCMeta)
+class XFormType(object):
+ """ Transform something to something else with great prejudice. """
+
+ @abc.abstractproperty
+ def command(self):
+ # type: (XFormType) -> str
+ """ Get the shell command to transform the confget output. """
+ raise NotImplementedError(
+ '{tname}.command'.format(tname=type(self).__name__))
+
+ @abc.abstractmethod
+ def do_xform(self, res):
+ # type: (XFormType, Iterable[cformat.FormatOutput]) -> str
+ """ Transform the Python representation of the result. """
+ raise NotImplementedError(
+ '{tname}.do_xform()'.format(tname=type(self).__name__))
+
+
+class XFormNone(XFormType):
+ """ No transformation, newlines preserved. """
+
+ @property
+ def command(self):
+ # type: (XFormNone) -> str
+ return ''
+
+ def do_xform(self, res):
+ # type: (XFormNone, Iterable[cformat.FormatOutput]) -> str
+ xform = '\n'.join([line.output_full for line in res]) # type: str
+ return xform
+
+
+class XFormNewlineToSpace(XFormType):
+ """ Translate newlines to spaces. """
+
+ @property
+ def command(self):
+ # type: (XFormNewlineToSpace) -> str
+ return '| tr "\\n" " "'
+
+ def do_xform(self, res):
+ # type: (XFormNewlineToSpace, Iterable[cformat.FormatOutput]) -> str
+ xform = ''.join([line.output_full + ' ' for line in res]) # type: str
+ return xform
+
+
+class XFormCountLines(XFormType):
+ """ Count the lines output by confget. """
+
+ def __init__(self, sought=None, sought_in=True):
+ # type: (XFormCountLines, Optional[str], bool) -> None
+ super(XFormCountLines, self).__init__()
+ self.sought = sought
+ self.sought_in = sought_in
+
+ @property
+ def command(self):
+ # type: (XFormCountLines) -> str
+ if self.sought:
+ prefix = '| fgrep -{inv}e {sought} '.format(
+ inv='' if self.sought_in else 'v',
+ sought=util.shell_escape(self.sought))
+ else:
+ prefix = ''
+ return prefix + "| wc -l | tr -d ' '"
+
+ def do_xform(self, res):
+ # type: (XFormCountLines, Iterable[cformat.FormatOutput]) -> str
+ if self.sought:
+ return str(len(
+ [line for line in res
+ if self.sought_in == (self.sought in line.output_full)]))
+ return str(len([line for line in res]))
+
+
+XFORM = {
+ '': XFormNone(),
+ 'count-lines': XFormCountLines(),
+ 'count-lines-eq': XFormCountLines(sought='='),
+ 'count-lines-non-eq': XFormCountLines(sought='=', sought_in=False),
+ 'newline-to-space': XFormNewlineToSpace(),
+}
+
+
+@six.add_metaclass(abc.ABCMeta)
+class TestOutputDef(object):
+ """ A definition for a single test's output. """
+
+ def __init(self):
+ # type: (TestOutputDef) -> None
+ """ No initialization at all for the base class. """
+
+ @abc.abstractmethod
+ def get_check(self):
+ # type: (TestOutputDef) -> str
+ """ Get the check string as a shell command. """
+ raise NotImplementedError(
+ '{name}.get_check()'.format(name=type(self).__name__))
+
+ @abc.abstractproperty
+ def var_name(self):
+ # type: (TestOutputDef) -> str
+ """ Get the variable name to display. """
+ raise NotImplementedError(
+ '{name}.var_name'.format(name=type(self).__name__))
+
+ @abc.abstractmethod
+ def check_result(self, _res):
+ # type: (TestOutputDef, str) -> None
+ """ Check whether the processed confget result is correct. """
+ raise NotImplementedError(
+ '{name}.check_result()'.format(name=type(self).__name__))
+
+
+class TestExactOutputDef(TestOutputDef):
+ """ Check that the program output this exact string. """
+
+ def __init__(self, exact):
+ # type: (TestExactOutputDef, str) -> None
+ """ Initialize an exact test output object. """
+ self.exact = exact
+
+ def get_check(self):
+ # type: (TestExactOutputDef) -> str
+ return '[ "$v" = ' + util.shell_escape(self.exact) + ' ]'
+
+ @property
+ def var_name(self):
+ # type: (TestExactOutputDef) -> str
+ return 'v'
+
+ def check_result(self, res):
+ # type: (TestExactOutputDef, str) -> None
+ assert res == self.exact
+
+
+class TestExitOKOutputDef(TestOutputDef):
+ """ Check that the program succeeded or failed as expected. """
+
+ def __init__(self, success):
+ # type: (TestExitOKOutputDef, bool) -> None
+ """ Initialize an "finished successfully" test output object. """
+ self.success = success
+
+ def get_check(self):
+ # type: (TestExitOKOutputDef) -> str
+ return '[ "$res" {compare} 0 ]'.format(
+ compare="=" if self.success else "!=")
+
+ @property
+ def var_name(self):
+ # type: (TestExitOKOutputDef) -> str
+ return 'res'
+
+ def check_result(self, res):
+ # type: (TestExitOKOutputDef, str) -> None
+ # pylint: disable=useless-super-delegation
+ super(TestExitOKOutputDef, self).check_result(res)
+
+
+class TestDef:
+ # pylint: disable=too-few-public-methods
+ """ A definition for a single test. """
+
+ def __init__(self, # type: TestDef
+ args, # type: Dict[str, str]
+ keys, # type: List[str]
+ output, # type: TestOutputDef
+ xform='', # type: str
+ backend='ini', # type: str
+ setenv=False, # type: bool
+ stdin=None, # type: Optional[str]
+ ): # type: (...) -> None
+ # pylint: disable=too-many-arguments
+ """ Initialize a test object. """
+
+ self.args = args
+ self.keys = keys
+ self.xform = xform
+ self.output = output
+ self.backend = backend
+ self.setenv = setenv
+ self.stdin = stdin
+
+ def get_backend(self):
+ # type: (TestDef) -> Type[cbackend.abstract.Backend]
+ """ Get the appropriate confget backend type. """
+ return cbackend.BACKENDS[self.backend]
+
+ def get_config(self):
+ # type: (TestDef) -> cformat.FormatConfig
+ """ Convert the test's data to a config object. """
+ data = {} # type: Dict[str, Any]
+ for name, value in self.args.items():
+ if name == 'hide_var_name':
+ continue
+
+ opt = CMDLINE_OPTIONS[name]
+ if opt[1]:
+ data[name] = value
+ else:
+ data[name] = True
+
+ if 'filename' in data:
+ data['filename'] = os.environ['TESTDIR'] + '/' + data['filename']
+ elif self.stdin:
+ data['filename'] = '-'
+
+ data['show_var_name'] = 'show_var_name' in self.args or \
+ (('match_var_names' in self.args or
+ 'list_all' in self.args or
+ len(self.keys) > 1) and
+ 'hide_var_name' not in self.args)
+
+ return cformat.FormatConfig(self.keys, **data)
+
+ def do_xform(self, res):
+ # type: (TestDef, Iterable[cformat.FormatOutput]) -> str
+ """ Return the output delimiter depending on the xform property. """
+ return XFORM[self.xform].do_xform(res)
+
+
+class TestFileDef:
+ # pylint: disable=too-few-public-methods
+ """ A definition for a file defining related tests. """
+
+ def __init__(self, # type: TestFileDef
+ tests, # type: List[TestDef]
+ setenv=None, # type: Optional[Dict[str, str]]
+ ): # type: (...) -> None
+ """ Initialize a test file object. """
+ self.tests = tests
+ self.setenv = {} if setenv is None else setenv
diff --git a/python/unit_tests/data/load.py b/python/unit_tests/data/load.py
new file mode 100644
index 0000000..cf23f54
--- /dev/null
+++ b/python/unit_tests/data/load.py
@@ -0,0 +1,106 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+Load a test definition from a JSON file.
+"""
+# pylint: disable=consider-using-dict-comprehension
+
+import json
+import os
+
+from . import defs
+
+try:
+ from typing import Any, Dict
+
+ TESTING_USED = (defs, Any, Dict)
+except ImportError:
+ pass
+
+
+def _load_test_v1(data, _version):
+ # type: (Dict[str, Any], Dict[str, int]) -> defs.TestFileDef
+ """ Load the tests from a v1.x test file. """
+ build = {
+ 'setenv': data.get('setenv', {}),
+ 'tests': [],
+ }
+
+ for test in data['tests']:
+ raw = dict([
+ (key, value) for key, value in test.items()
+ if key in ('args', 'keys', 'xform', 'backend', 'setenv', 'stdin')
+ ])
+
+ if 'exact' in test['output']:
+ raw['output'] = defs.TestExactOutputDef(
+ exact=test['output']['exact'])
+ elif 'exit' in test['output']:
+ raw['output'] = defs.TestExitOKOutputDef(
+ success=test['output']['exit'])
+ else:
+ raise ValueError('test output: ' + repr(test['output']))
+
+ build['tests'].append(defs.TestDef(**raw))
+
+ return defs.TestFileDef(**build)
+
+
+_PARSERS = {
+ 1: _load_test_v1,
+}
+
+
+def load_test(fname):
+ # type: (str) -> defs.TestFileDef
+ """ Load a single test file into a TestFileDef object. """
+ with open(fname, mode='r') as testf:
+ data = json.load(testf)
+
+ version = {
+ 'major': data['format']['version']['major'],
+ 'minor': data['format']['version']['minor'],
+ }
+ assert isinstance(version['major'], int)
+ assert isinstance(version['minor'], int)
+
+ parser = _PARSERS.get(version['major'], None)
+ if parser is not None:
+ return parser(data, version)
+ raise NotImplementedError(
+ 'Unsupported test file format version {major}.{minor} for {fname}'
+ .format(major=version['major'], minor=version['minor'], fname=fname))
+
+
+def load_all_tests(testdir):
+ # type: (str) -> Dict[str, defs.TestFileDef]
+ """ Load all the tests in the defs/tests/ subdirectory. """
+ tdir = testdir + '/defs/tests/'
+ filenames = sorted(fname for fname in os.listdir(tdir)
+ if fname.endswith('.json'))
+ return dict([
+ (os.path.splitext(fname)[0], load_test(tdir + fname))
+ for fname in filenames
+ ])
diff --git a/python/unit_tests/data/util.py b/python/unit_tests/data/util.py
new file mode 100644
index 0000000..32d7489
--- /dev/null
+++ b/python/unit_tests/data/util.py
@@ -0,0 +1,45 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+Utility functions for the confget test cases.
+"""
+
+import re
+
+
+_RE_APOS = re.compile("(?P<pre> [^']* ) (?P<apos> '+ ) (?P<post> .* )", re.X)
+
+
+def shell_escape(value):
+ # type: (str) -> str
+ """ Escape a value for the shell. """
+ res = "'"
+ while True:
+ match = _RE_APOS.match(value)
+ if not match:
+ return res + value + "'"
+
+ res += match.group('pre') + '\'"' + match.group('apos') + '"\''
+ value = match.group('post')
diff --git a/python/unit_tests/test_run.py b/python/unit_tests/test_run.py
new file mode 100644
index 0000000..9a3c13b
--- /dev/null
+++ b/python/unit_tests/test_run.py
@@ -0,0 +1,136 @@
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+Run the confget tests using the Python methods.
+
+Load the test data, then run the tests using the objects provided by
+the Python library, not by executing the command-line tool.
+"""
+
+from __future__ import print_function
+
+import itertools
+import os
+import sys
+import unittest
+
+import ddt # type: ignore
+import pytest # type: ignore
+
+from confget import format as cformat
+
+pytest.register_assert_rewrite('unit_tests.data.defs')
+
+from . import data as tdata # noqa: E402 pylint: disable=wrong-import-position
+
+
+try:
+ from typing import Dict
+
+ _TYPING_USED = (Dict,)
+except ImportError:
+ pass
+
+
+TESTS = tdata.load_all_tests(os.environ['TESTDIR'])
+
+FULL_TEST_DATA = sorted(itertools.chain(*[
+ [
+ (
+ tfile[0],
+ idx,
+ tfile[1].setenv,
+ test,
+ )
+ for idx, test in enumerate(tfile[1].tests)
+ ]
+ for tfile in TESTS.items()
+]))
+
+SKIP_ARGS = set(['check_only'])
+
+
+@ddt.ddt
+class TestStuff(unittest.TestCase):
+ # pylint: disable=no-self-use
+ """ Run the tests using the Python library. """
+
+ @ddt.data(
+ *FULL_TEST_DATA
+ )
+ @ddt.unpack
+ def test_run(self, # type: TestStuff
+ fname, # type: str
+ idx, # type: int
+ setenv, # type: Dict[str, str]
+ test, # type: tdata.TestDef
+ ): # type: (...) -> None
+ """ Instantiate a confget object, load the data, check it. """
+ print('')
+
+ save_env = dict(os.environ)
+ try:
+ print('fname {fname} {idx:2} setenv {count} keys {kkk}'
+ .format(fname=fname, idx=idx, count=len(setenv.keys()),
+ kkk=test.keys))
+ if set(test.args.keys()) & SKIP_ARGS:
+ print('- skipping: {skip}'.format(
+ skip=' '.join(sorted(set(test.args.keys()) & SKIP_ARGS))))
+ return
+
+ backend = test.get_backend()
+ print('- backend {back}'.format(back=backend))
+ config = test.get_config()
+ print('- config {cfg}'.format(cfg=config))
+ if test.setenv:
+ os.environ.update(setenv)
+
+ if test.stdin:
+ fname = os.environ['TESTDIR'] + '/' + test.stdin
+ print('- reopening {fname} as stdin'.format(fname=fname))
+ with open(fname, mode='r') as stdin:
+ save_stdin = sys.stdin
+ try:
+ sys.stdin = stdin
+ obj = backend(config)
+ print('- obj {obj}'.format(obj=obj))
+ data = obj.read_file()
+ finally:
+ sys.stdin = save_stdin
+ else:
+ obj = backend(config)
+ print('- obj {obj}'.format(obj=obj))
+ data = obj.read_file()
+
+ print('- sections: {sect}'.format(sect=sorted(data.keys())))
+ res = cformat.filter_vars(config, data)
+ print('- result: {res}'.format(res=res))
+ output = test.do_xform(res)
+ print('- transformed: {output}'.format(output=repr(output)))
+ test.output.check_result(output)
+ finally:
+ if test.setenv:
+ os.environ.clear()
+ os.environ.update(save_env)
diff --git a/t/01-get-values.t b/t/01-get-values.t
index d259bf1..f338516 100644
--- a/t/01-get-values.t
+++ b/t/01-get-values.t
@@ -1,6 +1,6 @@
#!/bin/sh
#
-# Copyright (c) 2008, 2011 Peter Pentchev
+# Copyright (c) 2008, 2011, 2019 Peter Pentchev
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -27,54 +27,64 @@
[ -z "$CONFGET" ] && CONFGET='./confget'
[ -z "$TESTDIR" ] && TESTDIR='t'
-T1="$TESTDIR/t1.ini"
-
echo '1..18'
-if [ ! -f "$T1" ]; then
- echo "Bail out! No test file $T1"
- exit 255
-fi
-v=`$CONFGET -f "$T1" -s a key1`
+if [ ! -f "$TESTDIR/t1.ini" ]; then
+ echo "Bail out! No test file $TESTDIR/t1.ini"
+ exit 255
+fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'a' 'key1' `
+res="$?"
if [ "$v" = 'value1' ]; then echo 'ok 1'; else echo "not ok 1 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -N -s a key2`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'a' '-N' 'key2' `
+res="$?"
if [ "$v" = 'key2=value2' ]; then echo 'ok 2'; else echo "not ok 2 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -s a key3`
-if [ "$v" = " val'ue3" ]; then echo 'ok 3'; else echo "not ok 3 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -s 'b sect' key4`
-if [ "$v" = "v'alu'e4" ]; then echo 'ok 4'; else echo "not ok 4 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -s c key5`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'a' 'key3' `
+res="$?"
+if [ "$v" = ' val'"'"'ue3' ]; then echo 'ok 3'; else echo "not ok 3 v is '$v'"; fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'b sect' 'key4' `
+res="$?"
+if [ "$v" = 'v'"'"'alu'"'"'e4' ]; then echo 'ok 4'; else echo "not ok 4 v is '$v'"; fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'c' 'key5' `
+res="$?"
if [ "$v" = ' # value5' ]; then echo 'ok 5'; else echo "not ok 5 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -s a key1 key2 | tr "\n" ' '`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'a' 'key1' 'key2' | tr "\n" " "`
+res="$?"
if [ "$v" = 'key1=value1 key2=value2 ' ]; then echo 'ok 6'; else echo "not ok 6 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -s a -n key6 key2 | tr "\n" ' '`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'a' '-n' 'key6' 'key2' | tr "\n" " "`
+res="$?"
if [ "$v" = 'value2 value6 ' ]; then echo 'ok 7'; else echo "not ok 7 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -s 'b sect' key7`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'b sect' 'key7' `
+res="$?"
if [ "$v" = 'value7' ]; then echo 'ok 8'; else echo "not ok 8 v is '$v'"; fi
-v=`$CONFGET -f - -s 'b sect' key7 < "$T1"`
+v=`$CONFGET '-s' 'b sect' -f - 'key7' < "$TESTDIR/t1.ini" `
+res="$?"
if [ "$v" = 'value7' ]; then echo 'ok 9'; else echo "not ok 9 v is '$v'"; fi
-v=`$CONFGET -f - -s 'b sect' key77 < "$T1"`
+v=`$CONFGET '-s' 'b sect' -f - 'key77' < "$TESTDIR/t1.ini" `
+res="$?"
if [ "$v" = '' ]; then echo 'ok 10'; else echo "not ok 10 v is '$v'"; fi
-
-QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3'
-Q1='key4&amp;key5=%09%09%20val%27ue5&key6'
-Q2=''
-export QUERY_STRING Q1 Q2
-
-v=`$CONFGET -t http_get key1`
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get 'key1' `
+res="$?"
if [ "$v" = 'value1' ]; then echo 'ok 11'; else echo "not ok 11 v is '$v'"; fi
-v=`$CONFGET -t http_get -N key2`
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-N' 'key2' `
+res="$?"
if [ "$v" = 'key2==value2&' ]; then echo 'ok 12'; else echo "not ok 12 v is '$v'"; fi
-v=`$CONFGET -t http_get key3`
-if [ "$v" = " val'ue3" ]; then echo 'ok 13'; else echo "not ok 13 v is '$v'"; fi
-v=`$CONFGET -t http_get -s Q1 key4`
-if [ "$v" = "" ]; then echo 'ok 14'; else echo "not ok 14 v is '$v'"; fi
-v=`$CONFGET -t http_get -s Q1 key5`
-if [ "$v" = " val'ue5" ]; then echo 'ok 15'; else echo "not ok 15 v is '$v'"; fi
-v=`$CONFGET -t http_get -s Q1 key6`
-if [ "$v" = "" ]; then echo 'ok 16'; else echo "not ok 16 v is '$v'"; fi
-v=`$CONFGET -t http_get -s Q1 key66`
-if [ "$v" = "" ]; then echo 'ok 17'; else echo "not ok 17 v is '$v'"; fi
-v=`$CONFGET -t http_get -s Q2 key66`
-if [ "$v" = "" ]; then echo 'ok 18'; else echo "not ok 18 v is '$v'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get 'key3' `
+res="$?"
+if [ "$v" = ' val'"'"'ue3' ]; then echo 'ok 13'; else echo "not ok 13 v is '$v'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-s' 'Q1' 'key4' `
+res="$?"
+if [ "$v" = '' ]; then echo 'ok 14'; else echo "not ok 14 v is '$v'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-s' 'Q1' 'key5' `
+res="$?"
+if [ "$v" = ' val'"'"'ue5' ]; then echo 'ok 15'; else echo "not ok 15 v is '$v'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-s' 'Q1' 'key6' `
+res="$?"
+if [ "$v" = '' ]; then echo 'ok 16'; else echo "not ok 16 v is '$v'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-s' 'Q1' 'key66' `
+res="$?"
+if [ "$v" = '' ]; then echo 'ok 17'; else echo "not ok 17 v is '$v'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-s' 'Q2' 'key66' `
+res="$?"
+if [ "$v" = '' ]; then echo 'ok 18'; else echo "not ok 18 v is '$v'"; fi
diff --git a/t/02-check-values.t b/t/02-check-values.t
index fd29a4c..f91fab3 100644
--- a/t/02-check-values.t
+++ b/t/02-check-values.t
@@ -1,6 +1,6 @@
#!/bin/sh
#
-# Copyright (c) 2008, 2011 Peter Pentchev
+# Copyright (c) 2008, 2011, 2019 Peter Pentchev
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -27,33 +27,55 @@
[ -z "$CONFGET" ] && CONFGET='./confget'
[ -z "$TESTDIR" ] && TESTDIR='t'
-T1="$TESTDIR/t1.ini"
-
echo '1..15'
-if [ ! -f "$T1" ]; then
- echo "Bail out! No test file $T1"
- exit 255
-fi
-
-if $CONFGET -f "$T1" -s a -c key1; then echo 'ok 1 '; else echo "not ok 1 $?"; fi
-if $CONFGET -f "$T1" -s a -c key2; then echo 'ok 2 '; else echo "not ok 2 $?"; fi
-if $CONFGET -f "$T1" -s a -c key3; then echo 'ok 3 '; else echo "not ok 3 $?"; fi
-if ! $CONFGET -f "$T1" -s a -c key4; then echo 'ok 4 '; else echo "not ok 4 $?"; fi
-if ! $CONFGET -f "$T1" -s 'b sect' -c key5; then echo 'ok 5 '; else echo "not ok 5 $?"; fi
-if $CONFGET -f "$T1" -s 'b sect' -c key4; then echo 'ok 6 '; else echo "not ok 6 $?"; fi
-if $CONFGET -f "$T1" -s c -c key5; then echo 'ok 7 '; else echo "not ok 7 $?"; fi
-QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3'
-Q1='key4&amp;key5=%09%09%20val%27ue5&key6'
-Q2=''
-export QUERY_STRING Q1 Q2
-
-if $CONFGET -t http_get -c key1; then echo 'ok 8 '; else echo "not ok 8 $?"; fi
-if $CONFGET -t http_get -c key2; then echo 'ok 9 '; else echo "not ok 9 $?"; fi
-if $CONFGET -t http_get -c key3; then echo 'ok 10 '; else echo "not ok 10 $?"; fi
-if $CONFGET -t http_get -s Q1 -c key4; then echo 'ok 11 '; else echo "not ok 11 $?"; fi
-if $CONFGET -t http_get -s Q1 -c key6; then echo 'ok 12 '; else echo "not ok 12 $?"; fi
-if ! $CONFGET -t http_get -c key6; then echo 'ok 13 '; else echo "not ok 13 $?"; fi
-if ! $CONFGET -t http_get -s Q1 -c key1; then echo 'ok 14 '; else echo "not ok 14 $?"; fi
-if ! $CONFGET -t http_get -s Q2 -c key1; then echo 'ok 15 '; else echo "not ok 15 $?"; fi
+if [ ! -f "$TESTDIR/t1.ini" ]; then
+ echo "Bail out! No test file $TESTDIR/t1.ini"
+ exit 255
+fi
+v=`$CONFGET '-c' '-f' "$TESTDIR/t1.ini" '-s' 'a' 'key1' `
+res="$?"
+if [ "$res" = 0 ]; then echo 'ok 1'; else echo "not ok 1 res is '$res'"; fi
+v=`$CONFGET '-c' '-f' "$TESTDIR/t1.ini" '-s' 'a' 'key2' `
+res="$?"
+if [ "$res" = 0 ]; then echo 'ok 2'; else echo "not ok 2 res is '$res'"; fi
+v=`$CONFGET '-c' '-f' "$TESTDIR/t1.ini" '-s' 'a' 'key3' `
+res="$?"
+if [ "$res" = 0 ]; then echo 'ok 3'; else echo "not ok 3 res is '$res'"; fi
+v=`$CONFGET '-c' '-f' "$TESTDIR/t1.ini" '-s' 'a' 'key4' `
+res="$?"
+if [ "$res" != 0 ]; then echo 'ok 4'; else echo "not ok 4 res is '$res'"; fi
+v=`$CONFGET '-c' '-f' "$TESTDIR/t1.ini" '-s' 'b sect' 'key5' `
+res="$?"
+if [ "$res" != 0 ]; then echo 'ok 5'; else echo "not ok 5 res is '$res'"; fi
+v=`$CONFGET '-c' '-f' "$TESTDIR/t1.ini" '-s' 'b sect' 'key4' `
+res="$?"
+if [ "$res" = 0 ]; then echo 'ok 6'; else echo "not ok 6 res is '$res'"; fi
+v=`$CONFGET '-c' '-f' "$TESTDIR/t1.ini" '-s' 'c' 'key5' `
+res="$?"
+if [ "$res" = 0 ]; then echo 'ok 7'; else echo "not ok 7 res is '$res'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-c' 'key1' `
+res="$?"
+if [ "$res" = 0 ]; then echo 'ok 8'; else echo "not ok 8 res is '$res'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-c' 'key2' `
+res="$?"
+if [ "$res" = 0 ]; then echo 'ok 9'; else echo "not ok 9 res is '$res'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-c' 'key3' `
+res="$?"
+if [ "$res" = 0 ]; then echo 'ok 10'; else echo "not ok 10 res is '$res'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-c' '-s' 'Q1' 'key4' `
+res="$?"
+if [ "$res" = 0 ]; then echo 'ok 11'; else echo "not ok 11 res is '$res'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-c' '-s' 'Q1' 'key6' `
+res="$?"
+if [ "$res" = 0 ]; then echo 'ok 12'; else echo "not ok 12 res is '$res'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-c' 'key6' `
+res="$?"
+if [ "$res" != 0 ]; then echo 'ok 13'; else echo "not ok 13 res is '$res'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-c' '-s' 'Q1' 'key1' `
+res="$?"
+if [ "$res" != 0 ]; then echo 'ok 14'; else echo "not ok 14 res is '$res'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-c' '-s' 'Q2' 'key1' `
+res="$?"
+if [ "$res" != 0 ]; then echo 'ok 15'; else echo "not ok 15 res is '$res'"; fi
diff --git a/t/03-list-all.t b/t/03-list-all.t
index e3b525b..327cb2d 100644
--- a/t/03-list-all.t
+++ b/t/03-list-all.t
@@ -1,6 +1,6 @@
#!/bin/sh
#
-# Copyright (c) 2008, 2011 Peter Pentchev
+# Copyright (c) 2008, 2011, 2019 Peter Pentchev
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -27,32 +27,31 @@
[ -z "$CONFGET" ] && CONFGET='./confget'
[ -z "$TESTDIR" ] && TESTDIR='t'
-T1="$TESTDIR/t1.ini"
-
echo '1..7'
-if [ ! -f "$T1" ]; then
- echo "Bail out! No test file $T1"
- exit 255
-fi
-
-l=`$CONFGET -f "$T1" -s a -l | wc -l | tr -d ' '`
-if [ "$l" = '4' ]; then echo 'ok 1'; else echo "not ok 1 '$l'"; fi
-l=`$CONFGET -f "$T1" -N -s 'b sect' -l | fgrep -e '=' | wc -l | tr -d ' '`
-if [ "$l" = '2' ]; then echo 'ok 2'; else echo "not ok 2 '$l'"; fi
-l=`$CONFGET -f "$T1" -n -s c -l | fgrep -ve '=' | wc -l | tr -d ' '`
-if [ "$l" = '1' ]; then echo 'ok 3'; else echo "not ok 3 '$l'"; fi
-l=`$CONFGET -f "$T1" -s d -l | wc -l | tr -d ' '`
-if [ "$l" = '0' ]; then echo 'ok 4'; else echo "not ok 4 '$l'"; fi
-QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3'
-Q1='key4&amp;key5=%09%09%20val%27ue5&key6'
-Q2=''
-export QUERY_STRING Q1 Q2
-
-l=`$CONFGET -t http_get -l | wc -l | tr -d ' '`
-if [ "$l" = '3' ]; then echo 'ok 5'; else echo "not ok 5 '$l'"; fi
-l=`$CONFGET -t http_get -s Q1 -l | wc -l | tr -d ' '`
-if [ "$l" = '3' ]; then echo 'ok 6'; else echo "not ok 6 '$l'"; fi
-l=`$CONFGET -t http_get -s Q2 -l | wc -l | tr -d ' '`
-if [ "$l" = '0' ]; then echo 'ok 7'; else echo "not ok 7 '$l'"; fi
+if [ ! -f "$TESTDIR/t1.ini" ]; then
+ echo "Bail out! No test file $TESTDIR/t1.ini"
+ exit 255
+fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'a' '-l' | wc -l | tr -d ' '`
+res="$?"
+if [ "$v" = '4' ]; then echo 'ok 1'; else echo "not ok 1 v is '$v'"; fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'b sect' '-l' '-N' | fgrep -e '=' | wc -l | tr -d ' '`
+res="$?"
+if [ "$v" = '2' ]; then echo 'ok 2'; else echo "not ok 2 v is '$v'"; fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'c' '-l' '-n' | fgrep -ve '=' | wc -l | tr -d ' '`
+res="$?"
+if [ "$v" = '1' ]; then echo 'ok 3'; else echo "not ok 3 v is '$v'"; fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'd' '-l' | wc -l | tr -d ' '`
+res="$?"
+if [ "$v" = '0' ]; then echo 'ok 4'; else echo "not ok 4 v is '$v'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-l' | wc -l | tr -d ' '`
+res="$?"
+if [ "$v" = '3' ]; then echo 'ok 5'; else echo "not ok 5 v is '$v'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-l' '-s' 'Q1' | wc -l | tr -d ' '`
+res="$?"
+if [ "$v" = '3' ]; then echo 'ok 6'; else echo "not ok 6 v is '$v'"; fi
+v=`env Q1='key4&amp;key5=%09%09%20val%27ue5&key6' Q2='' QUERY_STRING='key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3' $CONFGET -t http_get '-l' '-s' 'Q2' | wc -l | tr -d ' '`
+res="$?"
+if [ "$v" = '0' ]; then echo 'ok 7'; else echo "not ok 7 v is '$v'"; fi
diff --git a/t/04-test-manpage.t b/t/04-bespoke-test-manpage.t
index ddc0607..ddc0607 100644
--- a/t/04-test-manpage.t
+++ b/t/04-bespoke-test-manpage.t
diff --git a/t/05-match-names.t b/t/05-match-names.t
index be7d78a..cd529e0 100644
--- a/t/05-match-names.t
+++ b/t/05-match-names.t
@@ -1,6 +1,6 @@
#!/bin/sh
#
-# Copyright (c) 2008, 2011 Peter Pentchev
+# Copyright (c) 2008, 2011, 2019 Peter Pentchev
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -27,20 +27,22 @@
[ -z "$CONFGET" ] && CONFGET='./confget'
[ -z "$TESTDIR" ] && TESTDIR='t'
-T1="$TESTDIR/t1.ini"
-
echo '1..4'
-if [ ! -f "$T1" ]; then
- echo "Bail out! No test file $T1"
- exit 255
-fi
-l=`$CONFGET -f "$T1" -s a -L '*' | wc -l | tr -d ' '`
-if [ "$l" = '4' ]; then echo 'ok 1'; else echo "not ok 1 '$l'"; fi
-l=`$CONFGET -f "$T1" -N -s 'b sect' -L '*' | fgrep -e '=' | wc -l | tr -d ' '`
-if [ "$l" = '2' ]; then echo 'ok 2'; else echo "not ok 2 '$l'"; fi
-l=`$CONFGET -f "$T1" -n -s a -L '*ey2' | fgrep -ve '=' | wc -l | tr -d ' '`
-if [ "$l" = '1' ]; then echo 'ok 3'; else echo "not ok 3 '$l'"; fi
-l=`$CONFGET -f "$T1" -s d -L '*ey2' | wc -l | tr -d ' '`
-if [ "$l" = '0' ]; then echo 'ok 4'; else echo "not ok 4 '$l'"; fi
+if [ ! -f "$TESTDIR/t1.ini" ]; then
+ echo "Bail out! No test file $TESTDIR/t1.ini"
+ exit 255
+fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'a' '-L' '*' | wc -l | tr -d ' '`
+res="$?"
+if [ "$v" = '4' ]; then echo 'ok 1'; else echo "not ok 1 v is '$v'"; fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'b sect' '-L' '-N' '*' | fgrep -e '=' | wc -l | tr -d ' '`
+res="$?"
+if [ "$v" = '2' ]; then echo 'ok 2'; else echo "not ok 2 v is '$v'"; fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'a' '-L' '-n' '*ey2' | fgrep -ve '=' | wc -l | tr -d ' '`
+res="$?"
+if [ "$v" = '1' ]; then echo 'ok 3'; else echo "not ok 3 v is '$v'"; fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'd' '-L' '*ey2' | wc -l | tr -d ' '`
+res="$?"
+if [ "$v" = '0' ]; then echo 'ok 4'; else echo "not ok 4 v is '$v'"; fi
diff --git a/t/06-get-default.t b/t/06-get-default.t
index 6755248..052f08e 100644
--- a/t/06-get-default.t
+++ b/t/06-get-default.t
@@ -1,6 +1,6 @@
#!/bin/sh
#
-# Copyright (c) 2008 Peter Pentchev
+# Copyright (c) 2008, 2011, 2019 Peter Pentchev
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -27,27 +27,30 @@
[ -z "$CONFGET" ] && CONFGET='./confget'
[ -z "$TESTDIR" ] && TESTDIR='t'
-T1="$TESTDIR/t1.ini"
-T2="$TESTDIR/t2.ini"
-
echo '1..5'
-if [ ! -f "$T1" ]; then
- echo "Bail out! No test file $T1"
- exit 255
-fi
-if [ ! -f "$T2" ]; then
- echo "Bail out! No test file $T2"
- exit 255
+
+if [ ! -f "$TESTDIR/t1.ini" ]; then
+ echo "Bail out! No test file $TESTDIR/t1.ini"
+ exit 255
fi
-v=`$CONFGET -f "$T1" key1`
+if [ ! -f "$TESTDIR/t2.ini" ]; then
+ echo "Bail out! No test file $TESTDIR/t2.ini"
+ exit 255
+fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" 'key1' `
+res="$?"
if [ "$v" = 'value1' ]; then echo 'ok 1'; else echo "not ok 1 v is '$v'"; fi
-v=`$CONFGET -f "$T1" key4`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" 'key4' `
+res="$?"
if [ "$v" = '' ]; then echo 'ok 2'; else echo "not ok 2 v is '$v'"; fi
-v=`$CONFGET -f "$T1" key6`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" 'key6' `
+res="$?"
if [ "$v" = 'value6' ]; then echo 'ok 3'; else echo "not ok 3 v is '$v'"; fi
-v=`$CONFGET -f "$T2" key1`
+v=`$CONFGET '-f' "$TESTDIR/t2.ini" 'key1' `
+res="$?"
if [ "$v" = '1' ]; then echo 'ok 4'; else echo "not ok 4 v is '$v'"; fi
-v=`$CONFGET -f "$T2" key1 key2 | tr "\n" ' '`
+v=`$CONFGET '-f' "$TESTDIR/t2.ini" 'key1' 'key2' | tr "\n" " "`
+res="$?"
if [ "$v" = 'key1=1 key2=2 ' ]; then echo 'ok 5'; else echo "not ok 5 v is '$v'"; fi
diff --git a/t/07-match-values.t b/t/07-match-values.t
index bfcda6f..b66a9a8 100644
--- a/t/07-match-values.t
+++ b/t/07-match-values.t
@@ -1,6 +1,6 @@
#!/bin/sh
#
-# Copyright (c) 2008, 2011 Peter Pentchev
+# Copyright (c) 2008, 2011, 2019 Peter Pentchev
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -27,28 +27,37 @@
[ -z "$CONFGET" ] && CONFGET='./confget'
[ -z "$TESTDIR" ] && TESTDIR='t'
-T1="$TESTDIR/t1.ini"
-
echo '1..9'
-if [ ! -f "$T1" ]; then
- echo "Bail out! No test file $T1"
- exit 255
-fi
-v=`$CONFGET -f "$T1" -m 'value*' -s a key1`
+if [ ! -f "$TESTDIR/t1.ini" ]; then
+ echo "Bail out! No test file $TESTDIR/t1.ini"
+ exit 255
+fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'a' '-m' 'value*' 'key1' `
+res="$?"
if [ "$v" = 'value1' ]; then echo 'ok 1'; else echo "not ok 1 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -m '*ue2' -s a key2`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'a' '-m' '*ue2' 'key2' `
+res="$?"
if [ "$v" = 'value2' ]; then echo 'ok 2'; else echo "not ok 2 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -m "*val'ue*" -s a key3`
-if [ "$v" = " val'ue3" ]; then echo 'ok 3'; else echo "not ok 3 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -m '*alu*' -s 'b sect' key4`
-if [ "$v" = "v'alu'e4" ]; then echo 'ok 4'; else echo "not ok 4 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -s c key5`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'a' '-m' '*val'"'"'ue*' 'key3' `
+res="$?"
+if [ "$v" = ' val'"'"'ue3' ]; then echo 'ok 3'; else echo "not ok 3 v is '$v'"; fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'b sect' '-m' '*alu*' 'key4' `
+res="$?"
+if [ "$v" = 'v'"'"'alu'"'"'e4' ]; then echo 'ok 4'; else echo "not ok 4 v is '$v'"; fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'c' 'key5' `
+res="$?"
if [ "$v" = ' # value5' ]; then echo 'ok 5'; else echo "not ok 5 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -m '*alu*' -s a key1 key2 | tr "\n" ' '`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'a' '-m' '*alu*' 'key1' 'key2' | tr "\n" " "`
+res="$?"
if [ "$v" = 'key1=value1 key2=value2 ' ]; then echo 'ok 6'; else echo "not ok 6 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -s a -m '*7' key6`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'a' '-m' '*7' 'key6' `
+res="$?"
if [ "$v" = '' ]; then echo 'ok 7'; else echo "not ok 7 v is '$v'"; fi
-if $CONFGET -f "$T1" -s 'b sect' -c -m '*7' key7; then echo 'ok 8'; else echo "not ok 8 code '$?'"; fi
-if ! $CONFGET -f "$T1" -s 'b sect' -c -m '*7' key6; then echo 'ok 9'; else echo "not ok 9 code '$?'"; fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'b sect' '-c' '-m' '*7' 'key7' `
+res="$?"
+if [ "$res" = 0 ]; then echo 'ok 8'; else echo "not ok 8 res is '$res'"; fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' 'b sect' '-c' '-m' '*7' 'key6' `
+res="$?"
+if [ "$res" != 0 ]; then echo 'ok 9'; else echo "not ok 9 res is '$res'"; fi
diff --git a/t/08-shell-quote.t b/t/08-bespoke-shell-quote.t
index 49264ad..49264ad 100644
--- a/t/08-shell-quote.t
+++ b/t/08-bespoke-shell-quote.t
diff --git a/t/09-regexp.t b/t/09-bespoke-regexp.t
index 684de13..684de13 100644
--- a/t/09-regexp.t
+++ b/t/09-bespoke-regexp.t
diff --git a/t/10-qsections.t b/t/10-bespoke-qsections.t
index 7b7804a..7b7804a 100644
--- a/t/10-qsections.t
+++ b/t/10-bespoke-qsections.t
diff --git a/t/11-no-default.t b/t/11-no-default.t
index 82b96e4..b531334 100644
--- a/t/11-no-default.t
+++ b/t/11-no-default.t
@@ -1,6 +1,6 @@
#!/bin/sh
#
-# Copyright (c) 2008, 2016 Peter Pentchev
+# Copyright (c) 2008, 2011, 2019 Peter Pentchev
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -27,40 +27,45 @@
[ -z "$CONFGET" ] && CONFGET='./confget'
[ -z "$TESTDIR" ] && TESTDIR='t'
-T1="$TESTDIR/t1.ini"
-T2="$TESTDIR/t2.ini"
-
echo '1..10'
-if [ ! -f "$T1" ]; then
- echo "Bail out! No test file $T1"
- exit 255
-fi
-if [ ! -f "$T2" ]; then
- echo "Bail out! No test file $T2"
- exit 255
+
+if [ ! -f "$TESTDIR/t1.ini" ]; then
+ echo "Bail out! No test file $TESTDIR/t1.ini"
+ exit 255
fi
-v=`$CONFGET -f "$T1" key1`
+if [ ! -f "$TESTDIR/t2.ini" ]; then
+ echo "Bail out! No test file $TESTDIR/t2.ini"
+ exit 255
+fi
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" 'key1' `
+res="$?"
if [ "$v" = 'value1' ]; then echo 'ok 1'; else echo "not ok 1 v is '$v'"; fi
-v=`$CONFGET -f "$T1" key4`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" 'key4' `
+res="$?"
if [ "$v" = '' ]; then echo 'ok 2'; else echo "not ok 2 v is '$v'"; fi
-v=`$CONFGET -f "$T1" key6`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" 'key6' `
+res="$?"
if [ "$v" = 'value6' ]; then echo 'ok 3'; else echo "not ok 3 v is '$v'"; fi
-
-v=`$CONFGET -f "$T1" -s '' key1`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' '' 'key1' `
+res="$?"
if [ "$v" = '' ]; then echo 'ok 4'; else echo "not ok 4 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -s '' key4`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' '' 'key4' `
+res="$?"
if [ "$v" = '' ]; then echo 'ok 5'; else echo "not ok 5 v is '$v'"; fi
-v=`$CONFGET -f "$T1" -s '' key6`
+v=`$CONFGET '-f' "$TESTDIR/t1.ini" '-s' '' 'key6' `
+res="$?"
if [ "$v" = '' ]; then echo 'ok 6'; else echo "not ok 6 v is '$v'"; fi
-
-v=`$CONFGET -f "$T2" key1`
+v=`$CONFGET '-f' "$TESTDIR/t2.ini" 'key1' `
+res="$?"
if [ "$v" = '1' ]; then echo 'ok 7'; else echo "not ok 7 v is '$v'"; fi
-v=`$CONFGET -f "$T2" key1 key2 | tr "\n" ' '`
+v=`$CONFGET '-f' "$TESTDIR/t2.ini" 'key1' 'key2' | tr "\n" " "`
+res="$?"
if [ "$v" = 'key1=1 key2=2 ' ]; then echo 'ok 8'; else echo "not ok 8 v is '$v'"; fi
-
-v=`$CONFGET -f "$T2" -s '' key1`
+v=`$CONFGET '-f' "$TESTDIR/t2.ini" '-s' '' 'key1' `
+res="$?"
if [ "$v" = '1' ]; then echo 'ok 9'; else echo "not ok 9 v is '$v'"; fi
-v=`$CONFGET -f "$T2" -s '' key1 key2 | tr "\n" ' '`
+v=`$CONFGET '-f' "$TESTDIR/t2.ini" '-s' '' 'key1' 'key2' | tr "\n" " "`
+res="$?"
if [ "$v" = 'key1=1 key2=2 ' ]; then echo 'ok 10'; else echo "not ok 10 v is '$v'"; fi
diff --git a/t/12-last-value.t b/t/12-last-value.t
index a4623bb..baa8122 100644
--- a/t/12-last-value.t
+++ b/t/12-last-value.t
@@ -1,6 +1,6 @@
#!/bin/sh
#
-# Copyright (c) 2008, 2016 Peter Pentchev
+# Copyright (c) 2008, 2011, 2019 Peter Pentchev
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@@ -27,45 +27,52 @@
[ -z "$CONFGET" ] && CONFGET='./confget'
[ -z "$TESTDIR" ] && TESTDIR='t'
-T3="$TESTDIR/t3.ini"
-
echo '1..14'
-if [ ! -f "$T3" ]; then
- echo "Bail out! No test file $T3"
- exit 255
-fi
-v=`$CONFGET -f "$T3" both`
+if [ ! -f "$TESTDIR/t3.ini" ]; then
+ echo "Bail out! No test file $TESTDIR/t3.ini"
+ exit 255
+fi
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" 'both' `
+res="$?"
if [ "$v" = 'default' ]; then echo 'ok 1'; else echo "not ok 1 v is '$v'"; fi
-v=`$CONFGET -f "$T3" defonly`
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" 'defonly' `
+res="$?"
if [ "$v" = 'default' ]; then echo 'ok 2'; else echo "not ok 2 v is '$v'"; fi
-v=`$CONFGET -f "$T3" aonly`
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" 'aonly' `
+res="$?"
if [ "$v" = '' ]; then echo 'ok 3'; else echo "not ok 3 v is '$v'"; fi
-
-v=`$CONFGET -f "$T3" -s '' both`
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" '-s' '' 'both' `
+res="$?"
if [ "$v" = 'default' ]; then echo 'ok 4'; else echo "not ok 4 v is '$v'"; fi
-v=`$CONFGET -f "$T3" -s '' defonly`
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" '-s' '' 'defonly' `
+res="$?"
if [ "$v" = 'default' ]; then echo 'ok 5'; else echo "not ok 5 v is '$v'"; fi
-v=`$CONFGET -f "$T3" -s '' aonly`
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" '-s' '' 'aonly' `
+res="$?"
if [ "$v" = '' ]; then echo 'ok 6'; else echo "not ok 6 v is '$v'"; fi
-
-v=`$CONFGET -f "$T3" -s 'a' both`
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" '-s' 'a' 'both' `
+res="$?"
if [ "$v" = 'a' ]; then echo 'ok 7'; else echo "not ok 7 v is '$v'"; fi
-v=`$CONFGET -f "$T3" -s 'a' defonly`
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" '-s' 'a' 'defonly' `
+res="$?"
if [ "$v" = '' ]; then echo 'ok 8'; else echo "not ok 8 v is '$v'"; fi
-v=`$CONFGET -f "$T3" -s 'a' aonly`
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" '-s' 'a' 'aonly' `
+res="$?"
if [ "$v" = 'a' ]; then echo 'ok 9'; else echo "not ok 9 v is '$v'"; fi
-
-v=`$CONFGET -f "$T3" -O -s 'a' both`
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" '-s' 'a' '-O' 'both' `
+res="$?"
if [ "$v" = 'a' ]; then echo 'ok 10'; else echo "not ok 10 v is '$v'"; fi
-v=`$CONFGET -f "$T3" -O -s 'a' defonly`
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" '-s' 'a' '-O' 'defonly' `
+res="$?"
if [ "$v" = 'default' ]; then echo 'ok 11'; else echo "not ok 11 v is '$v'"; fi
-v=`$CONFGET -f "$T3" -O -s 'a' aonly`
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" '-s' 'a' '-O' 'aonly' `
+res="$?"
if [ "$v" = 'a' ]; then echo 'ok 12'; else echo "not ok 12 v is '$v'"; fi
-
-# A non-matching value should override a matching one and nothing should be displayed.
-v=`$CONFGET -f "$T3" -O -s 'a' -m 'def*' both`
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" '-m' 'def*' '-s' 'a' '-O' 'both' `
+res="$?"
if [ "$v" = '' ]; then echo 'ok 13'; else echo "not ok 13 v is '$v'"; fi
-v=`$CONFGET -f "$T3" -O -s 'a' -m 'a*' both`
+v=`$CONFGET '-f' "$TESTDIR/t3.ini" '-m' 'a*' '-s' 'a' '-O' 'both' `
+res="$?"
if [ "$v" = 'a' ]; then echo 'ok 14'; else echo "not ok 14 v is '$v'"; fi
diff --git a/t/13-features.t b/t/13-bespoke-features.t
index 4ed1088..4ed1088 100644
--- a/t/13-features.t
+++ b/t/13-bespoke-features.t
diff --git a/t/14-too-many.t b/t/14-bespoke-too-many.t
index 5949fe3..5c1be63 100644
--- a/t/14-too-many.t
+++ b/t/14-bespoke-too-many.t
@@ -56,6 +56,13 @@ for args in \
'-q sections key1' \
'-q features -q feature BASE' \
'-q features key1'; do
+ if [ "${args#-q sections -q}" != "$args" ] || [ "${args#-q features -q}" != "$args" ]; then
+ if [ "${CONFGET#*python}" != "$CONFGET" ]; then
+ echo "ok $idx $args - skipped, Python argparse"
+ idx="$((idx + 1))"
+ continue
+ fi
+ fi
v=`$CONFGET -f "$T2" $args 2>&1`
if expr "x$v" : 'x.*Only a single ' > /dev/null; then echo "ok $idx $args"; else echo "not ok $idx args $args v is $v"; fi
idx="$((idx + 1))"
diff --git a/t/defs/schema/test-1.0.json b/t/defs/schema/test-1.0.json
new file mode 100644
index 0000000..065fe1f
--- /dev/null
+++ b/t/defs/schema/test-1.0.json
@@ -0,0 +1,176 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$id": "https://devel.ringlet.net/textproc/confget/schema/test-1.0.json",
+ "title": "confget test definition",
+ "description": "A definition of a test file for the confget test suite",
+ "definitions": {
+ "format": {
+ "type": "object",
+ "properties": {
+ "version": {
+ "type": "object",
+ "properties": {
+ "major": {
+ "type": "integer",
+ "const": 1
+ },
+ "minor": {
+ "type": "integer",
+ "const": 0
+ }
+ },
+ "required": ["major", "minor"],
+ "additionalProperties": false
+ }
+ },
+ "required": ["version"],
+ "additionalProperties": false
+ },
+
+ "setenv": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ }
+ },
+
+ "test_def": {
+ "type": "object",
+ "properties": {
+ "args": {
+ "$ref": "#/definitions/test_def_args"
+ },
+ "keys": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "xform": {
+ "type": "string",
+ "enum": [
+ "",
+ "count-lines",
+ "count-lines-eq",
+ "count-lines-non-eq",
+ "newline-to-space"
+ ]
+ },
+ "backend": {
+ "type": "string",
+ "enum": ["http_get", "ini"]
+ },
+ "setenv": {
+ "type": "boolean"
+ },
+ "stdin": {
+ "oneOf": [
+ {
+ "type": "null"
+ },
+ {
+ "type": "string",
+ "pattern": "^[0-9a-z_-]+\\.ini$"
+ }
+ ]
+ },
+ "output": {
+ "oneOf": [
+ {
+ "$ref": "#/definitions/output_exact"
+ },
+ {
+ "$ref": "#/definitions/output_exit_ok"
+ }
+ ]
+ }
+ },
+ "required": ["args", "keys", "output"],
+ "additionalProperties": false
+ },
+
+ "test_def_args": {
+ "type": "object",
+ "properties": {
+ "check_only": {
+ "type": "string",
+ "maxLength": 0
+ },
+ "filename": {
+ "type": "string"
+ },
+ "hide_var_name": {
+ "type": "string",
+ "maxLength": 0
+ },
+ "list_all": {
+ "type": "string",
+ "maxLength": 0
+ },
+ "match_var_names": {
+ "type": "string",
+ "maxLength": 0
+ },
+ "match_var_values": {
+ "type": "string"
+ },
+ "section": {
+ "type": "string"
+ },
+ "section_override": {
+ "type": "string",
+ "maxLength": 0
+ },
+ "section_specified": {
+ "type": "string",
+ "maxLength": 0
+ },
+ "show_var_name": {
+ "type": "string",
+ "maxLength": 0
+ }
+ },
+ "additionalProperties": false
+ },
+
+ "output_exact": {
+ "type": "object",
+ "properties": {
+ "exact": {
+ "type": "string"
+ }
+ },
+ "required": ["exact"],
+ "additionalProperties": false
+ },
+
+ "output_exit_ok": {
+ "type": "object",
+ "properties": {
+ "exit": {
+ "type": "boolean"
+ }
+ },
+ "required": ["exit"],
+ "additionalProperties": false
+ }
+ },
+
+ "type": "object",
+ "properties": {
+ "format": {
+ "$ref": "#/definitions/format"
+ },
+ "setenv": {
+ "$ref": "#/definitions/setenv"
+ },
+ "tests": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/test_def"
+ }
+ }
+ },
+ "required": ["format", "tests"],
+ "additionalProperties": false
+}
diff --git a/t/defs/tests/01-get-values.json b/t/defs/tests/01-get-values.json
new file mode 100644
index 0000000..eb89340
--- /dev/null
+++ b/t/defs/tests/01-get-values.json
@@ -0,0 +1,293 @@
+{
+ "format": {
+ "version": {
+ "major": 1,
+ "minor": 0
+ }
+ },
+ "setenv": {
+ "QUERY_STRING": "key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3",
+ "Q1": "key4&amp;key5=%09%09%20val%27ue5&key6",
+ "Q2": ""
+ },
+ "tests": [
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "a"
+ },
+ "keys": [
+ "key1"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "value1"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "a",
+ "show_var_name": ""
+ },
+ "keys": [
+ "key2"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "key2=value2"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "a"
+ },
+ "keys": [
+ "key3"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "\t\t val'ue3"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "b sect"
+ },
+ "keys": [
+ "key4"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "v'alu'e4"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "c"
+ },
+ "keys": [
+ "key5"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "\t\t# value5"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "a"
+ },
+ "keys": [
+ "key1",
+ "key2"
+ ],
+ "xform": "newline-to-space",
+ "backend": "ini",
+ "output": {
+ "exact": "key1=value1 key2=value2 "
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "a",
+ "hide_var_name": ""
+ },
+ "keys": [
+ "key6",
+ "key2"
+ ],
+ "xform": "newline-to-space",
+ "backend": "ini",
+ "output": {
+ "exact": "value2 value6 "
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "b sect"
+ },
+ "keys": [
+ "key7"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "value7"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "section": "b sect"
+ },
+ "keys": [
+ "key7"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "value7"
+ },
+ "setenv": false,
+ "stdin": "t1.ini"
+ },
+ {
+ "args": {
+ "section": "b sect"
+ },
+ "keys": [
+ "key77"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": ""
+ },
+ "setenv": false,
+ "stdin": "t1.ini"
+ },
+ {
+ "args": {},
+ "keys": [
+ "key1"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exact": "value1"
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "show_var_name": ""
+ },
+ "keys": [
+ "key2"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exact": "key2==value2&"
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {},
+ "keys": [
+ "key3"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exact": "\t\t val'ue3"
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "section": "Q1"
+ },
+ "keys": [
+ "key4"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exact": ""
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "section": "Q1"
+ },
+ "keys": [
+ "key5"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exact": "\t\t val'ue5"
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "section": "Q1"
+ },
+ "keys": [
+ "key6"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exact": ""
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "section": "Q1"
+ },
+ "keys": [
+ "key66"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exact": ""
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "section": "Q2"
+ },
+ "keys": [
+ "key66"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exact": ""
+ },
+ "setenv": true,
+ "stdin": null
+ }
+ ]
+}
diff --git a/t/defs/tests/02-check-values.json b/t/defs/tests/02-check-values.json
new file mode 100644
index 0000000..1772d80
--- /dev/null
+++ b/t/defs/tests/02-check-values.json
@@ -0,0 +1,258 @@
+{
+ "format": {
+ "version": {
+ "major": 1,
+ "minor": 0
+ }
+ },
+ "setenv": {
+ "QUERY_STRING": "key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3",
+ "Q1": "key4&amp;key5=%09%09%20val%27ue5&key6",
+ "Q2": ""
+ },
+ "tests": [
+ {
+ "args": {
+ "check_only": "",
+ "filename": "t1.ini",
+ "section": "a"
+ },
+ "keys": [
+ "key1"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exit": true
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": "",
+ "filename": "t1.ini",
+ "section": "a"
+ },
+ "keys": [
+ "key2"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exit": true
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": "",
+ "filename": "t1.ini",
+ "section": "a"
+ },
+ "keys": [
+ "key3"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exit": true
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": "",
+ "filename": "t1.ini",
+ "section": "a"
+ },
+ "keys": [
+ "key4"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exit": false
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": "",
+ "filename": "t1.ini",
+ "section": "b sect"
+ },
+ "keys": [
+ "key5"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exit": false
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": "",
+ "filename": "t1.ini",
+ "section": "b sect"
+ },
+ "keys": [
+ "key4"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exit": true
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": "",
+ "filename": "t1.ini",
+ "section": "c"
+ },
+ "keys": [
+ "key5"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exit": true
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": ""
+ },
+ "keys": [
+ "key1"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exit": true
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": ""
+ },
+ "keys": [
+ "key2"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exit": true
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": ""
+ },
+ "keys": [
+ "key3"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exit": true
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": "",
+ "section": "Q1"
+ },
+ "keys": [
+ "key4"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exit": true
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": "",
+ "section": "Q1"
+ },
+ "keys": [
+ "key6"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exit": true
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": ""
+ },
+ "keys": [
+ "key6"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exit": false
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": "",
+ "section": "Q1"
+ },
+ "keys": [
+ "key1"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exit": false
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "check_only": "",
+ "section": "Q2"
+ },
+ "keys": [
+ "key1"
+ ],
+ "xform": "",
+ "backend": "http_get",
+ "output": {
+ "exit": false
+ },
+ "setenv": true,
+ "stdin": null
+ }
+ ]
+}
diff --git a/t/defs/tests/03-list-all.json b/t/defs/tests/03-list-all.json
new file mode 100644
index 0000000..b85b867
--- /dev/null
+++ b/t/defs/tests/03-list-all.json
@@ -0,0 +1,118 @@
+{
+ "format": {
+ "version": {
+ "major": 1,
+ "minor": 0
+ }
+ },
+ "setenv": {
+ "QUERY_STRING": "key1=value1&key2=%3Dvalue2%26&amp;key3=%09%09%20val%27ue3",
+ "Q1": "key4&amp;key5=%09%09%20val%27ue5&key6",
+ "Q2": ""
+ },
+ "tests": [
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "a",
+ "list_all": ""
+ },
+ "keys": [],
+ "xform": "count-lines",
+ "backend": "ini",
+ "output": {
+ "exact": "4"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "b sect",
+ "list_all": "",
+ "show_var_name": ""
+ },
+ "keys": [],
+ "xform": "count-lines-eq",
+ "backend": "ini",
+ "output": {
+ "exact": "2"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "c",
+ "list_all": "",
+ "hide_var_name": ""
+ },
+ "keys": [],
+ "xform": "count-lines-non-eq",
+ "backend": "ini",
+ "output": {
+ "exact": "1"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "d",
+ "list_all": ""
+ },
+ "keys": [],
+ "xform": "count-lines",
+ "backend": "ini",
+ "output": {
+ "exact": "0"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "list_all": ""
+ },
+ "keys": [],
+ "xform": "count-lines",
+ "backend": "http_get",
+ "output": {
+ "exact": "3"
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "list_all": "",
+ "section": "Q1"
+ },
+ "keys": [],
+ "xform": "count-lines",
+ "backend": "http_get",
+ "output": {
+ "exact": "3"
+ },
+ "setenv": true,
+ "stdin": null
+ },
+ {
+ "args": {
+ "list_all": "",
+ "section": "Q2"
+ },
+ "keys": [],
+ "xform": "count-lines",
+ "backend": "http_get",
+ "output": {
+ "exact": "0"
+ },
+ "setenv": true,
+ "stdin": null
+ }
+ ]
+}
diff --git a/t/defs/tests/05-match-names.json b/t/defs/tests/05-match-names.json
new file mode 100644
index 0000000..7dd76c2
--- /dev/null
+++ b/t/defs/tests/05-match-names.json
@@ -0,0 +1,81 @@
+{
+ "format": {
+ "version": {
+ "major": 1,
+ "minor": 0
+ }
+ },
+ "setenv": {},
+ "tests": [
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "a",
+ "match_var_names": ""
+ },
+ "keys": [
+ "*"
+ ],
+ "xform": "count-lines",
+ "backend": "ini",
+ "output": {
+ "exact": "4"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "b sect",
+ "match_var_names": "",
+ "show_var_name": ""
+ },
+ "keys": [
+ "*"
+ ],
+ "xform": "count-lines-eq",
+ "backend": "ini",
+ "output": {
+ "exact": "2"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "a",
+ "match_var_names": "",
+ "hide_var_name": ""
+ },
+ "keys": [
+ "*ey2"
+ ],
+ "xform": "count-lines-non-eq",
+ "backend": "ini",
+ "output": {
+ "exact": "1"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "d",
+ "match_var_names": ""
+ },
+ "keys": [
+ "*ey2"
+ ],
+ "xform": "count-lines",
+ "backend": "ini",
+ "output": {
+ "exact": "0"
+ },
+ "setenv": false,
+ "stdin": null
+ }
+ ]
+}
diff --git a/t/defs/tests/06-get-default.json b/t/defs/tests/06-get-default.json
new file mode 100644
index 0000000..d20f812
--- /dev/null
+++ b/t/defs/tests/06-get-default.json
@@ -0,0 +1,87 @@
+{
+ "format": {
+ "version": {
+ "major": 1,
+ "minor": 0
+ }
+ },
+ "setenv": {},
+ "tests": [
+ {
+ "args": {
+ "filename": "t1.ini"
+ },
+ "keys": [
+ "key1"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "value1"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini"
+ },
+ "keys": [
+ "key4"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": ""
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini"
+ },
+ "keys": [
+ "key6"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "value6"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t2.ini"
+ },
+ "keys": [
+ "key1"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "1"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t2.ini"
+ },
+ "keys": [
+ "key1",
+ "key2"
+ ],
+ "xform": "newline-to-space",
+ "backend": "ini",
+ "output": {
+ "exact": "key1=1 key2=2 "
+ },
+ "setenv": false,
+ "stdin": null
+ }
+ ]
+}
diff --git a/t/defs/tests/07-match-values.json b/t/defs/tests/07-match-values.json
new file mode 100644
index 0000000..96c6e2a
--- /dev/null
+++ b/t/defs/tests/07-match-values.json
@@ -0,0 +1,166 @@
+{
+ "format": {
+ "version": {
+ "major": 1,
+ "minor": 0
+ }
+ },
+ "setenv": {},
+ "tests": [
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "a",
+ "match_var_values": "value*"
+ },
+ "keys": [
+ "key1"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "value1"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "a",
+ "match_var_values": "*ue2"
+ },
+ "keys": [
+ "key2"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "value2"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "a",
+ "match_var_values": "*val'ue*"
+ },
+ "keys": [
+ "key3"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "\t\t val'ue3"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "b sect",
+ "match_var_values": "*alu*"
+ },
+ "keys": [
+ "key4"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "v'alu'e4"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "c"
+ },
+ "keys": [
+ "key5"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "\t\t# value5"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "a",
+ "match_var_values": "*alu*"
+ },
+ "keys": [
+ "key1",
+ "key2"
+ ],
+ "xform": "newline-to-space",
+ "backend": "ini",
+ "output": {
+ "exact": "key1=value1 key2=value2 "
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "a",
+ "match_var_values": "*7"
+ },
+ "keys": [
+ "key6"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": ""
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "b sect",
+ "check_only": "",
+ "match_var_values": "*7"
+ },
+ "keys": [
+ "key7"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exit": true
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "b sect",
+ "check_only": "",
+ "match_var_values": "*7"
+ },
+ "keys": [
+ "key6"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exit": false
+ },
+ "setenv": false,
+ "stdin": null
+ }
+ ]
+}
diff --git a/t/defs/tests/11-no-default.json b/t/defs/tests/11-no-default.json
new file mode 100644
index 0000000..492043f
--- /dev/null
+++ b/t/defs/tests/11-no-default.json
@@ -0,0 +1,173 @@
+{
+ "format": {
+ "version": {
+ "major": 1,
+ "minor": 0
+ }
+ },
+ "setenv": {},
+ "tests": [
+ {
+ "args": {
+ "filename": "t1.ini"
+ },
+ "keys": [
+ "key1"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "value1"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini"
+ },
+ "keys": [
+ "key4"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": ""
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini"
+ },
+ "keys": [
+ "key6"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "value6"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "",
+ "section_specified": ""
+ },
+ "keys": [
+ "key1"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": ""
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "",
+ "section_specified": ""
+ },
+ "keys": [
+ "key4"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": ""
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t1.ini",
+ "section": "",
+ "section_specified": ""
+ },
+ "keys": [
+ "key6"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": ""
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t2.ini"
+ },
+ "keys": [
+ "key1"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "1"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t2.ini"
+ },
+ "keys": [
+ "key1",
+ "key2"
+ ],
+ "xform": "newline-to-space",
+ "backend": "ini",
+ "output": {
+ "exact": "key1=1 key2=2 "
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t2.ini",
+ "section": "",
+ "section_specified": ""
+ },
+ "keys": [
+ "key1"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "1"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t2.ini",
+ "section": "",
+ "section_specified": ""
+ },
+ "keys": [
+ "key1",
+ "key2"
+ ],
+ "xform": "newline-to-space",
+ "backend": "ini",
+ "output": {
+ "exact": "key1=1 key2=2 "
+ },
+ "setenv": false,
+ "stdin": null
+ }
+ ]
+}
diff --git a/t/defs/tests/12-last-value.json b/t/defs/tests/12-last-value.json
new file mode 100644
index 0000000..3005780
--- /dev/null
+++ b/t/defs/tests/12-last-value.json
@@ -0,0 +1,239 @@
+{
+ "format": {
+ "version": {
+ "major": 1,
+ "minor": 0
+ }
+ },
+ "setenv": {},
+ "tests": [
+ {
+ "args": {
+ "filename": "t3.ini"
+ },
+ "keys": [
+ "both"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "default"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t3.ini"
+ },
+ "keys": [
+ "defonly"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "default"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t3.ini"
+ },
+ "keys": [
+ "aonly"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": ""
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t3.ini",
+ "section": ""
+ },
+ "keys": [
+ "both"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "default"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t3.ini",
+ "section": ""
+ },
+ "keys": [
+ "defonly"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "default"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t3.ini",
+ "section": ""
+ },
+ "keys": [
+ "aonly"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": ""
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t3.ini",
+ "section": "a"
+ },
+ "keys": [
+ "both"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "a"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t3.ini",
+ "section": "a"
+ },
+ "keys": [
+ "defonly"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": ""
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t3.ini",
+ "section": "a"
+ },
+ "keys": [
+ "aonly"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "a"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t3.ini",
+ "section": "a",
+ "section_override": ""
+ },
+ "keys": [
+ "both"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "a"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t3.ini",
+ "section": "a",
+ "section_override": ""
+ },
+ "keys": [
+ "defonly"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "default"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t3.ini",
+ "section": "a",
+ "section_override": ""
+ },
+ "keys": [
+ "aonly"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "a"
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t3.ini",
+ "match_var_values": "def*",
+ "section": "a",
+ "section_override": ""
+ },
+ "keys": [
+ "both"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": ""
+ },
+ "setenv": false,
+ "stdin": null
+ },
+ {
+ "args": {
+ "filename": "t3.ini",
+ "match_var_values": "a*",
+ "section": "a",
+ "section_override": ""
+ },
+ "keys": [
+ "both"
+ ],
+ "xform": "",
+ "backend": "ini",
+ "output": {
+ "exact": "a"
+ },
+ "setenv": false,
+ "stdin": null
+ }
+ ]
+}
diff --git a/t/defs/tools/encode.py b/t/defs/tools/encode.py
new file mode 100644
index 0000000..32e961a
--- /dev/null
+++ b/t/defs/tools/encode.py
@@ -0,0 +1,105 @@
+#!/usr/bin/python3
+#
+# Copyright (c) 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+Encode a Python confget test data structure into a JSON file.
+"""
+
+import json
+
+from typing import Any, Dict
+
+from unit_tests.data import data as t_data
+from unit_tests.data import defs as t_defs
+
+
+class TestEncoder(json.JSONEncoder):
+ """ Encode the confget test data into serializable objects. """
+
+ def encode_test_file_def(self, obj: t_defs.TestFileDef) -> Dict[str, Any]:
+ """ Encode a full TestFileDef object. """
+ return {
+ 'format': {
+ 'version': {
+ 'major': 1,
+ 'minor': 0,
+ },
+ },
+ 'setenv': obj.setenv,
+ 'tests': [self.default(test) for test in obj.tests],
+ }
+
+ def encode_test_def(self, obj: t_defs.TestDef) -> Dict[str, Any]:
+ """ Encode a single test definition. """
+ return {
+ 'args': obj.args,
+ 'keys': obj.keys,
+ 'xform': obj.xform,
+ 'backend': obj.backend,
+ 'output': self.default(obj.output),
+ 'setenv': obj.setenv,
+ 'stdin': obj.stdin,
+ }
+
+ def encode_exact_output_def(self, obj: t_defs.TestExactOutputDef
+ ) -> Dict[str, str]:
+ """ Encode an exact output requirement. """
+ return {
+ 'exact': obj.exact,
+ }
+
+ def encode_exit_ok_output_def(self, obj: t_defs.TestExitOKOutputDef
+ ) -> Dict[str, bool]:
+ """ Encode an exit code requirement. """
+ return {
+ 'exit': obj.success,
+ }
+
+ SERIALIZERS = {
+ t_defs.TestFileDef: encode_test_file_def,
+ t_defs.TestDef: encode_test_def,
+ t_defs.TestExactOutputDef: encode_exact_output_def,
+ t_defs.TestExitOKOutputDef: encode_exit_ok_output_def,
+ }
+
+ def default(self, obj: Any) -> Any:
+ """ Meow. """
+ method = self.SERIALIZERS.get(type(obj), None)
+ if method is not None:
+ return method(self, obj)
+ return super(TestEncoder, self).default(obj)
+
+
+def main() -> None:
+ """ Main function: encode, output. """
+ for name, tdef in sorted(t_data.TESTS.items()):
+ print(f'--- {name} ---')
+ with open(f'output/{name}.json', mode='w') as outf:
+ print(json.dumps(tdef, indent=2, cls=TestEncoder), file=outf)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/t/defs/tools/generate.py b/t/defs/tools/generate.py
new file mode 100755
index 0000000..45dd99a
--- /dev/null
+++ b/t/defs/tools/generate.py
@@ -0,0 +1,146 @@
+#!/usr/bin/python3
+#
+# Copyright (c) 2018, 2019 Peter Pentchev <roam@ringlet.net>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+"""
+Generate shell scripts with TAP output for the confget tests.
+"""
+
+from typing import List
+
+from unit_tests import data as t_data
+
+
+PRELUDE = r'''#!/bin/sh
+#
+# Copyright (c) 2008, 2011, 2019 Peter Pentchev
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+[ -z "$CONFGET" ] && CONFGET='./confget'
+[ -z "$TESTDIR" ] && TESTDIR='t'
+
+echo '1..{count}'
+'''
+
+CHECK_TESTFILE = r'''
+if [ ! -f "$TESTDIR/{fname}" ]; then
+ echo "Bail out! No test file $TESTDIR/{fname}"
+ exit 255
+fi'''
+
+
+def add_cmdline_options(cmdline: List[str], test: t_data.defs.TestDef
+ ) -> None:
+ """ Add the options from test.args into cmdline. """
+ for name, value in test.args.items():
+ option = t_data.CMDLINE_OPTIONS[name]
+ if option[0] == '':
+ continue
+ cmdline.append(f"'{option[0]}'")
+ if option[1]:
+ if name == 'filename':
+ cmdline.append(f'"$TESTDIR/{value}"')
+ else:
+ cmdline.append(t_data.shell_escape(value))
+
+
+def main() -> None:
+ """ Main function: generate the test files. """
+ tests = t_data.load_all_tests('.')
+ for fname, data in sorted(tests.items()):
+ print(f'Generating {fname}.t with {len(data.tests)} tests')
+ with open(f'{fname}.t', mode='w') as testf:
+ print(PRELUDE.format(count=len(data.tests)), file=testf)
+
+ filenames = sorted({
+ test.args['filename'] for test in data.tests
+ if 'filename' in test.args
+ })
+ for filename in filenames:
+ print(CHECK_TESTFILE.format(fname=filename), file=testf)
+
+ for idx, test in enumerate(data.tests):
+ tap_idx = idx + 1
+
+ cmdline: List[str] = []
+ if test.setenv:
+ cmdline.append('env')
+ for name, value in sorted(data.setenv.items()):
+ cmdline.append(f'{name}={t_data.shell_escape(value)}')
+
+ cmdline.append('$CONFGET')
+ if test.backend != 'ini':
+ cmdline.extend(['-t', test.backend])
+
+ add_cmdline_options(cmdline, test)
+
+ if test.stdin is not None:
+ cmdline.extend([
+ t_data.CMDLINE_OPTIONS['filename'][0],
+ '-'
+ ])
+
+ cmdline.extend([f"'{key}'" for key in test.keys])
+
+ if test.stdin is not None:
+ cmdline.extend(['<', f'"$TESTDIR/{test.stdin}"'])
+
+ xform = t_data.XFORM[test.xform].command
+ print(f'- {tap_idx}: {" ".join(cmdline)} {xform}')
+
+ print(f'v=`{" ".join(cmdline)} {xform}`', file=testf)
+ print('res="$?"', file=testf)
+
+ check = test.output.get_check()
+ print(f' - {check}')
+ print(f"if {check}; then echo 'ok {tap_idx}'; else "
+ f'echo "not ok {tap_idx} {test.output.var_name} is '
+ f"'${test.output.var_name}'\"; fi",
+ file=testf)
+
+
+if __name__ == '__main__':
+ main()