# -*- coding: utf-8 -*-

import sys
import os
import unittest
import platform
import io

sys.path.insert(
    0,
    os.path.abspath(
        os.path.normpath(os.path.join(os.path.dirname(__file__), ".."))))

import configmix
import configmix.ini
import configmix.yaml
import configmix.json
import configmix.py
import configmix.toml
from configmix.compat import u


TESTDATADIR = os.path.join(
    os.path.abspath(os.path.dirname(__file__)),
    "data")


class T01Basic(unittest.TestCase):

    """Check with low-level internal interfaces"""

    def __check_types(self, cfg):
        self.assertEqual(u("the value"),
                         cfg.get("key1"))
        self.assertTrue(isinstance(cfg.get("key1"), type(u(''))))
        self.assertEqual(2, cfg.get("key2"))
        self.assertEqual(5.7, cfg.get("key3"))
        self.assertTrue(isinstance(cfg.get("key1"), type(u(''))))
        self.assertTrue(cfg.get("key4"))
        self.assertTrue(isinstance(cfg.get("key4"), bool))
        self.assertFalse(cfg.get("key5"))
        self.assertTrue(isinstance(cfg.get("key5"), bool))
        self.assertEqual(255, cfg.get("key6"))
        self.assertEqual(u("Umlaute: ÄÖÜäöüß"),
                         cfg.get("key7"))

    def __check_comment(self, cfg):
        # Check comments: low level comments are *not* filtered
        self.assertEqual(u("Comment 1"), cfg.get("__comment1"))
        self.assertEqual(u("Comment no 2"), cfg.get("__comment2"))

    def __check_no_comment(self, cfg):

        def _c(name):
            def _f():
                cfg[u(name)]
            return _f

        # Variables with leading underscores are *not*  imported by default
        self.assertRaises(KeyError, _c("__comment1"))
        self.assertRaises(KeyError, _c("__comment2"))

    def __check_tree(self, cfg):
        self.assertEqual(u("in the root namespace"),
                         cfg.get("key1"))
        self.assertEqual(u("in the root namespace -- too"),
                         cfg.get("key2"))
        self.assertEqual(32,
                         cfg["tree1"]["key3"])
        self.assertEqual(u("get this as `tree1.tree2.key4'"),
                         cfg["tree1"]["tree2"]["key4"])
        self.assertTrue(cfg["tree1"]["tree2"]["key5"])

    def test01_ini_types(self):
        cfg = configmix.ini.load(os.path.join(TESTDATADIR, "conf1.ini"))
        self.__check_types(cfg)
        self.__check_comment(cfg)

    def test01_toml_types(self):
        cfg = configmix.toml.load(os.path.join(TESTDATADIR, "conf1.toml"))
        self.__check_types(cfg)
        self.__check_comment(cfg)

    def test02_py_types(self):
        cfg = configmix.py.load(os.path.join(TESTDATADIR, "conf1.py"))
        self.__check_types(cfg)
        self.__check_no_comment(cfg)

    def test03_yaml_types(self):
        with io.open(os.path.join(TESTDATADIR, "conf1.yml"), "rt",
                     encoding="utf-8") as f:
            cfg = configmix.yaml.safe_load(f)
            if configmix.yaml.OrderedDict:
                self.assertTrue(isinstance(cfg, configmix.yaml.OrderedDict))
            self.__check_types(cfg)
            self.__check_comment(cfg)

    def test04_json_types(self):
        cfg = configmix.json.load(os.path.join(TESTDATADIR, "conf1.json"))
        self.assertTrue(isinstance(cfg, configmix.json.DictImpl))
        self.__check_types(cfg)
        self.__check_comment(cfg)

    def test05_py_export_all(self):
        # When __all__ is given only it's keys are exported
        cfg = configmix.py.load(os.path.join(TESTDATADIR, "conf2.py"))
        self.assertEqual(u("the next value"), cfg.get("key1"))
        self.assertTrue(isinstance(cfg.get("key1"), type(u(''))))
        self.assertTrue(cfg.get("key2") is None)

    def test06_py_hide_private(self):
        # When no __all__ is given all symbols with leading "_" are hidden
        cfg = configmix.py.load(os.path.join(TESTDATADIR, "conf3.py"))
        self.assertEqual(u("the next value "), cfg.get("key1"))
        self.assertTrue(isinstance(cfg.get("key1"), type(u(''))))
        self.assertTrue(cfg.get("_key2") is None)

    def test07_ini_tree(self):
        cfg = configmix.ini.load(os.path.join(TESTDATADIR, "conf10.ini"))
        self.__check_tree(cfg)

    def test08_py_tree(self):
        cfg = configmix.py.load(os.path.join(TESTDATADIR, "conf10.py"))
        self.__check_tree(cfg)

    def test09_yaml_tree(self):
        with io.open(os.path.join(TESTDATADIR, "conf10.yml"), "rt",
                     encoding="utf-8") as f:
            cfg = configmix.yaml.safe_load(f)
            self.__check_tree(cfg)

    def test10_json_tree(self):
        cfg = configmix.json.load(os.path.join(TESTDATADIR, "conf10.json"))
        self.__check_tree(cfg)

    def test11_toml_tree(self):
        cfg = configmix.toml.load(os.path.join(TESTDATADIR, "conf10.toml"))
        self.__check_tree(cfg)

    def test12_yaml_no_duplicate_keys(self):
        import yaml.constructor as yc
        with io.open(os.path.join(TESTDATADIR, "duplicate-keys.yml"), "rt",
                     encoding="utf-8") as f:
            cfg = configmix.yaml.safe_load(f)

        with io.open(os.path.join(TESTDATADIR, "duplicate-keys.yml"), "rt",
                     encoding="utf-8") as f:
            self.assertRaises(yc.ConstructorError,
                              configmix.yaml.safe_load,
                              f, strict=True)

    def test13_yaml_no_duplicate_keys_2(self):
        import yaml.constructor as yc
        with io.open(os.path.join(TESTDATADIR, "duplicate-keys-2.yml"), "rt",
                     encoding="utf-8") as f:
            cfg = configmix.yaml.safe_load(f)

        with io.open(os.path.join(TESTDATADIR, "duplicate-keys-2.yml"), "rt",
                     encoding="utf-8") as f:
            self.assertRaises(yc.ConstructorError,
                              configmix.yaml.safe_load,
                              f, strict=True)


