Statistics
| Revision:

gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / dulwich / config.py @ 959

History | View | Annotate | Download (13.8 KB)

1
# config.py - Reading and writing Git config files
2
# Copyright (C) 2011-2013 Jelmer Vernooij <jelmer@samba.org>
3
#
4
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
5
# General Public License as public by the Free Software Foundation; version 2.0
6
# or (at your option) any later version. You can redistribute it and/or
7
# modify it under the terms of either of these two licenses.
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14
#
15
# You should have received a copy of the licenses; if not, see
16
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
17
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
18
# License, Version 2.0.
19
#
20

    
21
"""Reading and writing Git configuration files.
22

23
TODO:
24
 * preserve formatting when updating configuration files
25
 * treat subsection names as case-insensitive for [branch.foo] style
26
   subsections
27
"""
28

    
29
import errno
30
import os
31

    
32
from collections import (
33
    OrderedDict,
34
    MutableMapping,
35
    )
36

    
37

    
38
from dulwich.file import GitFile
39

    
40

    
41
class Config(object):
42
    """A Git configuration."""
43

    
44
    def get(self, section, name):
45
        """Retrieve the contents of a configuration setting.
46

47
        :param section: Tuple with section name and optional subsection namee
48
        :param subsection: Subsection name
49
        :return: Contents of the setting
50
        :raise KeyError: if the value is not set
51
        """
52
        raise NotImplementedError(self.get)
53

    
54
    def get_boolean(self, section, name, default=None):
55
        """Retrieve a configuration setting as boolean.
56

57
        :param section: Tuple with section name and optional subsection namee
58
        :param name: Name of the setting, including section and possible
59
            subsection.
60
        :return: Contents of the setting
61
        :raise KeyError: if the value is not set
62
        """
63
        try:
64
            value = self.get(section, name)
65
        except KeyError:
66
            return default
67
        if value.lower() == b"true":
68
            return True
69
        elif value.lower() == b"false":
70
            return False
71
        raise ValueError("not a valid boolean string: %r" % value)
72

    
73
    def set(self, section, name, value):
74
        """Set a configuration value.
75

76
        :param section: Tuple with section name and optional subsection namee
77
        :param name: Name of the configuration value, including section
78
            and optional subsection
79
        :param: Value of the setting
80
        """
81
        raise NotImplementedError(self.set)
82

    
83
    def iteritems(self, section):
84
        """Iterate over the configuration pairs for a specific section.
85

86
        :param section: Tuple with section name and optional subsection namee
87
        :return: Iterator over (name, value) pairs
88
        """
89
        raise NotImplementedError(self.iteritems)
90

    
91
    def itersections(self):
92
        """Iterate over the sections.
93

94
        :return: Iterator over section tuples
95
        """
96
        raise NotImplementedError(self.itersections)
97

    
98

    
99
class ConfigDict(Config, MutableMapping):
100
    """Git configuration stored in a dictionary."""
101

    
102
    def __init__(self, values=None):
103
        """Create a new ConfigDict."""
104
        if values is None:
105
            values = OrderedDict()
106
        self._values = values
107

    
108
    def __repr__(self):
109
        return "%s(%r)" % (self.__class__.__name__, self._values)
110

    
111
    def __eq__(self, other):
112
        return (
113
            isinstance(other, self.__class__) and
114
            other._values == self._values)
115

    
116
    def __getitem__(self, key):
117
        return self._values.__getitem__(key)
118

    
119
    def __setitem__(self, key, value):
120
        return self._values.__setitem__(key, value)
121

    
122
    def __delitem__(self, key):
123
        return self._values.__delitem__(key)
124

    
125
    def __iter__(self):
126
        return self._values.__iter__()
127

    
128
    def __len__(self):
129
        return self._values.__len__()
130

    
131
    @classmethod
132
    def _parse_setting(cls, name):
133
        parts = name.split(".")
134
        if len(parts) == 3:
135
            return (parts[0], parts[1], parts[2])
136
        else:
137
            return (parts[0], None, parts[1])
138

    
139
    def get(self, section, name):
140
        if not isinstance(section, tuple):
141
            section = (section, )
142
        if len(section) > 1:
143
            try:
144
                return self._values[section][name]
145
            except KeyError:
146
                pass
147
        return self._values[(section[0],)][name]
148

    
149
    def set(self, section, name, value):
150
        if not isinstance(section, tuple):
151
            section = (section, )
152
        if not isinstance(name, bytes):
153
            raise TypeError(name)
154
        if type(value) not in (bool, bytes):
155
            raise TypeError(value)
156
        self._values.setdefault(section, OrderedDict())[name] = value
