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)
|