summaryrefslogtreecommitdiff
path: root/talerbank/talerconfig.py
blob: 4a8d45ad18d7f49c5935c7e1ba0689439e5486d9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import logging
import collections
import os

logger = logging.getLogger(__name__)

__all__ = ["TalerConfig"]

taler_datadir = None
try:
    # not clear if this is a good idea ...
    from talerpaths import taler_datadir as t
    taler_datadir = t
except ImportError:
    pass


class OptionDict(collections.defaultdict):
    def __init__(self):
        super().__init__()
    def __missing__(self, key):
        raise KeyError(key)


class SectionDict(collections.defaultdict):
    def __init__(self):
        super().__init__()
    def __missing__(self, key):
        v = OptionDict()
        self[key] = v
        return v

class Entry:
    def __init__(self, section, option, value=None, filename=None, lineno=None):
        self.value = value
        self.filename = filename
        self.lineno = lineno
        self.section = section
        self.option = option

    def __repr__(self):
        return "<Entry section=%s, option=%s, value=%s>" % (self.section, self.option, repr(self.value),)

    def __str__(self):
        return self.value

    def value_string(self, default=None, warn=False):
        if self.value is None:
            if warn:
                if default is not None:
                    logger.warn("Configuration is missing option %s in section %s, falling back to '%s'", default)
                else:
                    logger.warn("Configuration is missing option %s in section %s", default)
            return default
        return self.value

    def location(self):
        if self.filename is None or self.lineno is None:
            return "<unknown>"
        return "%s:%s" % (self.filename, self.lineno)


class TalerConfig:
    """
    One loaded taler configuration, including base configuration
    files and included files.
    """
    def __init__(self):
        """
        Initialize an empty configuration
        """
        self.sections = SectionDict()

    @staticmethod
    def from_file(filename=None, load_defaults=True):
        cfg = TalerConfig()
        if filename is None:
            xdg = os.environ.get("XDG_CONFIG_HOME")
            if xdg:
                filename = os.path.join(xdg, "taler.conf")
            else:
                filename = os.path.expanduser("~/.config/taler.conf")
        if load_defaults:
            cfg.load_defaults()
        cfg.load_file(filename)
        return cfg

    def load_defaults(self):
        base_dir = os.environ.get("TALER_BASE_CONFIG")
        if base_dir:
            self.load_dir(base_dir)
            return
        prefix = os.environ.get("TALER_PREFIX")
        if prefix:
            self.load_dir(os.path.join(prefix, "share/config.d"))
            return
        if taler_datadir:
            self.load_dir(os.path.join(taler_datadir, "config.d"))
            return
        logger.warn("no base directory found")

    def load_dir(self, dirname):
        try:
            files = os.listdir(dirname)
        except FileNotFoundError:
            logger.warn("can't read config directory '%s'", dirname)
            return
        for file in files:
            if not file.endswith(".conf"):
                continue
            self.load_file(os.path.join(dirname, file))

    def load_file(self, filename):
        sections = self.sections
        with open(filename, "r") as file:
            lineno = 0
            current_section = None
            for line in file:
                lineno += 1
                line = line.strip()
                if len(line) == 0:
                    # empty line
                    continue
                if line.startswith("#"):
                    # comment
                    continue
                if line.startswith("["):
                    if not line.endswith("]"):
                        logger.error("invalid section header in line %s: %s", lineno, repr(line))
                    section_name = line.strip("[]").strip().strip('"')
                    current_section = section_name
                    continue
                if current_section is None:
                    logger.error("option outside of section in line %s: %s", lineno, repr(line))
                    continue
                kv = line.split("=", 2)
                if len(kv) != 2:
                    logger.error("invalid option in line %s: %s", lineno, repr(line))
                key = kv[0].strip()
                value = kv[1].strip()
                if value.startswith('"'):
                    value = value[1:]
                    if not value.endswith('"'):
                        logger.error("mismatched quotes in line %s: %s", lineno, repr(line))
                    else:
                        value = value[:-1]
                e = Entry(current_section, key, value, filename, lineno)
                sections[current_section][key] = e


    def dump(self):
        for section_name, section in self.sections.items():
            print("[%s]" % (section_name,))
            for option_name, option in section.items():
                print("%s = %s # %s" % (option_name, option, option.location()))

    def __getitem__(self, slice):
        if isinstance(slice, str):
            return self.sections[slice]
        raise TypeError("index must be string")


if __name__ == "__main__":
    import sys
    tc = TalerConfig.from_file(sys.argv[1])
    tc.dump()