157

    
158
    def iteritems(self, section):
159
        return self._values.get(section, OrderedDict()).items()
160

    
161
    def itersections(self):
162
        return self._values.keys()
163

    
164

    
165
def _format_string(value):
166
    if (value.startswith(b" ") or
167
        value.startswith(b"\t") or
168
        value.endswith(b" ") or
169
        value.endswith(b"\t")):
170
        return b'"' + _escape_value(value) + b'"'
171
    return _escape_value(value)
172

    
173

    
174
_ESCAPE_TABLE = {
175
    ord(b"\\"): ord(b"\\"),
176
    ord(b"\""): ord(b"\""),
177
    ord(b"n"): ord(b"\n"),
178
    ord(b"t"): ord(b"\t"),
179
    ord(b"b"): ord(b"\b"),
180
    }
181
_COMMENT_CHARS = [ord(b"#"), ord(b";")]
182
_WHITESPACE_CHARS = [ord(b"\t"), ord(b" ")]
183

    
184
def _parse_string(value):
185
    value = bytearray(value.strip())
186
    ret = bytearray()
187
    whitespace = bytearray()
188
    in_quotes = False
189
    i = 0
190
    while i < len(value):
191
        c = value[i]
192
        if c == ord(b"\\"):
193
            i += 1
194
            try:
195
                v = _ESCAPE_TABLE[value[i]]
196
            except IndexError:
197
                raise ValueError(
198
                    "escape character in %r at %d before end of string" %
199
                    (value, i))
200
            except KeyError:
201
                raise ValueError(
202
                    "escape character followed by unknown character %s at %d in %r" %
203
                    (value[i], i, value))
204
            if whitespace:
205
                ret.extend(whitespace)
206
                whitespace = bytearray()
207
            ret.append(v)
208
        elif c == ord(b"\""):
209
            in_quotes = (not in_quotes)
210
        elif c in _COMMENT_CHARS and not in_quotes:
211
            # the rest of the line is a comment
212
            break
213
        elif c in _WHITESPACE_CHARS:
214
            whitespace.append(c)
215
        else:
216
            if whitespace:
217
                ret.extend(whitespace)
218
                whitespace = bytearray()
219
            ret.append(c)
220
        i += 1
221

    
222
    if in_quotes:
223
        raise ValueError("missing end quote")
224

    
225
    return bytes(ret)
226

    
227

    
228
def _escape_value(value):
229
    """Escape a value."""
230
    return value.replace(b"\\", b"\\\\").replace(b"\n", b"\\n").replace(b"\t", b"\\t").replace(b"\"", b"\\\"")
231

    
232

    
233
def _check_variable_name(name):
234
    for i in range(len(name)):
235
        c = name[i:i+1]
236
        if not c.isalnum() and c != b'-':
237
            return False
238
    return True
239

    
240

    
241
def _check_section_name(name):
242
    for i in range(len(name)):
243
        c = name[i:i+1]
244
        if not c.isalnum() and c not in (b'-', b'.'):
245
            return False
246
    return True
247

    
248

    
249
def _strip_comments(line):
250
    line = line.split(b"#")[0]
251
    line = line.split(b";")[0]
252
    return line
253

    
254

    
255
class ConfigFile(ConfigDict):
256
    """A Git configuration file, like .git/config or ~/.gitconfig.
257
    """
258

    
259
    @classmethod
260
    def from_file(cls, f):
261
        """Read configuration from a file-like object."""
262
        ret = cls()
263
        section = None
264
        setting = None
265
        for lineno, line in enumerate(f.readlines()):
266
            line = line.lstrip()
267
            if setting is None:
268
                # Parse section header ("[bla]")
269
                if len(line) > 0 and line[:1] == b"[":
270
                    line = _strip_comments(line).rstrip()
271
                    last = line.index(b"]")
272
                    if last == -1:
273
                        raise ValueError("expected trailing ]")
274
                    pts = line[1:last].split(b" ", 1)
275
                    line = line[last+1:]
276
                    pts[0] = pts[0].lower()
277
                    if len(pts) == 2:
278
                        if pts[1][:1] != b"\"" or pts[1][-1:] != b"\"":
279
                            raise ValueError(
280
                                "Invalid subsection %r" % pts[1])
281
                        else:
282
                            pts[1] = pts[1][1:-1]
283
                        if not _check_section_name(pts[0]):
284
                            raise ValueError("invalid section name %r" %
285
                                             pts[0])
286
                        section = (pts[0], pts[1])
287
                    else:
288
                        if not _check_section_name(pts[0]):
289
                            raise ValueError("invalid section name %r" %
290
                                    pts[0])