class _T02MixinLoadAndMerge:

    def test01_load(self):
        cfg = self._load(
            os.path.join(TESTDATADIR, "conf20.yml"),
            os.path.join(TESTDATADIR, "conf21.yml"))

        self.assertEqual(u("the_database_user"),
                         cfg.getvar_s("db.user.name"))
        self.assertEqual(u("the-database-password"),
                         cfg.getvar_s("db.user.pwd"))

        tmpdir = cfg.getvar_s("tmpdir")
        if os.name == 'nt':
            self.assertFalse(u('/') in tmpdir)
        self.assertEqual(os.path.normpath(
            os.path.abspath(os.path.join(os.getcwd(), "tmp"))),
                         tmpdir)

        self.assertEqual(u("anotherhost"),
                         cfg.getvar_s("db.locinfo.ro.hostname"))
        self.assertEqual(u("localhost"),
                         cfg.getvar_s("db.locinfo.rw.hostname"))

        self.assertEqual(5432, cfg.getvar_s("db.locinfo.ro.port"))

        url = cfg.getvar_s("db.engines.ro.url")
        self.assertEqual(
            u("postgresql+psycopg2://the_database_user:the-database-password@anotherhost:5432/my_database_catalog"),
            url)

        self.assertEqual(u("not a list any more"),
                         cfg.getvar_s("test.List"))

        self.assertEqual(list(range(0, 3)),
                         cfg.getvar_s("test.Str"))

    def test02_load_with_ini(self):
        cfg = self._load(
            os.path.join(TESTDATADIR, "conf20.yml"),
            os.path.join(TESTDATADIR, "conf21.yml"),
            os.path.join(TESTDATADIR, "conf22.ini"))

        self.assertEqual(u("the_database_user_2"),
                         cfg.getvar_s("db.user.name"))
        self.assertEqual(u("the-database-password-2"),
                         cfg.getvar_s("db.user.pwd"))

        tmpdir = cfg.getvar_s("tmpdir")
        self.assertEqual(u(os.getcwd()) + u("/tmp\\2"), tmpdir)

        self.assertEqual(u("3rd-host"),
                         cfg.getvar_s("db.locinfo.ro.hostname"))
        self.assertEqual(u("localhost"),
                         cfg.getvar_s("db.locinfo.rw.hostname"))

        self.assertEqual(5432, cfg.getvar_s("db.locinfo.ro.port"))

        url = cfg.getvar_s("db.engines.ro.url")
        self.assertEqual(
            u("postgresql+psycopg2://the_database_user_2:the-database-password-2@3rd-host:5432/my_database_catalog"),
            url)

    def test02_load_with_json(self):
        cfg = self._load(
            os.path.join(TESTDATADIR, "conf20.yml"),
            os.path.join(TESTDATADIR, "conf21.yml"),
            # .ini replaced with an equivalent .json
            os.path.join(TESTDATADIR, "conf23.json"))

        self.assertEqual(u("the_database_user_2"),
                         cfg.getvar_s("db.user.name"))
        self.assertEqual(u("the-database-password-2"),
                         cfg.getvar_s("db.user.pwd"))

        tmpdir = cfg.getvar_s("tmpdir")
        self.assertEqual(u(os.getcwd()) + u("/tmp\\3"), tmpdir)

        self.assertEqual(u("3rd-host"),
                         cfg.getvar_s("db.locinfo.ro.hostname"))
        self.assertEqual(u("localhost"),
                         cfg.getvar_s("db.locinfo.rw.hostname"))

        self.assertEqual(5432, cfg.getvar_s("db.locinfo.ro.port"))

        url = cfg.getvar_s("db.engines.ro.url")
        self.assertEqual(
            u("postgresql+psycopg2://the_database_user_2:the-database-password-2@3rd-host:5432/my_database_catalog"),
            url)

    def test02_load_with_toml(self):
        cfg = self._load(
            os.path.join(TESTDATADIR, "conf20.yml"),
            os.path.join(TESTDATADIR, "conf21.yml"),
            # .ini replaced with an equivalent .toml
            os.path.join(TESTDATADIR, "conf24.toml"))

        self.assertEqual(u("the_database_user_2"),
                         cfg.getvar_s("db.user.name"))
        self.assertEqual(u("the-database-password-2"),
                         cfg.getvar_s("db.user.pwd"))

        tmpdir = cfg.getvar_s("tmpdir")
        self.assertEqual(u(os.getcwd()) + u("/tmp\\4"), tmpdir)

        self.assertEqual(u("3rd-host"),
                         cfg.getvar_s("db.locinfo.ro.hostname"))
        self.assertEqual(u("localhost"),
                         cfg.getvar_s("db.locinfo.rw.hostname"))

        self.assertEqual(5432, cfg.getvar_s("db.locinfo.ro.port"))

        url = cfg.getvar_s("db.engines.ro.url")
        self.assertEqual(
            u("postgresql+psycopg2://the_database_user_2:the-database-password-2@3rd-host:5432/my_database_catalog"),
            url)

    def test03_namespace(self):
        cfg = self._load(
            os.path.join(TESTDATADIR, "conf20.yml"),
            os.path.join(TESTDATADIR, "conf21.yml"),
            os.path.join(TESTDATADIR, "conf22.ini"))
        self.assertEqual(u(os.getcwd()), cfg.getvar("OS:cwd"))
        self.assertEqual(u(platform.python_version()),
                         cfg.getvar_s("PY:version"))

    def test03_namespace_l(self):
        cfg = self._load(
            os.path.join(TESTDATADIR, "conf20.yml"),
            os.path.join(TESTDATADIR, "conf21.yml"),
            os.path.join(TESTDATADIR, "conf22.ini"))
        self.assertEqual(u(os.getcwd()), cfg.getvarl("cwd", namespace="OS"))
        self.assertEqual(u(platform.python_version()),
                         cfg.getvarl_s("version", namespace="PY"))

    def test04_no_filter(self):
        cfg = self._load(
            os.path.join(TESTDATADIR, "conf20.yml"),
            os.path.join(TESTDATADIR, "conf21.yml"),
            os.path.join(TESTDATADIR, "conf22.ini"))

        def _look():
            return cfg.getvar("OS:cwd|upper")

        self.assertRaises(KeyError, _look)

    def test04_no_filter_l(self):
        cfg = self._load(
            os.path.join(TESTDATADIR, "conf20.yml"),
            os.path.join(TESTDATADIR, "conf21.yml"),
            os.path.join(TESTDATADIR, "conf22.ini"))

        def _look():
            return cfg.getvarl("cwd|upper", namespace="OS")

        self.assertRaises(KeyError, _look)

    def test05_comments(self):
        cfg = self._load(os.path.join(TESTDATADIR, "conf20.yml"),
                         os.path.join(TESTDATADIR, "conf21.yml"),
                         os.path.join(TESTDATADIR, "conf22.ini"),
                         os.path.join(TESTDATADIR, "conf23.json"),
                         os.path.join(TESTDATADIR, "conf24.toml"))

        def _c(name):
            def _f():
                cfg.getvar_s(name)
            return _f

        # Variables with leading underscores are *not*  imported by default
        self.assertEqual(0o0027, int(cfg.getvar_s("process.umask"), 0))
        self.assertRaises(KeyError, _c("process.__doc1"))
        self.assertRaises(KeyError, _c("db.__comment1"))
        self.assertRaises(KeyError, _c("db.user.__doc2"))

    def test05_comments_l(self):
        cfg = self._load(os.path.join(TESTDATADIR, "conf20.yml"),
                         os.path.join(TESTDATADIR, "conf21.yml"),
                         os.path.join(TESTDATADIR, "conf22.ini"),
                         os.path.join(TESTDATADIR, "conf23.json"),
                         os.path.join(TESTDATADIR, "conf24.toml"))

        def _c(*names):
            def _f():
                cfg.getvarl_s(*names)
            return _f

        # Variables with leading underscores are *not*  imported by default
        self.assertEqual(0o0027, int(cfg.getvarl_s("process", "umask"), 0))
        self.assertRaises(KeyError, _c("process", "__doc1"))
        self.assertRaises(KeyError, _c("db", "__comment1"))
        self.assertRaises(KeyError, _c("db", "user", "__doc2"))

    def test06_check_all_comments(self):
        cfg = self._load(os.path.join(TESTDATADIR, "conf20.yml"),
                         os.path.join(TESTDATADIR, "conf21.yml"),
                         os.path.join(TESTDATADIR, "conf22.ini"),
                         os.path.join(TESTDATADIR, "conf23.json"),
                         os.path.join(TESTDATADIR, "conf24.toml"))

        def _check(d):
            for k, v in d.items():
                self.assertFalse(configmix._is_comment(k))
                if isinstance(v, dict):
                    _check(v)

        _check(cfg)

    def test07_deletions(self):
        cfg = self._load(os.path.join(TESTDATADIR, "conf20.yml"),
                         os.path.join(TESTDATADIR, "conf21.yml"),
                         os.path.join(TESTDATADIR, "conf22.ini"),
                         os.path.join(TESTDATADIR, "conf23.json"),
                         os.path.join(TESTDATADIR, "conf24.toml"),
                         os.path.join(TESTDATADIR, "delete-in-dict.yml"))
        # automatic clean-up
        self.assertRaises(KeyError, cfg.getvar_s, "not-deleted")
        # explicit deletion
        self.assertRaises(KeyError, cfg.getvar_s, "to-be-deleted")
        self.assertRaises(KeyError, cfg.getvar_s, "db.user.name")
        self.assertEqual("the-database-password-2", cfg.getvar_s("db.user.pwd"))
        self.assertRaises(KeyError, cfg.getvar_s, "test.Str")
        self.assertEqual("not a list any more", cfg.getvar_s("test.List"))
        self.assertEqual("the last value",
                         cfg.getvar_s("to-be-deleted-but-reassigned"))

    def test07_deletions_l(self):
        cfg = self._load(os.path.join(TESTDATADIR, "conf20.yml"),
                         os.path.join(TESTDATADIR, "conf21.yml"),
                         os.path.join(TESTDATADIR, "conf22.ini"),
                         os.path.join(TESTDATADIR, "conf23.json"),
                         os.path.join(TESTDATADIR, "conf24.toml"),
                         os.path.join(TESTDATADIR, "delete-in-dict.yml"))
        # automatic clean-up
        self.assertRaises(KeyError, cfg.getvarl_s, "not-deleted")
        # explicit deletion
        self.assertRaises(KeyError, cfg.getvarl_s, "to-be-deleted")
        self.assertRaises(KeyError, cfg.getvarl_s, "db" "user.name")
        self.assertEqual("the-database-password-2",
                         cfg.getvarl_s("db", "user", "pwd"))
        self.assertRaises(KeyError, cfg.getvarl_s, "test", "Str")
        self.assertEqual("not a list any more", cfg.getvarl_s("test", "List"))
        self.assertEqual("the last value",
                         cfg.getvarl_s("to-be-deleted-but-reassigned"))

    def test08_None_filter_single(self):
        cfg = self._load()
        x = cfg.expand_variable("{{non-existing|None}}")
        self.assertIsNone(x)

    def test09_None_filter_embedded(self):
        cfg = self._load()
        x = cfg.expand_variable("A{{non-existing|None}}Z")
        self.assertEqual("AZ", x)

    def test10_Empty_filtersingle(self):
        cfg = self._load()
        x = cfg.expand_variable("{{non-existing|Empty}}")
        self.assertEqual("", x)

    def test11_None_filter_pass_through(self):
        cfg = self._load(os.path.join(TESTDATADIR, "conf20.yml"),
                         os.path.join(TESTDATADIR, "conf21.yml"),
                         os.path.join(TESTDATADIR, "conf22.ini"),
                         os.path.join(TESTDATADIR, "conf23.json"),
                         os.path.join(TESTDATADIR, "conf24.toml"))
        x = cfg.expand_variable("{{intl.cache.items|None}}")
        self.assertEqual(10, x)

    def test12_Empty_filter_pass_through(self):
        cfg = self._load(os.path.join(TESTDATADIR, "conf20.yml"),
                         os.path.join(TESTDATADIR, "conf21.yml"),
                         os.path.join(TESTDATADIR, "conf22.ini"),
                         os.path.join(TESTDATADIR, "conf23.json"),
                         os.path.join(TESTDATADIR, "conf24.toml"))
        x = cfg.expand_variable("{{intl.cache.items|Empty}}")
        self.assertEqual(10, x)

    def test13_keyerror(self):
        cfg = self._load(os.path.join(TESTDATADIR, "conf20.yml"))
        self.assertRaises(KeyError, cfg.getvar_s, "non.existing.key")

    def test14_getvar_with_default(self):
        cfg = self._load(os.path.join(TESTDATADIR, "conf20.yml"))
        self.assertEqual("999", cfg.getvar("non.existing.key", default="999"))

    def test15_getvar_s_with_default(self):
        cfg = self._load(os.path.join(TESTDATADIR, "conf20.yml"))
        self.assertEqual("999", cfg.getvar_s("non.existing.key",
                                             default="999"))

    def test16_getintvar_s_with_default(self):
        cfg = self._load(os.path.join(TESTDATADIR, "conf20.yml"))
        self.assertEqual(9999, cfg.getintvar_s("non.existing.key",
                                               default=9999))
    def test17_getintvar_s_with_default(self):
        cfg = self._load(os.path.join(TESTDATADIR, "conf20.yml"))
        self.assertFalse(cfg.getboolvar_s("non.existing.key",
                                          default="false"))


