1from jailconf.exceptions import ConfSemanticError
2from jailconf.lexer import Lexer
3
4from collections import OrderedDict
5import os
6import re
7from tempfile import mkstemp
8
9class Append(object):
10    def __init__(self, param, value):
11        self.param = param
12        self.value = value
13
14class Conf(OrderedDict):
15
16    valid_param_rgx = re.compile('^%s$' % Lexer.t_NAME.__doc__)
17    valid_value_rgx = re.compile('^(%s)$' % '|'.join((
18        Lexer.t_NAME.__doc__,
19        Lexer.t_SINGLE_QUOTED_STRING.__doc__,
20        Lexer.t_DOUBLE_QUOTED_STRING.__doc__
21    )))
22
23    @classmethod
24    def validate_param(cls, param):
25        if cls.valid_param_rgx.match(param):
26            return param
27        else:
28            raise ValueError("Invalid parameter %s" % repr(param))
29
30    @classmethod
31    def validate_value(cls, value):
32        if cls.valid_value_rgx.match(value):
33            return value
34        else:
35            raise ValueError("Invalid value %s" % repr(value))
36
37    @classmethod
38    def validate_key_value(cls, key, value):
39        if isinstance(value, JailBlock):
40            cls.validate_value(key)
41        else:
42            cls.validate_param(key)
43            if isinstance(value, str):
44                cls.validate_value(value)
45            elif isinstance(value, list):
46                if len(value) < 2:
47                    raise ValueError("Lists of values should have at least 2 elements.")
48                for v in value:
49                    cls.validate_value(v)
50            elif value is not True:
51                raise ValueError(
52                    "Invalid type for value %s. Should be of type str, list, JailBlock, or should be the value True."
53                    % repr(value)
54                )
55        return key, value
56
57    def update(self, *args, **kwargs):
58        if len(args) > 1:
59            raise TypeError("Expected at most 1 argument, got 2")
60        if args:
61            if hasattr(args[0], 'keys'):
62                lst = [(k,args[0][k]) for k in args[0].keys()]
63            else:
64                lst = args[0]
65        else:
66            lst = []
67        if kwargs:
68            lst.extend(kwargs.items())
69        for item in lst:
70            if isinstance(item, Append):
71                if item.param in self:
72                    if isinstance(self[item.param], str):
73                        super(Conf, self).__setitem__(item.param, [self[item.param], item.value])
74                    elif isinstance(self[item.param], list):
75                        self[item.param].append(item.value)
76                    else:
77                        raise ConfSemanticError(
78                            "Trying to append to parameter %s but it has type %s" % (
79                                item.param, type(self[item.param])
80                            )
81                        )
82                else:
83                    raise ConfSemanticError(
84                        "Trying to append to undefined parameter %s" % item.param
85                    )
86            else:
87                key, value = item
88                self[key] = value
89
90
91    def __setitem__(self, k, v):
92        self.__class__.validate_key_value(k, v)
93        super(Conf, self).__setitem__(k, v)
94
95    def __init__(self, *args, **kwargs):
96        super(Conf, self).__init__()
97        self.update(*args, **kwargs)
98
99    def strgen(self, indentation = '\t', current_indent = 0):
100        for key, value in self.items():
101            yield current_indent * indentation
102            yield key
103            if isinstance(value, JailBlock):
104                yield ' {\n'
105                yield from value.strgen(indentation, current_indent + 1)
106                yield '}\n'
107            elif value is True:
108                yield ';\n'
109            elif isinstance(value, list):
110                yield ' = %s;\n' % (', '.join(value))
111            else:
112                yield ' = %s;\n' % value
113
114    def dumps(self, indentation = '\t', current_indent = 0):
115        return ''.join(self.strgen(indentation, current_indent))
116
117    def write(self, path, indentation = '\t'):
118        handle, temp_path = mkstemp(prefix = path)
119        os.write(handle, self.dumps(indentation = indentation).encode('utf-8'))
120        os.close(handle)
121        os.rename(temp_path, path)
122
123class JailConf(Conf):
124    def jails(self):
125        for key, value in self.items():
126            if isinstance(value, JailBlock):
127                yield (key, value)
128
129class JailBlock(Conf):
130    @classmethod
131    def validate_key_value(cls, k, v):
132        if isinstance(v, JailBlock):
133            raise ValueError("Jail block not allowed in this context.")
134        return super(JailBlock, cls).validate_key_value(k, v)
135