##// END OF EJS Templates
path: forbid chaining `path://` definitions...
path: forbid chaining `path://` definitions To have `path://xxx` referencing paths that use `path://` too, we need to analyze dependencies to initialize them in the right order (and to detect cycle). I don't want to deal with that right now, so I just disallow it for now. Differential Revision: https://phab.mercurial-scm.org/D10264

File last commit:

r47575:d4ba4d51 default
r47583:1ecf0823 default
Show More
config.py
350 lines | 11.4 KiB | text/x-python | PythonLexer
Martin Geisler
config: add copyright and license header
r8229 # config.py - configuration parsing for Mercurial
#
Raphaël Gomès
contributor: change mentions of mpm to olivia...
r47575 # Copyright 2009 Olivia Mackall <olivia@selenic.com> and others
Martin Geisler
config: add copyright and license header
r8229 #
# This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Martin Geisler
config: add copyright and license header
r8229
Gregory Szorc
config: use absolute_import
r25923 from __future__ import absolute_import
import errno
import os
from .i18n import _
Gregory Szorc
py3: manually import getattr where it is needed...
r43359 from .pycompat import getattr
Gregory Szorc
config: use absolute_import
r25923 from . import (
Denis Laxalde
py3: encode underlying error message during parse error of %include
r43570 encoding,
Gregory Szorc
config: use absolute_import
r25923 error,
Augie Fackler
config: guard against setconfig specifying unicode values on py3...
r31306 pycompat,
Gregory Szorc
config: use absolute_import
r25923 util,
)
Matt Mackall
ui: introduce new config parser
r8144
Augie Fackler
formatting: blacken the codebase...
r43346
Matt Mackall
config: add some helper methods
r8186 class config(object):
Martin von Zweigbergk
config: remove now-unused support for "includepaths"...
r45814 def __init__(self, data=None):
config: track the "level" of a value...
r47367 self._current_source_level = 0
Matt Mackall
ui: introduce new config parser
r8144 self._data = {}
FUJIWARA Katsunori
config: discard "%unset" values defined in the other files read in previously...
r19087 self._unset = []
Matt Mackall
ui: introduce new config parser
r8144 if data:
for k in data._data:
self._data[k] = data[k].copy()
config: track the "level" of a value...
r47367 self._current_source_level = data._current_source_level + 1
def new_source(self):
"""increment the source counter
This is used to define source priority when reading"""
self._current_source_level += 1
Augie Fackler
formatting: blacken the codebase...
r43346
Matt Mackall
ui: introduce new config parser
r8144 def copy(self):
return config(self)
Augie Fackler
formatting: blacken the codebase...
r43346
Matt Mackall
ui: introduce new config parser
r8144 def __contains__(self, section):
return section in self._data
Augie Fackler
formatting: blacken the codebase...
r43346
Bryan O'Sullivan
config: add hasconfig method and supporting plumbing...
r27696 def hasitem(self, section, item):
return item in self._data.get(section, {})
Augie Fackler
formatting: blacken the codebase...
r43346
Matt Mackall
config: add some helper methods
r8186 def __getitem__(self, section):
return self._data.get(section, {})
Augie Fackler
formatting: blacken the codebase...
r43346
Matt Mackall
config: add some helper methods
r8186 def __iter__(self):
for d in self.sections():
yield d
Augie Fackler
formatting: blacken the codebase...
r43346
Matt Mackall
config: add section filter to read...
r8193 def update(self, src):
config: track the "level" of a value...
r47367 current_level = self._current_source_level
current_level += 1
max_level = self._current_source_level
FUJIWARA Katsunori
config: discard "%unset" values defined in the other files read in previously...
r19087 for s, n in src._unset:
Jun Wu
config: use copy-on-write to improve copy performance...
r34353 ds = self._data.get(s, None)
if ds is not None and n in ds:
self._data[s] = ds.preparewrite()
FUJIWARA Katsunori
config: discard "%unset" values defined in the other files read in previously...
r19087 del self._data[s][n]
Matt Mackall
config: add section filter to read...
r8193 for s in src:
Jun Wu
config: use copy-on-write to improve copy performance...
r34353 ds = self._data.get(s, None)
if ds:
self._data[s] = ds.preparewrite()
else:
self._data[s] = util.cowsortdict()
config: track the "level" of a value...
r47367 for k, v in src._data[s].items():
value, source, level = v
level += current_level
max_level = max(level, current_level)
self._data[s][k] = (value, source, level)
self._current_source_level = max_level
config: track "source" along side value...
r47366
def _get(self, section, item):
return self._data.get(section, {}).get(item)
Augie Fackler
formatting: blacken the codebase...
r43346
Matt Mackall
ui: introduce new config parser
r8144 def get(self, section, item, default=None):
config: track "source" along side value...
r47366 result = self._get(section, item)
if result is None:
return default
return result[0]
Pierre-Yves David
config: have a way to backup and restore value in config...
r15919
config: track "source" along side value...
r47366 def backup(self, section, key):
timeless@mozdev.org
spelling: value
r17527 """return a tuple allowing restore to reinstall a previous value
Pierre-Yves David
config: have a way to backup and restore value in config...
r15919
timeless@mozdev.org
grammar: it-handles
r17530 The main reason we need it is because it handles the "no data" case.
Pierre-Yves David
config: have a way to backup and restore value in config...
r15919 """
try:
config: track "source" along side value...
r47366 item = self._data[section][key]
Pierre-Yves David
config: have a way to backup and restore value in config...
r15919 except KeyError:
config: track "source" along side value...
r47366 return (section, key)
else:
return (section, key) + item
Pierre-Yves David
config: have a way to backup and restore value in config...
r15919
Matt Mackall
config: getsource -> source
r8198 def source(self, section, item):
config: track "source" along side value...
r47366 result = self._get(section, item)
if result is None:
return b""
return result[1]
Augie Fackler
formatting: blacken the codebase...
r43346
config: track the "level" of a value...
r47367 def level(self, section, item):
result = self._get(section, item)
if result is None:
return None
return result[2]
Matt Mackall
ui: introduce new config parser
r8144 def sections(self):
return sorted(self._data.keys())
Augie Fackler
formatting: blacken the codebase...
r43346
Matt Mackall
ui: introduce new config parser
r8144 def items(self, section):
config: track "source" along side value...
r47366 items = pycompat.iteritems(self._data.get(section, {}))
config: track the "level" of a value...
r47367 return [(k, v[0]) for (k, v) in items]
Augie Fackler
formatting: blacken the codebase...
r43346
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 def set(self, section, item, value, source=b""):
Augie Fackler
config: guard against setconfig specifying unicode values on py3...
r31306 if pycompat.ispy3:
Augie Fackler
formatting: blacken the codebase...
r43346 assert not isinstance(
section, str
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ), b'config section may not be unicode strings on Python 3'
Augie Fackler
formatting: blacken the codebase...
r43346 assert not isinstance(
item, str
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ), b'config item may not be unicode strings on Python 3'
Augie Fackler
formatting: blacken the codebase...
r43346 assert not isinstance(
value, str
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 ), b'config values may not be unicode strings on Python 3'
Matt Mackall
ui: introduce new config parser
r8144 if section not in self:
Jun Wu
config: use copy-on-write to improve copy performance...
r34353 self._data[section] = util.cowsortdict()
else:
self._data[section] = self._data[section].preparewrite()
config: track the "level" of a value...
r47367 self._data[section][item] = (value, source, self._current_source_level)
Matt Mackall
ui: introduce new config parser
r8144
config: use a new `alter` method in `fixconfig`...
r47365 def alter(self, section, key, new_value):
"""alter a value without altering its source or level
This method is meant to be used by `ui.fixconfig` only."""
item = self._data[section][key]
size = len(item)
new_item = (new_value,) + item[1:]
assert len(new_item) == size
self._data[section][key] = new_item
Pierre-Yves David
config: have a way to backup and restore value in config...
r15919 def restore(self, data):
"""restore data returned by self.backup"""
config: track "source" along side value...
r47366 if len(data) != 2:
Pierre-Yves David
config: have a way to backup and restore value in config...
r15919 # restore old data
config: track "source" along side value...
r47366 section, key = data[:2]
item = data[2:]
Jun Wu
config: use copy-on-write to improve copy performance...
r34353 self._data[section] = self._data[section].preparewrite()
config: track "source" along side value...
r47366 self._data[section][key] = item
Pierre-Yves David
config: have a way to backup and restore value in config...
r15919 else:
# no data before, remove everything
section, item = data
if section in self._data:
Pierre-Yves David
config: fix restoreconfig of non existing config...
r22037 self._data[section].pop(item, None)
Pierre-Yves David
config: have a way to backup and restore value in config...
r15919
Matt Mackall
config: add parse interface
r8265 def parse(self, src, data, sections=None, remap=None, include=None):
Augie Fackler
config: mark parser regexes as bytes explicitly...
r30349 sectionre = util.re.compile(br'\[([^\[]+)\]')
itemre = util.re.compile(br'([^=\s][^=]*?)\s*=\s*(.*\S|)')
contre = util.re.compile(br'\s+(\S|\S.*\S)\s*$')
emptyre = util.re.compile(br'(;|#|\s*$)')
commentre = util.re.compile(br'(;|#)')
unsetre = util.re.compile(br'%unset\s+(\S+)')
includere = util.re.compile(br'%include\s+(\S|\S.*\S)\s*$')
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 section = b""
Matt Mackall
ui: introduce new config parser
r8144 item = None
line = 0
config: improve code readability
r9339 cont = False
Matt Mackall
hgweb: use config.config
r8180
Yuya Nishihara
config: allow remapping the default section...
r34714 if remap:
section = remap.get(section, section)
Nicolas Dumazet
for calls expecting bool args, pass bool instead of int...
r9136 for l in data.splitlines(True):
Matt Mackall
ui: introduce new config parser
r8144 line += 1
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if line == 1 and l.startswith(b'\xef\xbb\xbf'):
Matt Mackall
config: discard UTF-8 BOM if found
r16348 # Someone set us up the BOM
l = l[3:]
Matt Mackall
ui: introduce new config parser
r8144 if cont:
Matt Mackall
config: handle comment lines in continuations (issue2854)
r14642 if commentre.match(l):
continue
Matt Mackall
ui: introduce new config parser
r8144 m = contre.match(l)
if m:
Matt Mackall
config: add section filter to read...
r8193 if sections and section not in sections:
continue
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 v = self.get(section, item) + b"\n" + m.group(1)
self.set(section, item, v, b"%s:%d" % (src, line))
Matt Mackall
ui: introduce new config parser
r8144 continue
item = None
Nicolas Dumazet
config: abort on indented non-continuation lines (issue1829)...
r9469 cont = False
Matt Mackall
config: allow including other config files
r8183 m = includere.match(l)
Jordi Gutiérrez Hermoso
config: give it an includepaths option for looking for config files...
r25095
if m and include:
expanded = util.expandpath(m.group(1))
Martin von Zweigbergk
config: remove now-unused support for "includepaths"...
r45814 try:
Martin von Zweigbergk
config: remove now-unused `abs` argument from `include` callback...
r45817 include(expanded, remap=remap, sections=sections)
Martin von Zweigbergk
config: remove now-unused support for "includepaths"...
r45814 except IOError as inst:
if inst.errno != errno.ENOENT:
Martin von Zweigbergk
errors: raise ConfigError on failure to parse config file...
r46506 raise error.ConfigError(
Martin von Zweigbergk
config: remove now-unused support for "includepaths"...
r45814 _(b"cannot include %s (%s)")
Martin von Zweigbergk
config: remove now-unused `abs` argument from `include` callback...
r45817 % (expanded, encoding.strtolocal(inst.strerror)),
Martin von Zweigbergk
config: remove now-unused support for "includepaths"...
r45814 b"%s:%d" % (src, line),
)
Matt Mackall
config: allow including other config files
r8183 continue
Matt Mackall
ui: introduce new config parser
r8144 if emptyre.match(l):
continue
m = sectionre.match(l)
if m:
section = m.group(1)
Matt Mackall
config: make remap actually work
r8298 if remap:
section = remap.get(section, section)
Matt Mackall
ui: introduce new config parser
r8144 if section not in self:
Jun Wu
config: use copy-on-write to improve copy performance...
r34353 self._data[section] = util.cowsortdict()
Matt Mackall
ui: introduce new config parser
r8144 continue
m = itemre.match(l)
if m:
item = m.group(1)
config: improve code readability
r9339 cont = True
Matt Mackall
config: add section filter to read...
r8193 if sections and section not in sections:
continue
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 self.set(section, item, m.group(2), b"%s:%d" % (src, line))
Matt Mackall
ui: introduce new config parser
r8144 continue
Matt Mackall
config: add %unset name support
r8184 m = unsetre.match(l)
if m:
name = m.group(1)
Matt Mackall
config: add section filter to read...
r8193 if sections and section not in sections:
continue
Martin Geisler
code style: prefer 'is' and 'is not' tests with singletons
r13031 if self.get(section, name) is not None:
Jun Wu
config: add a missing preparewrite() call...
r34455 self._data[section] = self._data[section].preparewrite()
Matt Mackall
config: add %unset name support
r8184 del self._data[section][name]
FUJIWARA Katsunori
config: discard "%unset" values defined in the other files read in previously...
r19087 self._unset.append((section, name))
Matt Mackall
config: add %unset name support
r8184 continue
Martin von Zweigbergk
config: move message about leading spaces in config to config.py...
r46362 message = l.rstrip()
if l.startswith(b' '):
message = b"unexpected leading whitespace: %s" % message
Martin von Zweigbergk
errors: raise ConfigError on failure to parse config file...
r46506 raise error.ConfigError(message, (b"%s:%d" % (src, line)))
Matt Mackall
config: add parse interface
r8265
def read(self, path, fp=None, sections=None, remap=None):
config: track the "level" of a value...
r47367 self.new_source()
Matt Mackall
config: add parse interface
r8265 if not fp:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 fp = util.posixfile(path, b'rb')
Augie Fackler
formating: upgrade to black 20.8b1...
r46554 assert (
getattr(fp, 'mode', 'rb') == 'rb'
), b'config files must be opened in binary mode, got fp=%r mode=%r' % (
fp,
fp.mode,
Augie Fackler
formatting: blacken the codebase...
r43346 )
Martin von Zweigbergk
config: pass both relative and absolute paths to `include` callback...
r45768
Martin von Zweigbergk
config: re-calculate absolute %include path in `include` callback...
r45816 dir = os.path.dirname(path)
Martin von Zweigbergk
config: remove now-unused `abs` argument from `include` callback...
r45817 def include(rel, remap, sections):
Martin von Zweigbergk
config: re-calculate absolute %include path in `include` callback...
r45816 abs = os.path.normpath(os.path.join(dir, rel))
Martin von Zweigbergk
config: pass both relative and absolute paths to `include` callback...
r45768 self.read(abs, remap=remap, sections=sections)
config: track the "level" of a value...
r47367 # anything after the include has a higher level
self.new_source()
Martin von Zweigbergk
config: pass both relative and absolute paths to `include` callback...
r45768
Augie Fackler
formatting: blacken the codebase...
r43346 self.parse(
Martin von Zweigbergk
config: pass both relative and absolute paths to `include` callback...
r45768 path, fp.read(), sections=sections, remap=remap, include=include
Augie Fackler
formatting: blacken the codebase...
r43346 )
Jun Wu
ui: move configlist parser to config.py...
r31481
def parselist(value):
"""parse a configuration value as a list of comma/space separated strings
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> parselist(b'this,is "a small" ,test')
Jun Wu
ui: move configlist parser to config.py...
r31481 ['this', 'is', 'a small', 'test']
"""
def _parse_plain(parts, s, offset):
whitespace = False
Augie Fackler
formatting: blacken the codebase...
r43346 while offset < len(s) and (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 s[offset : offset + 1].isspace() or s[offset : offset + 1] == b','
Augie Fackler
formatting: blacken the codebase...
r43346 ):
Jun Wu
ui: move configlist parser to config.py...
r31481 whitespace = True
offset += 1
if offset >= len(s):
return None, parts, offset
if whitespace:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 parts.append(b'')
if s[offset : offset + 1] == b'"' and not parts[-1]:
Jun Wu
ui: move configlist parser to config.py...
r31481 return _parse_quote, parts, offset + 1
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 elif s[offset : offset + 1] == b'"' and parts[-1][-1:] == b'\\':
Augie Fackler
formatting: blacken the codebase...
r43346 parts[-1] = parts[-1][:-1] + s[offset : offset + 1]
Jun Wu
ui: move configlist parser to config.py...
r31481 return _parse_plain, parts, offset + 1
Augie Fackler
formatting: blacken the codebase...
r43346 parts[-1] += s[offset : offset + 1]
Jun Wu
ui: move configlist parser to config.py...
r31481 return _parse_plain, parts, offset + 1
def _parse_quote(parts, s, offset):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if offset < len(s) and s[offset : offset + 1] == b'"': # ""
parts.append(b'')
Jun Wu
ui: move configlist parser to config.py...
r31481 offset += 1
Augie Fackler
formatting: blacken the codebase...
r43346 while offset < len(s) and (
s[offset : offset + 1].isspace()
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 or s[offset : offset + 1] == b','
Augie Fackler
formatting: blacken the codebase...
r43346 ):
Jun Wu
ui: move configlist parser to config.py...
r31481 offset += 1
return _parse_plain, parts, offset
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 while offset < len(s) and s[offset : offset + 1] != b'"':
Augie Fackler
formatting: blacken the codebase...
r43346 if (
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 s[offset : offset + 1] == b'\\'
Augie Fackler
formatting: blacken the codebase...
r43346 and offset + 1 < len(s)
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 and s[offset + 1 : offset + 2] == b'"'
Augie Fackler
formatting: blacken the codebase...
r43346 ):
Jun Wu
ui: move configlist parser to config.py...
r31481 offset += 1
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 parts[-1] += b'"'
Jun Wu
ui: move configlist parser to config.py...
r31481 else:
Augie Fackler
formatting: blacken the codebase...
r43346 parts[-1] += s[offset : offset + 1]
Jun Wu
ui: move configlist parser to config.py...
r31481 offset += 1
if offset >= len(s):
real_parts = _configlist(parts[-1])
if not real_parts:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 parts[-1] = b'"'
Jun Wu
ui: move configlist parser to config.py...
r31481 else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 real_parts[0] = b'"' + real_parts[0]
Jun Wu
ui: move configlist parser to config.py...
r31481 parts = parts[:-1]
parts.extend(real_parts)
return None, parts, offset
offset += 1
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 while offset < len(s) and s[offset : offset + 1] in [b' ', b',']:
Jun Wu
ui: move configlist parser to config.py...
r31481 offset += 1
if offset < len(s):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 if offset + 1 == len(s) and s[offset : offset + 1] == b'"':
parts[-1] += b'"'
Jun Wu
ui: move configlist parser to config.py...
r31481 offset += 1
else:
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 parts.append(b'')
Jun Wu
ui: move configlist parser to config.py...
r31481 else:
return None, parts, offset
return _parse_plain, parts, offset
def _configlist(s):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 s = s.rstrip(b' ,')
Jun Wu
ui: move configlist parser to config.py...
r31481 if not s:
return []
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 parser, parts, offset = _parse_plain, [b''], 0
Jun Wu
ui: move configlist parser to config.py...
r31481 while parser:
parser, parts, offset = parser(parts, s, offset)
return parts
if value is not None and isinstance(value, bytes):
Augie Fackler
formatting: byteify all mercurial/ and hgext/ string literals...
r43347 result = _configlist(value.lstrip(b' ,\n'))
Jun Wu
ui: move configlist parser to config.py...
r31481 else:
result = value
return result or []