class T02LoadAndMerge(_T02MixinLoadAndMerge, unittest.TestCase):

    def setUp(self):
        self._load = configmix.load

    def test0100_identity(self):
        cfg = configmix.ini.load(os.path.join(TESTDATADIR, "conf1.ini"))
        cfg2 = configmix.merge(cfg, None)
        self.assertEqual(id(cfg), id(cfg2))

    def test0101_identity(self):
        cfg = configmix.ini.load(os.path.join(TESTDATADIR, "conf1.ini"))
        cfg2 = configmix.merge(cfg, {})
        self.assertEqual(id(cfg), id(cfg2))


class T03SafeLoadAndMerge(_T02MixinLoadAndMerge, unittest.TestCase):

    def setUp(self):
        self._load = configmix.safe_load

    def test0100_deepcopy(self):
        cfg = configmix.ini.load(os.path.join(TESTDATADIR, "conf1.ini"))
        cfg2 = configmix.safe_merge(cfg, None)
        self.assertNotEqual(id(cfg), id(cfg2))

    def test0101_deepcopy(self):
        cfg = configmix.ini.load(os.path.join(TESTDATADIR, "conf1.ini"))
        cfg2 = configmix.safe_merge(cfg, {})
        self.assertNotEqual(id(cfg), id(cfg2))


