Switch to unified view

a b/conftree.py
1
#!/usr/bin/env python
2
# Copyright (C) 2016 J.F.Dockes
3
#   This program is free software; you can redistribute it and/or modify
4
#   it under the terms of the GNU General Public License as published by
5
#   the Free Software Foundation; either version 2 of the License, or
6
#   (at your option) any later version.
7
#
8
#   This program is distributed in the hope that it will be useful,
9
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
#   GNU General Public License for more details.
12
#
13
#   You should have received a copy of the GNU General Public License
14
#   along with this program; if not, write to the
15
#   Free Software Foundation, Inc.,
16
#   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17
18
from __future__ import print_function
19
20
import locale
21
import re
22
import os
23
import sys
24
import base64
25
import platform
26
27
def _debug(s):
28
    print("%s"%s, file=sys.stderr)
29
    
30
class ConfSimple(object):
31
    """A ConfSimple class reads a recoll configuration file, which is
32
    a typical ini file (see the Recoll manual). It's a dictionary of
33
    dictionaries which lets you retrieve named values from the top
34
    level or a subsection"""
35
36
    def __init__(self, confname, tildexp = False, readonly = True):
37
        self.submaps = {}
38
        self.dotildexpand = tildexp
39
        self.readonly = readonly
40
        self.confname = confname
41
        
42
        try:
43
            f = open(confname, 'rb')
44
        except Exception as exc:
45
            #_debug("Open Exception: %s" % exc)
46
            # File does not exist -> empty config, not an error.
47
            self.submaps = {}
48
            self.submaps[b''] = {}
49
            return
50
51
        self._parseinput(f)
52
        
53
    def _parseinput(self, f):
54
        appending = False
55
        line = b''
56
        submapkey = b''
57
        for cline in f:
58
            cline = cline.rstrip(b'\r\n')
59
            if appending:
60
                line = line + cline
61
            else:
62
                line = cline
63
            line = line.strip()
64
            if line == b'' or line[0] == b'#'[0]:
65
                continue
66
67
            if line[len(line)-1] == b'\\'[0]:
68
                line = line[0:len(line)-1]
69
                appending = True
70
                continue
71
72
            appending = False
73
            #_debug(line)
74
            if line[0] == b'['[0]:
75
                line = line.strip(b'[]')
76
                if self.dotildexpand:
77
                    submapkey = os.path.expanduser(line)
78
                    if type(submapkey) == type(u''):
79
                        submapkey = submapkey.encode('utf-8')
80
                else:
81
                    submapkey = line
82
                #_debug("Submapkey: [%s]" % submapkey)
83
                continue
84
85
            nm, sep, value = line.partition(b'=')
86
            if sep == b'':
87
                # No equal sign in line -> considered comment
88
                continue
89
90
            nm = nm.strip()
91
            value = value.strip()
92
            #_debug("sk [%s] nm: [%s] value: [%s]" % (submapkey, nm, value))
93
            if not submapkey in self.submaps:
94
                self.submaps[submapkey] = {}
95
            self.submaps[submapkey][nm] = value
96
97
    def getbin(self, nm, sk = b''):
98
        '''Returns None if not found, empty string if found empty'''
99
        if type(nm) != type(b'') or type(sk) != type(b''):
100
            raise TypeError("getbin: parameters must be binary not unicode")
101
        #_debug("ConfSimple::getbin nm [%s] sk [%s]" % (nm, sk))
102
        if not sk in self.submaps:
103
            return None
104
        if not nm in self.submaps[sk]:
105
            return None
106
        return self.submaps[sk][nm]
107
108
    def get(self, nm, sk = b''):
109
        dodecode = False
110
        if type(nm) == type(u''):
111
            dodecode = True
112
            nm = nm.encode('utf-8')
113
        if type(sk) == type(u''):
114
            sk = sk.encode('utf-8')
115
        #v = ConfSimple.getbin(self, nm, sk)
116
        v = self.getbin(nm, sk)
117
        if v and dodecode:
118
            v = v.decode('utf-8')
119
        return v
120
121
    def getNamesbin(self, sk = b''):
122
        if not sk in self.submaps:
123
            return None
124
        return list(self.submaps[sk].keys())
125
126
    def getNames(self, sk = ''):
127
        if not sk in self.submaps:
128
            return None
129
        dodecode = False
130
        if type(sk) == type(u''):
131
            dodecode = True
132
            sk = sk.encode('utf-8')
133
        names = self.getNamesbin(sk)
134
        if names and dodecode:
135
            names = [nm.decode('utf-8') for nm in names]
