--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/config.py Thu Apr 23 15:40:10 2009 -0500
@@ -0,0 +1,93 @@
+from i18n import _
+import re, error
+
+class sortdict(dict):
+ 'a simple append-only sorted dictionary'
+ def __init__(self, data=None):
+ self._list = []
+ if data:
+ if hasattr(data, '_list'):
+ self._list = list(data._list)
+ self.update(data)
+ def copy(self):
+ return sortdict(self)
+ def __setitem__(self, key, val):
+ if key in self:
+ self._list.remove(key)
+ self._list.append(key)
+ dict.__setitem__(self, key, val)
+ def __iter__(self):
+ return self._list.__iter__()
+ def update(self, src):
+ for k in src:
+ self[k] = src[k]
+ def items(self):
+ return [(k,self[k]) for k in self._list]
+
+class config:
+ def __init__(self, data=None):
+ self._data = {}
+ if data:
+ for k in data._data:
+ self._data[k] = data[k].copy()
+ def copy(self):
+ return config(self)
+ def __contains__(self, section):
+ return section in self._data
+ def update(self, src, sections=None):
+ if not sections:
+ sections = src.sections()
+ for s in sections:
+ if s not in src:
+ continue
+ if s not in self:
+ self._data[s] = sortdict()
+ for k in src._data[s]:
+ self._data[s][k] = src._data[s][k]
+ def get(self, section, item, default=None):
+ return self._data.get(section, {}).get(item, (default, ""))[0]
+ def getsource(self, section, item):
+ return self._data.get(section, {}).get(item, (None, ""))[1]
+ def sections(self):
+ return sorted(self._data.keys())
+ def items(self, section):
+ return [(k, v[0]) for k,v in self._data.get(section, {}).items()]
+ def set(self, section, item, value, source=""):
+ if section not in self:
+ self._data[section] = sortdict()
+ self._data[section][item] = (value, source)
+
+ def read(self, path, fp):
+ sectionre = re.compile(r'\[([^\[]+)\]')
+ itemre = re.compile(r'([^=\s]+)\s*=\s*(.*)')
+ contre = re.compile(r'\s+(\S.*)')
+ emptyre = re.compile(r'(;|#|\s*$)')
+ section = ""
+ item = None
+ line = 0
+ cont = 0
+ for l in fp:
+ line += 1
+ if cont:
+ m = contre.match(l)
+ if m:
+ v = self.get(section, item) + "\n" + m.group(1)
+ self.set(section, item, v, "%s:%d" % (path, line))
+ continue
+ item = None
+ if emptyre.match(l):
+ continue
+ m = sectionre.match(l)
+ if m:
+ section = m.group(1)
+ if section not in self:
+ self._data[section] = sortdict()
+ continue
+ m = itemre.match(l)
+ if m:
+ item = m.group(1)
+ self.set(section, item, m.group(2), "%s:%d" % (path, line))
+ cont = 1
+ continue
+ raise error.ConfigError(_('config error at %s:%d: \'%s\'')
+ % (path, line, l.rstrip()))
--- a/mercurial/dispatch.py Thu Apr 23 15:40:10 2009 -0500
+++ b/mercurial/dispatch.py Thu Apr 23 15:40:10 2009 -0500
@@ -55,6 +55,8 @@
except error.AmbiguousCommand, inst:
ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
(inst.args[0], " ".join(inst.args[1])))
+ except error.ConfigError, inst:
+ ui.warn(_("hg: %s\n") % inst.args[0])
except error.LockHeld, inst:
if inst.errno == errno.ETIMEDOUT:
reason = _('timed out waiting for lock held by %s') % inst.locker
--- a/mercurial/error.py Thu Apr 23 15:40:10 2009 -0500
+++ b/mercurial/error.py Thu Apr 23 15:40:10 2009 -0500
@@ -28,6 +28,9 @@
class ParseError(Exception):
"""Exception raised on errors in parsing the command line."""
+class ConfigError(Exception):
+ 'Exception raised when parsing config files'
+
class RepoError(Exception):
pass
--- a/mercurial/ui.py Thu Apr 23 15:40:10 2009 -0500
+++ b/mercurial/ui.py Thu Apr 23 15:40:10 2009 -0500
@@ -7,27 +7,19 @@
from i18n import _
import errno, getpass, os, re, socket, sys, tempfile
-import ConfigParser, traceback, util
+import config, traceback, util, error
-def updateconfig(source, dest, sections=None):
- if not sections:
- sections = source.sections()
- for section in sections:
- if not dest.has_section(section):
- dest.add_section(section)
- if not source.has_section(section):
- continue
- for name, value in source.items(section, raw=True):
- dest.set(section, name, value)
+_booleans = {'1':True, 'yes':True, 'true':True, 'on':True,
+ '0':False, 'no':False, 'false':False, 'off':False}
class ui(object):
def __init__(self, parentui=None):
self.buffers = []
self.quiet = self.verbose = self.debugflag = self.traceback = False
self.interactive = self.report_untrusted = True
- self.overlay = util.configparser()
- self.cdata = util.configparser()
- self.ucdata = util.configparser()
+ self.overlay = config.config()
+ self.cdata = config.config()
+ self.ucdata = config.config()
self.parentui = None
self.trusted_users = {}
self.trusted_groups = {}
@@ -35,10 +27,10 @@
if parentui:
# parentui may point to an ui object which is already a child
self.parentui = parentui.parentui or parentui
- updateconfig(self.parentui.cdata, self.cdata)
- updateconfig(self.parentui.ucdata, self.ucdata)
+ self.cdata.update(self.parentui.cdata)
+ self.ucdata.update(self.parentui.ucdata)
# we want the overlay from the parent, not the root
- updateconfig(parentui.overlay, self.overlay)
+ self.overlay.update(parentui.overlay)
self.buffers = parentui.buffers
self.trusted_users = parentui.trusted_users.copy()
self.trusted_groups = parentui.trusted_groups.copy()
@@ -89,22 +81,21 @@
return
raise
- cdata = util.configparser()
+ cdata = config.config()
trusted = sections or assumetrusted or self._is_trusted(fp, filename)
try:
- cdata.readfp(fp, filename)
- except ConfigParser.ParsingError, inst:
- msg = _("Failed to parse %s\n%s") % (filename, inst)
+ cdata.read(filename, fp)
+ except error.ConfigError, inst:
if trusted:
- raise util.Abort(msg)
- self.warn(_("Ignored: %s\n") % msg)
+ raise
+ self.warn(_("Ignored: %s\n") % str(inst))
if trusted:
- updateconfig(cdata, self.cdata, sections)
- updateconfig(self.overlay, self.cdata, sections)
- updateconfig(cdata, self.ucdata, sections)
- updateconfig(self.overlay, self.ucdata, sections)
+ self.cdata.update(cdata, sections)
+ self.cdata.update(self.overlay, sections)
+ self.ucdata.update(cdata, sections)
+ self.ucdata.update(self.overlay, sections)
if root is None:
root = os.path.expanduser('~')
@@ -117,7 +108,7 @@
root = os.getcwd()
items = section and [(name, value)] or []
for cdata in self.cdata, self.ucdata, self.overlay:
- if not items and cdata.has_section('paths'):
+ if not items and 'paths' in cdata:
pathsitems = cdata.items('paths')
else:
pathsitems = items
@@ -149,8 +140,6 @@
def setconfig(self, section, name, value):
for cdata in (self.overlay, self.cdata, self.ucdata):
- if not cdata.has_section(section):
- cdata.add_section(section)
cdata.set(section, name, value)
self.fixconfig(section, name, value)
@@ -159,37 +148,23 @@
return self.ucdata
return self.cdata
- def _config(self, section, name, default, funcname, untrusted, abort):
- cdata = self._get_cdata(untrusted)
- if cdata.has_option(section, name):
- try:
- func = getattr(cdata, funcname)
- return func(section, name)
- except (ConfigParser.InterpolationError, ValueError), inst:
- msg = _("Error in configuration section [%s] "
- "parameter '%s':\n%s") % (section, name, inst)
- if abort:
- raise util.Abort(msg)
- self.warn(_("Ignored: %s\n") % msg)
- return default
-
- def _configcommon(self, section, name, default, funcname, untrusted):
- value = self._config(section, name, default, funcname,
- untrusted, abort=True)
+ def config(self, section, name, default=None, untrusted=False):
+ value = self._get_cdata(untrusted).get(section, name, default)
if self.debugflag and not untrusted:
- uvalue = self._config(section, name, None, funcname,
- untrusted=True, abort=False)
+ uvalue = self.ucdata.get(section, name)
if uvalue is not None and uvalue != value:
self.warn(_("Ignoring untrusted configuration option "
"%s.%s = %s\n") % (section, name, uvalue))
return value
- def config(self, section, name, default=None, untrusted=False):
- return self._configcommon(section, name, default, 'get', untrusted)
-
def configbool(self, section, name, default=False, untrusted=False):
- return self._configcommon(section, name, default, 'getboolean',
- untrusted)
+ v = self.config(section, name, None, untrusted)
+ if v == None:
+ return default
+ if v.lower() not in _booleans:
+ raise error.ConfigError(_("%s.%s not a boolean ('%s')")
+ % (section, name, v))
+ return _booleans[v.lower()]
def configlist(self, section, name, default=None, untrusted=False):
"""Return a list of comma/space separated strings"""
@@ -202,38 +177,20 @@
def has_section(self, section, untrusted=False):
'''tell whether section exists in config.'''
- cdata = self._get_cdata(untrusted)
- return cdata.has_section(section)
-
- def _configitems(self, section, untrusted, abort):
- items = {}
- cdata = self._get_cdata(untrusted)
- if cdata.has_section(section):
- try:
- items.update(dict(cdata.items(section)))
- except ConfigParser.InterpolationError, inst:
- msg = _("Error in configuration section [%s]:\n"
- "%s") % (section, inst)
- if abort:
- raise util.Abort(msg)
- self.warn(_("Ignored: %s\n") % msg)
- return items
+ return section in self._get_cdata(untrusted)
def configitems(self, section, untrusted=False):
- items = self._configitems(section, untrusted=untrusted, abort=True)
+ items = self._get_cdata(untrusted).items(section)
if self.debugflag and not untrusted:
- uitems = self._configitems(section, untrusted=True, abort=False)
- for k in util.sort(uitems):
- if uitems[k] != items.get(k):
+ for k,v in self.ucdata.items(section):
+ if self.cdata.get(section, k) != v:
self.warn(_("Ignoring untrusted configuration option "
- "%s.%s = %s\n") % (section, k, uitems[k]))
- return util.sort(items.items())
+ "%s.%s = %s\n") % (section, k, v))
+ return items
def walkconfig(self, untrusted=False):
cdata = self._get_cdata(untrusted)
- sections = cdata.sections()
- sections.sort()
- for section in sections:
+ for section in cdata.sections():
for name, value in self.configitems(section, untrusted):
yield section, name, str(value).replace('\n', '\\n')
--- a/tests/test-hgrc.out Thu Apr 23 15:40:10 2009 -0500
+++ b/tests/test-hgrc.out Thu Apr 23 15:40:10 2009 -0500
@@ -1,16 +1,13 @@
-abort: Failed to parse .../t/.hg/hgrc
-File contains no section headers.
-file: .../t/.hg/hgrc, line: 1
-'invalid\n'
+hg: config error at .../t/.hg/hgrc:1: 'invalid'
updating working directory
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
[paths]
default = .../foo%%bar
-default = .../foo%bar
+default = .../foo%%bar
bundle.mainreporoot=.../foobar
defaults.backout=-d "0 0"
defaults.commit=-d "0 0"
defaults.debugrawcommit=-d "0 0"
defaults.tag=-d "0 0"
-paths.default=.../foo%bar
+paths.default=.../foo%%bar
ui.slash=True
--- a/tests/test-trusted.py Thu Apr 23 15:40:10 2009 -0500
+++ b/tests/test-trusted.py Thu Apr 23 15:40:10 2009 -0500
@@ -3,7 +3,7 @@
# monkey-patching some functions in the util module
import os
-from mercurial import ui, util
+from mercurial import ui, util, error
hgrc = os.environ['HGRCPATH']
f = open(hgrc)
@@ -85,7 +85,6 @@
f = open('.hg/hgrc', 'w')
f.write('[paths]\n')
f.write('local = /another/path\n\n')
-f.write('interpolated = %(global)s%(local)s\n\n')
f.close()
#print '# Everything is run by user foo, group bar\n'
@@ -154,10 +153,8 @@
u2.readconfig('.hg/hgrc')
print 'trusted:'
print u2.config('foobar', 'baz')
-print u2.config('paths', 'interpolated')
print 'untrusted:'
print u2.config('foobar', 'baz', untrusted=True)
-print u2.config('paths', 'interpolated', untrusted=True)
print
print "# error handling"
@@ -179,33 +176,15 @@
print
print "# parse error"
f = open('.hg/hgrc', 'w')
-f.write('foo = bar')
-f.close()
-testui(user='abc', group='def', silent=True)
-assertraises(lambda: testui(debug=True, silent=True))
-
-print
-print "# interpolation error"
-f = open('.hg/hgrc', 'w')
-f.write('[foo]\n')
-f.write('bar = %(')
+f.write('foo')
f.close()
-u = testui(debug=True, silent=True)
-print '# regular config:'
-print ' trusted',
-assertraises(lambda: u.config('foo', 'bar'))
-print 'untrusted',
-assertraises(lambda: u.config('foo', 'bar', untrusted=True))
-u = testui(user='abc', group='def', debug=True, silent=True)
-print ' trusted ',
-print u.config('foo', 'bar')
-print 'untrusted',
-assertraises(lambda: u.config('foo', 'bar', untrusted=True))
+try:
+ testui(user='abc', group='def', silent=True)
+except error.ConfigError, inst:
+ print inst
-print '# configitems:'
-print ' trusted ',
-print u.configitems('foo')
-print 'untrusted',
-assertraises(lambda: u.configitems('foo', untrusted=True))
-
+try:
+ testui(debug=True, silent=True)
+except error.ConfigError, inst:
+ print inst
--- a/tests/test-trusted.py.out Thu Apr 23 15:40:10 2009 -0500
+++ b/tests/test-trusted.py.out Thu Apr 23 15:40:10 2009 -0500
@@ -1,21 +1,17 @@
# same user, same group
trusted
global = /some/path
- interpolated = /some/path/another/path
local = /another/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# same user, different group
trusted
global = /some/path
- interpolated = /some/path/another/path
local = /another/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# different user, same group
@@ -24,17 +20,14 @@
global = /some/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# different user, same group, but we trust the group
trusted
global = /some/path
- interpolated = /some/path/another/path
local = /another/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# different user, different group
@@ -43,70 +36,57 @@
global = /some/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# different user, different group, but we trust the user
trusted
global = /some/path
- interpolated = /some/path/another/path
local = /another/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# different user, different group, but we trust the group
trusted
global = /some/path
- interpolated = /some/path/another/path
local = /another/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# different user, different group, but we trust the user and the group
trusted
global = /some/path
- interpolated = /some/path/another/path
local = /another/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# we trust all users
# different user, different group
trusted
global = /some/path
- interpolated = /some/path/another/path
local = /another/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# we trust all groups
# different user, different group
trusted
global = /some/path
- interpolated = /some/path/another/path
local = /another/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# we trust all users and groups
# different user, different group
trusted
global = /some/path
- interpolated = /some/path/another/path
local = /another/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# we don't get confused by users and groups with the same name
@@ -116,29 +96,24 @@
global = /some/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# list of user names
# different user, different group, but we trust the user
trusted
global = /some/path
- interpolated = /some/path/another/path
local = /another/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# list of group names
# different user, different group, but we trust the group
trusted
global = /some/path
- interpolated = /some/path/another/path
local = /another/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# Can't figure out the name of the user running this process
@@ -148,20 +123,16 @@
global = /some/path
untrusted
. . global = /some/path
-. . interpolated = /some/path/another/path
. . local = /another/path
# prints debug warnings
# different user, different group
Not trusting file .hg/hgrc from untrusted user abc, group def
trusted
-Ignoring untrusted configuration option paths.interpolated = /some/path/another/path
Ignoring untrusted configuration option paths.local = /another/path
global = /some/path
untrusted
. . global = /some/path
-.Ignoring untrusted configuration option paths.interpolated = /some/path/another/path
- . interpolated = /some/path/another/path
.Ignoring untrusted configuration option paths.local = /another/path
. local = /another/path
@@ -173,10 +144,8 @@
trusted:
Ignoring untrusted configuration option foobar.baz = quux
None
-/some/path/another/path
untrusted:
quux
-/some/path/another/path
# error handling
# file doesn't exist
@@ -186,26 +155,6 @@
# parse error
# different user, different group
Not trusting file .hg/hgrc from untrusted user abc, group def
-Ignored: Failed to parse .hg/hgrc
-File contains no section headers.
-file: .hg/hgrc, line: 1
-'foo = bar'
-# same user, same group
-raised Abort
-
-# interpolation error
+Ignored: config error at .hg/hgrc:1: 'foo'
# same user, same group
-# regular config:
- trusted raised Abort
-untrusted raised Abort
-# different user, different group
-Not trusting file .hg/hgrc from untrusted user abc, group def
- trusted Ignored: Error in configuration section [foo] parameter 'bar':
-bad interpolation variable reference '%('
- None
-untrusted raised Abort
-# configitems:
- trusted Ignored: Error in configuration section [foo]:
-bad interpolation variable reference '%('
- []
-untrusted raised Abort
+config error at .hg/hgrc:1: 'foo'
--- a/tests/test-ui-config Thu Apr 23 15:40:10 2009 -0500
+++ b/tests/test-ui-config Thu Apr 23 15:40:10 2009 -0500
@@ -1,7 +1,6 @@
#!/usr/bin/env python
-import ConfigParser
-from mercurial import ui, util, dispatch
+from mercurial import ui, util, dispatch, error
testui = ui.ui()
parsed = dispatch._parseconfig(testui, [
@@ -12,19 +11,10 @@
'lists.list2=foo bar baz',
'lists.list3=alice, bob',
'lists.list4=foo bar baz alice, bob',
- 'interpolation.value1=hallo',
- 'interpolation.value2=%(value1)s world',
- 'interpolation.value3=%(novalue)s',
- 'interpolation.value4=%(bad)1',
- 'interpolation.value5=%bad2',
])
print repr(testui.configitems('values'))
print repr(testui.configitems('lists'))
-try:
- print repr(testui.configitems('interpolation'))
-except util.Abort, inst:
- print inst
print "---"
print repr(testui.config('values', 'string'))
print repr(testui.config('values', 'bool1'))
@@ -33,7 +23,7 @@
print "---"
try:
print repr(testui.configbool('values', 'string'))
-except util.Abort, inst:
+except error.ConfigError, inst:
print inst
print repr(testui.configbool('values', 'bool1'))
print repr(testui.configbool('values', 'bool2'))
@@ -54,37 +44,12 @@
print repr(testui.configlist('lists', 'unknown', 'foo, bar'))
print repr(testui.configlist('lists', 'unknown', ['foo bar']))
print repr(testui.configlist('lists', 'unknown', ['foo', 'bar']))
-print "---"
-print repr(testui.config('interpolation', 'value1'))
-print repr(testui.config('interpolation', 'value2'))
-try:
- print repr(testui.config('interpolation', 'value3'))
-except util.Abort, inst:
- print inst
-try:
- print repr(testui.config('interpolation', 'value4'))
-except util.Abort, inst:
- print inst
-try:
- print repr(testui.config('interpolation', 'value5'))
-except util.Abort, inst:
- print inst
-print "---"
-cp = util.configparser()
-cp.add_section('foo')
-cp.set('foo', 'bar', 'baz')
-try:
- # should fail - keys are case-sensitive
- cp.get('foo', 'Bar')
-except ConfigParser.NoOptionError, inst:
- print inst
+print repr(testui.config('values', 'String'))
def function():
pass
-cp.add_section('hook')
# values that aren't strings should work
-cp.set('hook', 'commit', function)
-f = cp.get('hook', 'commit')
-print "f %s= function" % (f == function and '=' or '!')
+testui.setconfig('hook', 'commit', function)
+print function == testui.config('hook', 'commit')
--- a/tests/test-ui-config.out Thu Apr 23 15:40:10 2009 -0500
+++ b/tests/test-ui-config.out Thu Apr 23 15:40:10 2009 -0500
@@ -1,15 +1,12 @@
-[('bool1', 'true'), ('bool2', 'false'), ('string', 'string value')]
+[('string', 'string value'), ('bool1', 'true'), ('bool2', 'false')]
[('list1', 'foo'), ('list2', 'foo bar baz'), ('list3', 'alice, bob'), ('list4', 'foo bar baz alice, bob')]
-Error in configuration section [interpolation]:
-'%' must be followed by '%' or '(', found: '%bad2'
---
'string value'
'true'
'false'
None
---
-Error in configuration section [values] parameter 'string':
-Not a boolean: string value
+values.string not a boolean ('string value')
True
False
False
@@ -29,20 +26,5 @@
['foo', 'bar']
['foo bar']
['foo', 'bar']
----
-'hallo'
-'hallo world'
-Error in configuration section [interpolation] parameter 'value3':
-Bad value substitution:
- section: [interpolation]
- option : value3
- key : novalue
- rawval :
-
-Error in configuration section [interpolation] parameter 'value4':
-bad interpolation variable reference '%(bad)1'
-Error in configuration section [interpolation] parameter 'value5':
-'%' must be followed by '%' or '(', found: '%bad2'
----
-No option 'Bar' in section: 'foo'
-f == function
+None
+True