class T04CustomExtension(unittest.TestCase):

    def setUp(self):
        self._reset()

    def tearDown(self):
        self._reset()

    def _reset(self):
        configmix.clear_assoc()
        for pat, fmode in configmix.DEFAULT_ASSOC:
            configmix.set_assoc(pat, fmode)

    def test01_additional(self):
        configmix.set_assoc("*.conf", configmix.get_default_assoc("*.yml"))
        cfg = configmix.load(
            os.path.join(TESTDATADIR, "conf1.ini"),
            os.path.join(TESTDATADIR, "conf30.conf"))
        self.assertEqual(u("Umlaute: ÄÖÜäöüß"), cfg.getvar_s("key7"))
        self.assertEqual(u("new value"), cfg.getvar_s("key-new"))

    def test02_only_style_wrong_style(self):
        configmix.clear_assoc()
        configmix.set_assoc("*.conf", configmix.get_default_assoc("*.yml"))

        def _ld():
            return configmix.load(os.path.join(TESTDATADIR, "conf1.ini"),
                                  os.path.join(TESTDATADIR, "conf30.conf"))

        self.assertRaises(ValueError, _ld)

    def test02_ignore_one_style(self):
        configmix.clear_assoc()
        configmix.set_assoc("*.conf", configmix.get_default_assoc("*.yml"))
        configmix.set_assoc("*", "-*-ignore-*-", append=True)

        cfg = configmix.load(os.path.join(TESTDATADIR, "conf1.ini"),
                             os.path.join(TESTDATADIR, "conf30.conf"))
        self.assertEqual(u("new value"), cfg.getvar_s("key-new"))
        self.assertRaises(KeyError, cfg.getvar_s, "key1")

    def test02_ignore_all(self):
        configmix.clear_assoc()
        configmix.set_assoc("*.conf", configmix.get_default_assoc("*.yml"))
        # This inserts at index 0 effectively ignoring all configuration files!
        configmix.set_assoc("*", "-*-ignore-*-")

        cfg = configmix.load(os.path.join(TESTDATADIR, "conf1.ini"),
                             os.path.join(TESTDATADIR, "conf30.conf"))
        self.assertRaises(KeyError, cfg.getvar_s, "key-new")
        self.assertRaises(KeyError, cfg.getvar_s, "key1")

    def test03_only_style_corrrect_style(self):
        configmix.clear_assoc()
        configmix.set_assoc("*.conf", configmix.get_default_assoc("*.yml"))
        cfg = configmix.load(os.path.join(TESTDATADIR, "conf30.conf"))
        self.assertEqual(u("new value"), cfg.getvar_s("key-new"))

        def _g():
            return cfg.getvar_s("key7")

        self.assertRaises(KeyError, _g)

    def test04_determine_mode(self):
        configmix.clear_assoc()
        configmix.set_assoc("*.conf", configmix.try_determine_filemode)
        cfg = configmix.load(os.path.join(TESTDATADIR, "conf30.conf"))
        self.assertEqual(u("new value"), cfg.getvar_s("key-new"))

        self.assertRaises(KeyError, cfg.getvar_s, "key7")

    def test05_try_determine_mode_none(self):
        configmix.clear_assoc()
        configmix.set_assoc("*.conf", configmix.try_determine_filemode)

        def _ld():
            return configmix.load(os.path.join(TESTDATADIR, "no-mode.conf"))

        self.assertRaises(ValueError, _ld)

    def test06_try_determine_mode_unknown(self):
        configmix.clear_assoc()
        configmix.set_assoc("*.conf", configmix.try_determine_filemode)

        def _ld():
            return configmix.load(os.path.join(TESTDATADIR, "unknown-mode.conf"))

        self.assertRaises(KeyError, _ld)