136
        return names
137
138
    def _rewrite(self):
139
        if self.readonly:
140
            raise Exception("ConfSimple is readonly")
141
142
        tname = self.confname + "-"
143
        f = open(tname, 'wb')
144
        # First output null subkey submap
145
        if b'' in self.submaps:
146
            for nm,value in self.submaps[b''].items():
147
                f.write(nm + b'=' + value + b'\n')
148
        for sk,mp in self.submaps.items():
149
            if sk == b'':
150
                continue
151
            f.write(b'[' + sk + b']\n')
152
            for nm,value in mp.items():
153
                f.write(nm + b'=' + value + b'\n')
154
        f.close()
155
        os.rename(tname, self.confname)
156
157
    def setbin(self, nm, value, sk = b''):
158
        if self.readonly:
159
            raise Exception("ConfSimple is readonly")
160
        if sk not in self.submaps:
161
            self.submaps[sk] = {}
162
        self.submaps[sk][nm] = value
163
        self._rewrite()
164
        return True
165
166
    def set(self, nm, value, sk = b''):
167
        if self.readonly:
168
            raise Exception("ConfSimple is readonly")
169
        if type(nm) == type(u''):
170
            nm = nm.encode('utf-8')
171
        if type(value) == type(u''):
172
            value = value.encode('utf-8')
173
        if type(sk) == type(u''):
174
            sk = sk.encode('utf-8')
175
        return self.setbin(nm, value, sk)
176
    
177
    
178
class ConfTree(ConfSimple):
179
    """A ConfTree adds path-hierarchical interpretation of the section keys,
180
    which should be '/'-separated values. When a value is requested for a
181
    given path, it will also be searched in the sections corresponding to
182
    the ancestors. E.g. get(name, '/a/b') will also look in sections '/a' and
183
    '/' or '' (the last 2 are equivalent)"""
184
185
    def getbin(self, nm, sk = b''):
186
        if type(nm) != type(b'') or type(sk) != type(b''):
187
            raise TypeError("getbin: parameters must be binary not unicode")
188
        #_debug("ConfTree::getbin: nm [%s] sk [%s]" % (nm, sk))
189
        
190
        if sk == b'' or sk[0] != b'/'[0]:
191
            return ConfSimple.getbin(self, nm, sk)
192
193
        if sk[len(sk)-1] == b'/'[0]:
194
             sk = sk[:len(sk)-1]
195
196
        # Try all sk ancestors as submaps (/a/b/c-> /a/b/c, /a/b, /a, b'')
197
        while sk:
198
            if sk in self.submaps:
199
                return ConfSimple.getbin(self, nm, sk)
200
            if sk + b'/' in self.submaps:
201
                return ConfSimple.getbin(self, nm, sk+b'/')
202
            i = sk.rfind(b'/')
203
            if i == -1:
204
                break
205
            sk = sk[:i]
206
207
        return ConfSimple.getbin(self, nm)
208
209
210
class ConfStack(object):
211
    """ A ConfStack manages the superposition of a list of Configuration
212
    objects. Values are looked for in each object from the list until found.
213
    This typically provides for defaults overriden by sparse values in the
214
    topmost file."""
215
216
    def __init__(self, nm, dirs, tp = 'simple'):
217
        fnames = []
218
        for dir in dirs:
219
            fnm = os.path.join(dir, nm)
220
            fnames.append(fnm)
221
            self._construct(tp, fnames)
222
223
    def _construct(self, tp, fnames):
224
        self.confs = []
225
        for fname in fnames:
226
            if tp.lower() == 'simple':
227
                conf = ConfSimple(fname)
228
            else:
229
                conf = ConfTree(fname)
230
            self.confs.append(conf)
231
232
    # Accepts / returns binary strings (non-unicode)
233
    def getbin(self, nm, sk = b''):
234
        if type(nm) != type(b'') or type(sk) != type(b''):
235
           raise TypeError("getbin: parameters must be binary not unicode")
236
        for conf in self.confs:
237
            value = conf.getbin(nm, sk)
238
            if value is not None:
239
                return value
240
        return None
241
242
    def get(self, nm, sk = b''):
243
        dodecode = False
244
        if type(nm) == type(u''):
245
            dodecode = True
246
            nm = nm.encode('utf-8')
247
        if type(sk) == type(u''):
248
            sk = sk.encode('utf-8')
249
        #v = ConfSimple.getbin(self, nm, sk)
250
        v = self.getbin(nm, sk)
251
        if v and dodecode:
252
            v = v.decode('utf-8')
253
        return v