291
                        pts = pts[0].split(b".", 1)
292
                        if len(pts) == 2:
293
                            section = (pts[0], pts[1])
294
                        else:
295
                            section = (pts[0], )
296
                    ret._values[section] = OrderedDict()
297
                if _strip_comments(line).strip() == b"":
298
                    continue
299
                if section is None:
300
                    raise ValueError("setting %r without section" % line)
301
                try:
302
                    setting, value = line.split(b"=", 1)
303
                except ValueError:
304
                    setting = line
305
                    value = b"true"
306
                setting = setting.strip().lower()
307
                if not _check_variable_name(setting):
308
                    raise ValueError("invalid variable name %s" % setting)
309
                if value.endswith(b"\\\n"):
310
                    value = value[:-2]
311
                    continuation = True
312
                else:
313
                    continuation = False
314
                value = _parse_string(value)
315
                ret._values[section][setting] = value
316
                if not continuation:
317
                    setting = None
318
            else:  # continuation line
319
                if line.endswith(b"\\\n"):
320
                    line = line[:-2]
321
                    continuation = True
322
                else:
323
                    continuation = False
324
                value = _parse_string(line)
325
                ret._values[section][setting] += value
326
                if not continuation:
327
                    setting = None
328
        return ret
329

    
330
    @classmethod
331
    def from_path(cls, path):
332
        """Read configuration from a file on disk."""
333
        with GitFile(path, 'rb') as f:
334
            ret = cls.from_file(f)
335
            ret.path = path
336
            return ret
337

    
338
    def write_to_path(self, path=None):
339
        """Write configuration to a file on disk."""
340
        if path is None:
341
            path = self.path
342
        with GitFile(path, 'wb') as f:
343
            self.write_to_file(f)
344

    
345
    def write_to_file(self, f):
346
        """Write configuration to a file-like object."""
347
        for section, values in self._values.items():
348
            try:
349
                section_name, subsection_name = section
350
            except ValueError:
351
                (section_name, ) = section
352
                subsection_name = None
353
            if subsection_name is None:
354
                f.write(b"[" + section_name + b"]\n")
355
            else:
356
                f.write(b"[" + section_name + b" \"" + subsection_name + b"\"]\n")
357
            for key, value in values.items():
358
                if value is True:
359
                    value = b"true"
360
                elif value is False:
361
                    value = b"false"
362
                else:
363
                    value = _escape_value(value)
364
                f.write(b"\t" + key + b" = " + value + b"\n")
365

    
366

    
367
class StackedConfig(Config):
368
    """Configuration which reads from multiple config files.."""
369

    
370
    def __init__(self, backends, writable=None):
371
        self.backends = backends
372
        self.writable = writable
373

    
374
    def __repr__(self):
375
        return "<%s for %r>" % (self.__class__.__name__, self.backends)
376

    
377
    @classmethod
378
    def default_backends(cls):
379
        """Retrieve the default configuration.
380

381
        See git-config(1) for details on the files searched.
382
        """
383
        paths = []
384
        paths.append(os.path.expanduser("~/.gitconfig"))
385

    
386
        xdg_config_home = os.environ.get(
387
            "XDG_CONFIG_HOME", os.path.expanduser("~/.config/"),
388
        )
389
        paths.append(os.path.join(xdg_config_home, "git", "config"))
390

    
391
        if "GIT_CONFIG_NOSYSTEM" not in os.environ:
392
            paths.append("/etc/gitconfig")
393

    
394
        backends = []
395
        for path in paths:
396
            try:
397
                cf = ConfigFile.from_path(path)
398
            except (IOError, OSError) as e:
399
                if e.errno != errno.ENOENT:
400
                    raise
401
                else:
402
                    continue
403
            backends.append(cf)
404
        return backends
405

    
406
    def get(self, section, name):
407
        for backend in self.backends:
408
            try:
409
                return backend.get(section, name)
410
            except KeyError:
411
                pass
412
        raise KeyError(name)
413

    
414
    def set(self, section, name, value):
415
        if self.writable is None:
416
            raise NotImplementedError(self.set)
417
        return self.writable.set(section, name, value)
418

    
419

    
420
def parse_submodules(config):
421
    """Parse a gitmodules GitConfig file, returning submodules.
422

423
   :param config: A `ConfigFile`
424
   :return: list of tuples (submodule path, url, name),
425
       where name is quoted part of the section's name.
426
    """
427
    for section in config.keys():
428
        section_kind, section_name = section
429
        if section_kind == b'submodule':
430
            sm_path = config.get(section, b'path')
431
            sm_url = config.get(section, b'url')
432
            yield (sm_path, sm_url, section_name)