class T05SubstituteExpand(unittest.TestCase):

    def setUp(self):
        self._reset()

    def tearDown(self):
        self._reset()

    def _reset(self):
        configmix.clear_assoc()
        for pat, fmode in configmix.DEFAULT_ASSOC:
            configmix.set_assoc(pat, fmode)

    def test01_expand_int_ini(self):
        cfg = configmix.load(os.path.join(TESTDATADIR, "conf1.ini"))
        self.assertEqual(2, cfg.getvar_s("key100"))

    def test01_expand_int_ini_l(self):
        cfg = configmix.load(os.path.join(TESTDATADIR, "conf1.ini"))
        self.assertEqual(2, cfg.getvarl_s("key100"))

    def test02_expand_int_indirect_ini(self):
        cfg = configmix.load(os.path.join(TESTDATADIR, "conf1.ini"))
        self.assertEqual(2, cfg.getvar_s("key102"))

    def test02_expand_int_indirect_ini_l(self):
        cfg = configmix.load(os.path.join(TESTDATADIR, "conf1.ini"))
        self.assertEqual(2, cfg.getvarl_s("key102"))

    def test03_expand_int2str_ini(self):
        cfg = configmix.load(os.path.join(TESTDATADIR, "conf1.ini"))
        self.assertEqual("the 2 value", cfg.getvar_s("key101"))

    def test03_expand_int2str_ini_l(self):
        cfg = configmix.load(os.path.join(TESTDATADIR, "conf1.ini"))
        self.assertEqual("the 2 value", cfg.getvarl_s("key101"))

    def test04_expand_intint2str_ini(self):
        cfg = configmix.load(os.path.join(TESTDATADIR, "conf1.ini"))
        self.assertEqual("22", cfg.getvar_s("key103"))

    def test04_expand_intint2str_ini_l(self):
        cfg = configmix.load(os.path.join(TESTDATADIR, "conf1.ini"))
        self.assertEqual("22", cfg.getvarl_s("key103"))


class T06References(unittest.TestCase):

    def setUp(self):
        self._reset()
        self._cfg = configmix.load(
            os.path.join(TESTDATADIR, "conf20.yml"),
            os.path.join(TESTDATADIR, "conf21.yml"),
            os.path.join(TESTDATADIR, "conf22.ini"),
            os.path.join(TESTDATADIR, "conf23.json"),
            os.path.join(TESTDATADIR, "conf24.toml"),
            os.path.join(TESTDATADIR, "reference-style.yml"))

    def tearDown(self):
        self._reset()

    def _reset(self):
        configmix.clear_assoc()
        for pat, fmode in configmix.DEFAULT_ASSOC:
            configmix.set_assoc(pat, fmode)

    def test01_reference_without_expansions(self):
        self.assertTrue(isinstance(self._cfg.getvar("wsgi.profiler"), dict))
        self.assertTrue(isinstance(
            self._cfg.getvar("wsgi.profiler.params"), dict))
        self.assertEqual("werkzeug",
                         self._cfg.getvar("wsgi.profiler.params.type"))
        self.assertTrue(self._cfg.getvar("wsgi.profiler.params.params.evalex"))
        self.assertEqual(self._cfg.getvar("wsgi.debugger"),
                         self._cfg.getvar("wsgi.profiler.params"))

    def test01_reference_without_expansions_l(self):
        self.assertTrue(isinstance(self._cfg.getvarl("wsgi", "profiler"), dict))
        self.assertTrue(isinstance(
            self._cfg.getvarl("wsgi", "profiler", "params"), dict))
        self.assertEqual(
            "werkzeug",
            self._cfg.getvarl("wsgi", "profiler", "params", "type"))
        self.assertTrue(self._cfg.getvarl(
            "wsgi", "profiler", "params", "params", "evalex"))
        self.assertEqual(self._cfg.getvarl("wsgi", "debugger"),
                         self._cfg.getvarl("wsgi", "profiler", "params"))

    def test02_reference__with_expansions(self):
        self.assertTrue(isinstance(self._cfg.getvar_s("wsgi.profiler"), dict))
        self.assertTrue(isinstance(
            self._cfg.getvar_s("wsgi.profiler.params"), dict))
        self.assertTrue(
            self._cfg.getvar_s("wsgi.profiler.params.params.evalex"))
        self.assertEqual("werkzeug",
                         self._cfg.getvar_s("wsgi.profiler.params.type"))

    def test02_reference__with_expansions_l(self):
        self.assertTrue(isinstance(
            self._cfg.getvarl_s("wsgi", "profiler"), dict))
        self.assertTrue(isinstance(
            self._cfg.getvarl_s("wsgi", "profiler", "params"), dict))
        self.assertTrue(
            self._cfg.getvarl_s("wsgi", "profiler", "params", "params", "evalex"))
        self.assertEqual(
            "werkzeug",
            self._cfg.getvarl_s("wsgi", "profiler", "params", "type"))

    def test03_no_direct_attribute_access_to_expanded_references(self):
        self.assertEqual(
            "{{ref:#wsgi.debugger}}",
            self._cfg.wsgi.profiler.params)
        try:
            self._cfg.wsgi.profiler.params.type
        except AttributeError:
            pass
        else:
            self.fail("no attribute error seen")

    def test04_indirect_recursive_references(self):
        self.assertEqual(
            "werkzeug",
            self._cfg.getvar_s("testref.here.params.type"))
        self.assertTrue(
            self._cfg.getvar_s("testref.here.params.params.evalex"))

    def test04_indirect_recursive_references_l(self):
        self.assertEqual(
            "werkzeug",
            self._cfg.getvarl_s("testref", "here", "params", "type"))
        self.assertTrue(
            self._cfg.getvarl_s("testref", "here", "params", "params", "evalex"))

    def test05_recursive_expansion(self):
        c = self._cfg.getvar_s("testref")
        self.assertTrue(c["here"]["params"]["params"]["evalex"])

    def test05_recursive_expansion_l(self):
        c = self._cfg.getvarl_s("testref")
        self.assertTrue(c["here"]["params"]["params"]["evalex"])

    def test06_no_recursive_expansion_in_getvar_parents(self):
        v = self._cfg.getvar("wsgi.profiler")
        self.assertEqual(
            "{{ref:#wsgi.debugger}}",
            v["params"])

    def test06_no_recursive_expansion_in_getvar_parents_l(self):
        v = self._cfg.getvarl("wsgi", "profiler")
        self.assertEqual(
            "{{ref:#wsgi.debugger}}",
            v["params"])

    def test07_explicit_reference_expansion(self):
        v = self._cfg.getvar("wsgi.profiler")
        self.assertTrue(isinstance(self._cfg.expand_if_reference(v["params"]),
                                   dict))

    def test07_explicit_reference_expansion_l(self):
        v = self._cfg.getvarl("wsgi", "profiler")
        self.assertTrue(isinstance(self._cfg.expand_if_reference(v["params"]),
                                   dict))

    def test08_explicit_indirect_expansion_through_value(self):
        v = self._cfg.getvar_s("expand-ref-value.key0")
        self.assertTrue(isinstance(v, bool))
        self.assertTrue(v)
        # but not that .getvar does not **not**
        v2 = self._cfg.getvar("expand-ref-value.key0")
        self.assertEqual("{{testref.here.params.params.evalex}}", v2)

    def test08_explicit_indirect_expansion_through_value_l(self):
        v = self._cfg.getvarl_s("expand-ref-value", "key0")
        self.assertTrue(isinstance(v, bool))
        self.assertTrue(v)
        # but not that .getvar does not **not**
        v2 = self._cfg.getvarl("expand-ref-value", "key0")
        self.assertEqual("{{testref.here.params.params.evalex}}", v2)


class T07Quoting(unittest.TestCase):

    def setUp(self):
        self._reset()
        self._cfg = configmix.load(os.path.join(TESTDATADIR, "quoting.yml"))

    def tearDown(self):
        self._reset()

    def _reset(self):
        configmix.clear_assoc()
        for pat, fmode in configmix.DEFAULT_ASSOC:
            configmix.set_assoc(pat, fmode)

    def test_getvar(self):
        self.assertEqual(
            "value",
            self._cfg.getvar("%x23%x3a%x7c%x25%x2e.%x2e.%x3a.%x25.%x7c./.%x23"))
        self.assertEqual(
            "value",
            self._cfg.getvar(
                "%u0023%u003a%u007c%u0025%u002e.%u002e.%u003a.%u0025.%u007c./.%u0023"))
        self.assertEqual(
            "value",
            self._cfg.getvar(
                "%U00000023%U0000003a%U0000007c%U00000025%U0000002e.%U0000002e.%U0000003a.%U00000025.%U0000007c./.%U00000023"))

    def test_getvar_s(self):
        self.assertEqual(
            "value",
            self._cfg.getvar_s("%x23%x3a%x7c%x25%x2e.%x2e.%x3a.%x25.%x7c./.%x23"))
        self.assertEqual(
            "value",
            self._cfg.getvar_s(
                "%u0023%u003a%u007c%u0025%u002e.%u002e.%u003a.%u0025.%u007c./.%u0023"))
        self.assertEqual(
            "value",
            self._cfg.getvar_s(
                "%U00000023%U0000003a%U0000007c%U00000025%U0000002e.%U0000002e.%U0000003a.%U00000025.%U0000007c./.%U00000023"))

    def test_getvarl(self):
        self.assertEqual(
            "value",
            self._cfg.getvarl("#:|%.", ".", ":", "%", "|", "/", "#"))

    def test_getvarl_s(self):
        self.assertEqual(
            "value",
            self._cfg.getvarl_s("#:|%.", ".", ":", "%", "|", "/", "#"))

    def test_interpolation1(self):
        self.assertEqual(
            "value",
            self._cfg.getvarl_s("events", "qc-2021.1-5G-summit", "xname"))

    def test_interpolation2(self):
        self.assertEqual(
            "value",
            self._cfg.getvar_s("events.qc-2021%x2e1-5G-summit.xname"))

    def test_reference1(self):
        self.assertTrue(
            isinstance(
                self._cfg.getvar_s("events.qc-2021%x2e1-5G-summit.xref"),
                dict))

    def test_reference2(self):
        self.assertEqual(
            "value",
            self._cfg.getvar_s("events.qc-2021%x2e1-5G-summit.xref.%x23"))

    def test_quoting_and_unquoting_are_inverse(self):
        for c in """%.:#|"'{}[]""":
            qc = self._cfg.quote(c)
            self.assertTrue(qc.startswith("%x") and len(qc) == 4)
            self.assertEqual(c, self._cfg.unquote(qc))

    def test_quoting_and_unquoting_are_identical(self):
        # other characters
        for c in """abc09/""":
            qc = self._cfg.quote(c)
            self.assertEqual(c, qc)
            self.assertEqual(c, self._cfg.unquote(qc))

    def test_namespace_quoting(self):
        v1 = self._cfg.getvar("PY:version")
        v2 = self._cfg.getvar("P%x59:version")
        v3 = self._cfg.getvar("%U00000050Y:version")
        v4 = self._cfg.getvar("%x50%u0059:version")
        v5 = self._cfg.getvarl_s("events", "qc-2021.1-5G-summit", "xver")
        v6 = self._cfg.getvar_s("events.qc-2021%x2e1-5G-summit.xver")
        self.assertEqual(v1, v2)
        self.assertEqual(v1, v3)
        self.assertEqual(v1, v4)
        self.assertEqual(v1, v5)
        self.assertEqual(v1, v6)

    def test_direct_ref_namespace_quoting(self):
        v = self._cfg.getvar_s("re%x66:#%x23%u003a%x7c%U00000025%x2e.%x2e.%x3a.%x25.%x7c./.%x23")
        self.assertEqual("value", v)


if __name__ == "__main__":
    unittest.main()
