~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/config.py

resolve conflicts against trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
63
63
"""
64
64
 
65
65
import os
 
66
import string
66
67
import sys
67
68
 
68
69
from bzrlib import commands
69
70
from bzrlib.decorators import needs_write_lock
70
71
from bzrlib.lazy_import import lazy_import
71
72
lazy_import(globals(), """
72
 
import errno
73
73
import fnmatch
74
74
import re
75
75
from cStringIO import StringIO
279
279
        # We need to iterate until no more refs appear ({{foo}} will need two
280
280
        # iterations for example).
281
281
        while True:
282
 
            try:
283
 
                raw_chunks = self.option_ref_re.split(result)
284
 
            except TypeError:
285
 
                import pdb; pdb.set_trace()
 
282
            raw_chunks = self.option_ref_re.split(result)
286
283
            if len(raw_chunks) == 1:
287
284
                # Shorcut the trivial case: no refs
288
285
                return result
446
443
        the concrete policy type is checked, and finally
447
444
        $EMAIL is examined.
448
445
        If no username can be found, errors.NoWhoami exception is raised.
449
 
 
450
 
        TODO: Check it's reasonably well-formed.
451
446
        """
452
447
        v = os.environ.get('BZR_EMAIL')
453
448
        if v:
454
449
            return v.decode(osutils.get_user_encoding())
455
 
 
456
450
        v = self._get_user_id()
457
451
        if v:
458
452
            return v
459
 
 
460
453
        v = os.environ.get('EMAIL')
461
454
        if v:
462
455
            return v.decode(osutils.get_user_encoding())
463
 
 
 
456
        name, email = _auto_user_id()
 
457
        if name and email:
 
458
            return '%s <%s>' % (name, email)
 
459
        elif email:
 
460
            return email
464
461
        raise errors.NoWhoami()
465
462
 
466
463
    def ensure_username(self):
853
850
        # LockableConfig for other kind of transports, we will need to reuse
854
851
        # whatever connection is already established -- vila 20100929
855
852
        self.transport = transport.get_transport(self.dir)
856
 
        self._lock = lockdir.LockDir(self.transport, 'lock')
 
853
        self._lock = lockdir.LockDir(self.transport, self.lock_name)
857
854
 
858
855
    def _create_from_string(self, unicode_bytes, save):
859
856
        super(LockableConfig, self)._create_from_string(unicode_bytes, False)
972
969
        super(LockableConfig, self).remove_user_option(option_name,
973
970
                                                       section_name)
974
971
 
 
972
def _iter_for_location_by_parts(sections, location):
 
973
    """Keep only the sessions matching the specified location.
 
974
 
 
975
    :param sections: An iterable of section names.
 
976
 
 
977
    :param location: An url or a local path to match against.
 
978
 
 
979
    :returns: An iterator of (section, extra_path, nb_parts) where nb is the
 
980
        number of path components in the section name, section is the section
 
981
        name and extra_path is the difference between location and the section
 
982
        name.
 
983
 
 
984
    ``location`` will always be a local path and never a 'file://' url but the
 
985
    section names themselves can be in either form.
 
986
    """
 
987
    location_parts = location.rstrip('/').split('/')
 
988
 
 
989
    for section in sections:
 
990
        # location is a local path if possible, so we need to convert 'file://'
 
991
        # urls in section names to local paths if necessary.
 
992
 
 
993
        # This also avoids having file:///path be a more exact
 
994
        # match than '/path'.
 
995
 
 
996
        # FIXME: This still raises an issue if a user defines both file:///path
 
997
        # *and* /path. Should we raise an error in this case -- vila 20110505
 
998
 
 
999
        if section.startswith('file://'):
 
1000
            section_path = urlutils.local_path_from_url(section)
 
1001
        else:
 
1002
            section_path = section
 
1003
        section_parts = section_path.rstrip('/').split('/')
 
1004
 
 
1005
        matched = True
 
1006
        if len(section_parts) > len(location_parts):
 
1007
            # More path components in the section, they can't match
 
1008
            matched = False
 
1009
        else:
 
1010
            # Rely on zip truncating in length to the length of the shortest
 
1011
            # argument sequence.
 
1012
            names = zip(location_parts, section_parts)
 
1013
            for name in names:
 
1014
                if not fnmatch.fnmatch(name[0], name[1]):
 
1015
                    matched = False
 
1016
                    break
 
1017
        if not matched:
 
1018
            continue
 
1019
        # build the path difference between the section and the location
 
1020
        extra_path = '/'.join(location_parts[len(section_parts):])
 
1021
        yield section, extra_path, len(section_parts)
 
1022
 
975
1023
 
976
1024
class LocationConfig(LockableConfig):
977
1025
    """A configuration object that gives the policy for a location."""
1006
1054
 
1007
1055
    def _get_matching_sections(self):
1008
1056
        """Return an ordered list of section names matching this location."""
1009
 
        sections = self._get_parser()
1010
 
        location_names = self.location.split('/')
1011
 
        if self.location.endswith('/'):
1012
 
            del location_names[-1]
1013
 
        matches=[]
1014
 
        for section in sections:
1015
 
            # location is a local path if possible, so we need
1016
 
            # to convert 'file://' urls to local paths if necessary.
1017
 
            # This also avoids having file:///path be a more exact
1018
 
            # match than '/path'.
1019
 
            if section.startswith('file://'):
1020
 
                section_path = urlutils.local_path_from_url(section)
1021
 
            else:
1022
 
                section_path = section
1023
 
            section_names = section_path.split('/')
1024
 
            if section.endswith('/'):
1025
 
                del section_names[-1]
1026
 
            names = zip(location_names, section_names)
1027
 
            matched = True
1028
 
            for name in names:
1029
 
                if not fnmatch.fnmatch(name[0], name[1]):
1030
 
                    matched = False
1031
 
                    break
1032
 
            if not matched:
1033
 
                continue
1034
 
            # so, for the common prefix they matched.
1035
 
            # if section is longer, no match.
1036
 
            if len(section_names) > len(location_names):
1037
 
                continue
1038
 
            matches.append((len(section_names), section,
1039
 
                            '/'.join(location_names[len(section_names):])))
 
1057
        matches = list(_iter_for_location_by_parts(self._get_parser(),
 
1058
                                                   self.location))
1040
1059
        # put the longest (aka more specific) locations first
1041
 
        matches.sort(reverse=True)
1042
 
        sections = []
1043
 
        for (length, section, extra_path) in matches:
1044
 
            sections.append((section, extra_path))
 
1060
        matches.sort(
 
1061
            key=lambda (section, extra_path, length): (length, section),
 
1062
            reverse=True)
 
1063
        for (section, extra_path, length) in matches:
 
1064
            yield section, extra_path
1045
1065
            # should we stop looking for parent configs here?
1046
1066
            try:
1047
1067
                if self._get_parser()[section].as_bool('ignore_parents'):
1048
1068
                    break
1049
1069
            except KeyError:
1050
1070
                pass
1051
 
        return sections
1052
1071
 
1053
1072
    def _get_sections(self, name=None):
1054
1073
        """See IniBasedConfig._get_sections()."""
1412
1431
        return os.path.expanduser('~/.cache')
1413
1432
 
1414
1433
 
 
1434
def _get_default_mail_domain():
 
1435
    """If possible, return the assumed default email domain.
 
1436
 
 
1437
    :returns: string mail domain, or None.
 
1438
    """
 
1439
    if sys.platform == 'win32':
 
1440
        # No implementation yet; patches welcome
 
1441
        return None
 
1442
    try:
 
1443
        f = open('/etc/mailname')
 
1444
    except (IOError, OSError), e:
 
1445
        return None
 
1446
    try:
 
1447
        domain = f.read().strip()
 
1448
        return domain
 
1449
    finally:
 
1450
        f.close()
 
1451
 
 
1452
 
 
1453
def _auto_user_id():
 
1454
    """Calculate automatic user identification.
 
1455
 
 
1456
    :returns: (realname, email), either of which may be None if they can't be
 
1457
    determined.
 
1458
 
 
1459
    Only used when none is set in the environment or the id file.
 
1460
 
 
1461
    This only returns an email address if we can be fairly sure the 
 
1462
    address is reasonable, ie if /etc/mailname is set on unix.
 
1463
 
 
1464
    This doesn't use the FQDN as the default domain because that may be 
 
1465
    slow, and it doesn't use the hostname alone because that's not normally 
 
1466
    a reasonable address.
 
1467
    """
 
1468
    if sys.platform == 'win32':
 
1469
        # No implementation to reliably determine Windows default mail
 
1470
        # address; please add one.
 
1471
        return None, None
 
1472
 
 
1473
    default_mail_domain = _get_default_mail_domain()
 
1474
    if not default_mail_domain:
 
1475
        return None, None
 
1476
 
 
1477
    import pwd
 
1478
    uid = os.getuid()
 
1479
    try:
 
1480
        w = pwd.getpwuid(uid)
 
1481
    except KeyError:
 
1482
        mutter('no passwd entry for uid %d?' % uid)
 
1483
        return None, None
 
1484
 
 
1485
    # we try utf-8 first, because on many variants (like Linux),
 
1486
    # /etc/passwd "should" be in utf-8, and because it's unlikely to give
 
1487
    # false positives.  (many users will have their user encoding set to
 
1488
    # latin-1, which cannot raise UnicodeError.)
 
1489
    try:
 
1490
        gecos = w.pw_gecos.decode('utf-8')
 
1491
        encoding = 'utf-8'
 
1492
    except UnicodeError:
 
1493
        try:
 
1494
            encoding = osutils.get_user_encoding()
 
1495
            gecos = w.pw_gecos.decode(encoding)
 
1496
        except UnicodeError, e:
 
1497
            mutter("cannot decode passwd entry %s" % w)
 
1498
            return None, None
 
1499
    try:
 
1500
        username = w.pw_name.decode(encoding)
 
1501
    except UnicodeError, e:
 
1502
        mutter("cannot decode passwd entry %s" % w)
 
1503
        return None, None
 
1504
 
 
1505
    comma = gecos.find(',')
 
1506
    if comma == -1:
 
1507
        realname = gecos
 
1508
    else:
 
1509
        realname = gecos[:comma]
 
1510
 
 
1511
    return realname, (username + '@' + default_mail_domain)
 
1512
 
 
1513
 
1415
1514
def parse_username(username):
1416
1515
    """Parse e-mail username and return a (name, address) tuple."""
1417
1516
    match = re.match(r'(.*?)\s*<?([\w+.-]+@[\w+.-]+)>?', username)
1993
2092
        self._transport.put_file(self._filename, out_file)
1994
2093
 
1995
2094
 
 
2095
class Section(object):
 
2096
    """A section defines a dict of options.
 
2097
 
 
2098
    This is merely a read-only dict which can add some knowledge about the
 
2099
    options. It is *not* a python dict object though and doesn't try to mimic
 
2100
    its API.
 
2101
    """
 
2102
 
 
2103
    def __init__(self, section_id, options):
 
2104
        self.id = section_id
 
2105
        # We re-use the dict-like object received
 
2106
        self.options = options
 
2107
 
 
2108
    def get(self, name, default=None):
 
2109
        return self.options.get(name, default)
 
2110
 
 
2111
    def __repr__(self):
 
2112
        # Mostly for debugging use
 
2113
        return "<config.%s id=%s>" % (self.__class__.__name__, self.id)
 
2114
 
 
2115
 
 
2116
_NewlyCreatedOption = object()
 
2117
"""Was the option created during the MutableSection lifetime"""
 
2118
 
 
2119
 
 
2120
class MutableSection(Section):
 
2121
    """A section allowing changes and keeping track of the original values."""
 
2122
 
 
2123
    def __init__(self, section_id, options):
 
2124
        super(MutableSection, self).__init__(section_id, options)
 
2125
        self.orig = {}
 
2126
 
 
2127
    def set(self, name, value):
 
2128
        if name not in self.options:
 
2129
            # This is a new option
 
2130
            self.orig[name] = _NewlyCreatedOption
 
2131
        elif name not in self.orig:
 
2132
            self.orig[name] = self.get(name, None)
 
2133
        self.options[name] = value
 
2134
 
 
2135
    def remove(self, name):
 
2136
        if name not in self.orig:
 
2137
            self.orig[name] = self.get(name, None)
 
2138
        del self.options[name]
 
2139
 
 
2140
 
 
2141
class Store(object):
 
2142
    """Abstract interface to persistent storage for configuration options."""
 
2143
 
 
2144
    readonly_section_class = Section
 
2145
    mutable_section_class = MutableSection
 
2146
 
 
2147
    def is_loaded(self):
 
2148
        """Returns True if the Store has been loaded.
 
2149
 
 
2150
        This is used to implement lazy loading and ensure the persistent
 
2151
        storage is queried only when needed.
 
2152
        """
 
2153
        raise NotImplementedError(self.is_loaded)
 
2154
 
 
2155
    def load(self):
 
2156
        """Loads the Store from persistent storage."""
 
2157
        raise NotImplementedError(self.load)
 
2158
 
 
2159
    def _load_from_string(self, str_or_unicode):
 
2160
        """Create a store from a string in configobj syntax.
 
2161
 
 
2162
        :param str_or_unicode: A string representing the file content. This will
 
2163
            be encoded to suit store needs internally.
 
2164
 
 
2165
        This is for tests and should not be used in production unless a
 
2166
        convincing use case can be demonstrated :)
 
2167
        """
 
2168
        raise NotImplementedError(self._load_from_string)
 
2169
 
 
2170
    def save(self):
 
2171
        """Saves the Store to persistent storage."""
 
2172
        raise NotImplementedError(self.save)
 
2173
 
 
2174
    def external_url(self):
 
2175
        raise NotImplementedError(self.external_url)
 
2176
 
 
2177
    def get_sections(self):
 
2178
        """Returns an ordered iterable of existing sections.
 
2179
 
 
2180
        :returns: An iterable of (name, dict).
 
2181
        """
 
2182
        raise NotImplementedError(self.get_sections)
 
2183
 
 
2184
    def get_mutable_section(self, section_name=None):
 
2185
        """Returns the specified mutable section.
 
2186
 
 
2187
        :param section_name: The section identifier
 
2188
        """
 
2189
        raise NotImplementedError(self.get_mutable_section)
 
2190
 
 
2191
    def __repr__(self):
 
2192
        # Mostly for debugging use
 
2193
        return "<config.%s(%s)>" % (self.__class__.__name__,
 
2194
                                    self.external_url())
 
2195
 
 
2196
 
 
2197
class IniFileStore(Store):
 
2198
    """A config Store using ConfigObj for storage.
 
2199
 
 
2200
    :ivar transport: The transport object where the config file is located.
 
2201
 
 
2202
    :ivar file_name: The config file basename in the transport directory.
 
2203
 
 
2204
    :ivar _config_obj: Private member to hold the ConfigObj instance used to
 
2205
        serialize/deserialize the config file.
 
2206
    """
 
2207
 
 
2208
    def __init__(self, transport, file_name):
 
2209
        """A config Store using ConfigObj for storage.
 
2210
 
 
2211
        :param transport: The transport object where the config file is located.
 
2212
 
 
2213
        :param file_name: The config file basename in the transport directory.
 
2214
        """
 
2215
        super(IniFileStore, self).__init__()
 
2216
        self.transport = transport
 
2217
        self.file_name = file_name
 
2218
        self._config_obj = None
 
2219
 
 
2220
    def is_loaded(self):
 
2221
        return self._config_obj != None
 
2222
 
 
2223
    def load(self):
 
2224
        """Load the store from the associated file."""
 
2225
        if self.is_loaded():
 
2226
            return
 
2227
        content = self.transport.get_bytes(self.file_name)
 
2228
        self._load_from_string(content)
 
2229
 
 
2230
    def _load_from_string(self, str_or_unicode):
 
2231
        """Create a config store from a string.
 
2232
 
 
2233
        :param str_or_unicode: A string representing the file content. This will
 
2234
            be utf-8 encoded internally.
 
2235
 
 
2236
        This is for tests and should not be used in production unless a
 
2237
        convincing use case can be demonstrated :)
 
2238
        """
 
2239
        if self.is_loaded():
 
2240
            raise AssertionError('Already loaded: %r' % (self._config_obj,))
 
2241
        co_input = StringIO(str_or_unicode.encode('utf-8'))
 
2242
        try:
 
2243
            # The config files are always stored utf8-encoded
 
2244
            self._config_obj = ConfigObj(co_input, encoding='utf-8')
 
2245
        except configobj.ConfigObjError, e:
 
2246
            self._config_obj = None
 
2247
            raise errors.ParseConfigError(e.errors, self.external_url())
 
2248
 
 
2249
    def save(self):
 
2250
        if not self.is_loaded():
 
2251
            # Nothing to save
 
2252
            return
 
2253
        out = StringIO()
 
2254
        self._config_obj.write(out)
 
2255
        self.transport.put_bytes(self.file_name, out.getvalue())
 
2256
 
 
2257
    def external_url(self):
 
2258
        # FIXME: external_url should really accepts an optional relpath
 
2259
        # parameter (bug #750169) :-/ -- vila 2011-04-04
 
2260
        # The following will do in the interim but maybe we don't want to
 
2261
        # expose a path here but rather a config ID and its associated
 
2262
        # object </hand wawe>.
 
2263
        return urlutils.join(self.transport.external_url(), self.file_name)
 
2264
 
 
2265
    def get_sections(self):
 
2266
        """Get the configobj section in the file order.
 
2267
 
 
2268
        :returns: An iterable of (name, dict).
 
2269
        """
 
2270
        # We need a loaded store
 
2271
        try:
 
2272
            self.load()
 
2273
        except errors.NoSuchFile:
 
2274
            # If the file doesn't exist, there is no sections
 
2275
            return
 
2276
        cobj = self._config_obj
 
2277
        if cobj.scalars:
 
2278
            yield self.readonly_section_class(None, cobj)
 
2279
        for section_name in cobj.sections:
 
2280
            yield self.readonly_section_class(section_name, cobj[section_name])
 
2281
 
 
2282
    def get_mutable_section(self, section_name=None):
 
2283
        # We need a loaded store
 
2284
        try:
 
2285
            self.load()
 
2286
        except errors.NoSuchFile:
 
2287
            # The file doesn't exist, let's pretend it was empty
 
2288
            self._load_from_string('')
 
2289
        if section_name is None:
 
2290
            section = self._config_obj
 
2291
        else:
 
2292
            section = self._config_obj.setdefault(section_name, {})
 
2293
        return self.mutable_section_class(section_name, section)
 
2294
 
 
2295
 
 
2296
# Note that LockableConfigObjStore inherits from ConfigObjStore because we need
 
2297
# unlockable stores for use with objects that can already ensure the locking
 
2298
# (think branches). If different stores (not based on ConfigObj) are created,
 
2299
# they may face the same issue.
 
2300
 
 
2301
 
 
2302
class LockableIniFileStore(IniFileStore):
 
2303
    """A ConfigObjStore using locks on save to ensure store integrity."""
 
2304
 
 
2305
    def __init__(self, transport, file_name, lock_dir_name=None):
 
2306
        """A config Store using ConfigObj for storage.
 
2307
 
 
2308
        :param transport: The transport object where the config file is located.
 
2309
 
 
2310
        :param file_name: The config file basename in the transport directory.
 
2311
        """
 
2312
        if lock_dir_name is None:
 
2313
            lock_dir_name = 'lock'
 
2314
        self.lock_dir_name = lock_dir_name
 
2315
        super(LockableIniFileStore, self).__init__(transport, file_name)
 
2316
        self._lock = lockdir.LockDir(self.transport, self.lock_dir_name)
 
2317
 
 
2318
    def lock_write(self, token=None):
 
2319
        """Takes a write lock in the directory containing the config file.
 
2320
 
 
2321
        If the directory doesn't exist it is created.
 
2322
        """
 
2323
        # FIXME: This doesn't check the ownership of the created directories as
 
2324
        # ensure_config_dir_exists does. It should if the transport is local
 
2325
        # -- vila 2011-04-06
 
2326
        self.transport.create_prefix()
 
2327
        return self._lock.lock_write(token)
 
2328
 
 
2329
    def unlock(self):
 
2330
        self._lock.unlock()
 
2331
 
 
2332
    def break_lock(self):
 
2333
        self._lock.break_lock()
 
2334
 
 
2335
    @needs_write_lock
 
2336
    def save(self):
 
2337
        super(LockableIniFileStore, self).save()
 
2338
 
 
2339
 
 
2340
# FIXME: global, bazaar, shouldn't that be 'user' instead or even
 
2341
# 'user_defaults' as opposed to 'user_overrides', 'system_defaults'
 
2342
# (/etc/bzr/bazaar.conf) and 'system_overrides' ? -- vila 2011-04-05
 
2343
 
 
2344
# FIXME: Moreover, we shouldn't need classes for these stores either, factory
 
2345
# functions or a registry will make it easier and clearer for tests, focusing
 
2346
# on the relevant parts of the API that needs testing -- vila 20110503 (based
 
2347
# on a poolie's remark)
 
2348
class GlobalStore(LockableIniFileStore):
 
2349
 
 
2350
    def __init__(self, possible_transports=None):
 
2351
        t = transport.get_transport(config_dir(),
 
2352
                                    possible_transports=possible_transports)
 
2353
        super(GlobalStore, self).__init__(t, 'bazaar.conf')
 
2354
 
 
2355
 
 
2356
class LocationStore(LockableIniFileStore):
 
2357
 
 
2358
    def __init__(self, possible_transports=None):
 
2359
        t = transport.get_transport(config_dir(),
 
2360
                                    possible_transports=possible_transports)
 
2361
        super(LocationStore, self).__init__(t, 'locations.conf')
 
2362
 
 
2363
 
 
2364
class BranchStore(IniFileStore):
 
2365
 
 
2366
    def __init__(self, branch):
 
2367
        super(BranchStore, self).__init__(branch.control_transport,
 
2368
                                          'branch.conf')
 
2369
 
 
2370
class SectionMatcher(object):
 
2371
    """Select sections into a given Store.
 
2372
 
 
2373
    This intended to be used to postpone getting an iterable of sections from a
 
2374
    store.
 
2375
    """
 
2376
 
 
2377
    def __init__(self, store):
 
2378
        self.store = store
 
2379
 
 
2380
    def get_sections(self):
 
2381
        # This is where we require loading the store so we can see all defined
 
2382
        # sections.
 
2383
        sections = self.store.get_sections()
 
2384
        # Walk the revisions in the order provided
 
2385
        for s in sections:
 
2386
            if self.match(s):
 
2387
                yield s
 
2388
 
 
2389
    def match(self, secion):
 
2390
        raise NotImplementedError(self.match)
 
2391
 
 
2392
 
 
2393
class LocationSection(Section):
 
2394
 
 
2395
    def __init__(self, section, length, extra_path):
 
2396
        super(LocationSection, self).__init__(section.id, section.options)
 
2397
        self.length = length
 
2398
        self.extra_path = extra_path
 
2399
 
 
2400
    def get(self, name, default=None):
 
2401
        value = super(LocationSection, self).get(name, default)
 
2402
        if value is not None:
 
2403
            policy_name = self.get(name + ':policy', None)
 
2404
            policy = _policy_value.get(policy_name, POLICY_NONE)
 
2405
            if policy == POLICY_APPENDPATH:
 
2406
                value = urlutils.join(value, self.extra_path)
 
2407
        return value
 
2408
 
 
2409
 
 
2410
class LocationMatcher(SectionMatcher):
 
2411
 
 
2412
    def __init__(self, store, location):
 
2413
        super(LocationMatcher, self).__init__(store)
 
2414
        if location.startswith('file://'):
 
2415
            location = urlutils.local_path_from_url(location)
 
2416
        self.location = location
 
2417
 
 
2418
    def _get_matching_sections(self):
 
2419
        """Get all sections matching ``location``."""
 
2420
        # We slightly diverge from LocalConfig here by allowing the no-name
 
2421
        # section as the most generic one and the lower priority.
 
2422
        no_name_section = None
 
2423
        sections = []
 
2424
        # Filter out the no_name_section so _iter_for_location_by_parts can be
 
2425
        # used (it assumes all sections have a name).
 
2426
        for section in self.store.get_sections():
 
2427
            if section.id is None:
 
2428
                no_name_section = section
 
2429
            else:
 
2430
                sections.append(section)
 
2431
        # Unfortunately _iter_for_location_by_parts deals with section names so
 
2432
        # we have to resync.
 
2433
        filtered_sections = _iter_for_location_by_parts(
 
2434
            [s.id for s in sections], self.location)
 
2435
        iter_sections = iter(sections)
 
2436
        matching_sections = []
 
2437
        if no_name_section is not None:
 
2438
            matching_sections.append(
 
2439
                LocationSection(no_name_section, 0, self.location))
 
2440
        for section_id, extra_path, length in filtered_sections:
 
2441
            # a section id is unique for a given store so it's safe to iterate
 
2442
            # again
 
2443
            section = iter_sections.next()
 
2444
            if section_id == section.id:
 
2445
                matching_sections.append(
 
2446
                    LocationSection(section, length, extra_path))
 
2447
        return matching_sections
 
2448
 
 
2449
    def get_sections(self):
 
2450
        # Override the default implementation as we want to change the order
 
2451
        matching_sections = self._get_matching_sections()
 
2452
        # We want the longest (aka more specific) locations first
 
2453
        sections = sorted(matching_sections,
 
2454
                          key=lambda section: (section.length, section.id),
 
2455
                          reverse=True)
 
2456
        # Sections mentioning 'ignore_parents' restrict the selection
 
2457
        for section in sections:
 
2458
            # FIXME: We really want to use as_bool below -- vila 2011-04-07
 
2459
            ignore = section.get('ignore_parents', None)
 
2460
            if ignore is not None:
 
2461
                ignore = ui.bool_from_string(ignore)
 
2462
            if ignore:
 
2463
                break
 
2464
            # Finally, we have a valid section
 
2465
            yield section
 
2466
 
 
2467
 
 
2468
class Stack(object):
 
2469
    """A stack of configurations where an option can be defined"""
 
2470
 
 
2471
    def __init__(self, sections_def, store=None, mutable_section_name=None):
 
2472
        """Creates a stack of sections with an optional store for changes.
 
2473
 
 
2474
        :param sections_def: A list of Section or callables that returns an
 
2475
            iterable of Section. This defines the Sections for the Stack and
 
2476
            can be called repeatedly if needed.
 
2477
 
 
2478
        :param store: The optional Store where modifications will be
 
2479
            recorded. If none is specified, no modifications can be done.
 
2480
 
 
2481
        :param mutable_section_name: The name of the MutableSection where
 
2482
            changes are recorded. This requires the ``store`` parameter to be
 
2483
            specified.
 
2484
        """
 
2485
        self.sections_def = sections_def
 
2486
        self.store = store
 
2487
        self.mutable_section_name = mutable_section_name
 
2488
 
 
2489
    def get(self, name):
 
2490
        """Return the *first* option value found in the sections.
 
2491
 
 
2492
        This is where we guarantee that sections coming from Store are loaded
 
2493
        lazily: the loading is delayed until we need to either check that an
 
2494
        option exists or get its value, which in turn may require to discover
 
2495
        in which sections it can be defined. Both of these (section and option
 
2496
        existence) require loading the store (even partially).
 
2497
        """
 
2498
        # FIXME: No caching of options nor sections yet -- vila 20110503
 
2499
 
 
2500
        # Ensuring lazy loading is achieved by delaying section matching (which
 
2501
        # implies querying the persistent storage) until it can't be avoided
 
2502
        # anymore by using callables to describe (possibly empty) section
 
2503
        # lists.
 
2504
        for section_or_callable in self.sections_def:
 
2505
            # Each section can expand to multiple ones when a callable is used
 
2506
            if callable(section_or_callable):
 
2507
                sections = section_or_callable()
 
2508
            else:
 
2509
                sections = [section_or_callable]
 
2510
            for section in sections:
 
2511
                value = section.get(name)
 
2512
                if value is not None:
 
2513
                    return value
 
2514
        # No definition was found
 
2515
        return None
 
2516
 
 
2517
    def _get_mutable_section(self):
 
2518
        """Get the MutableSection for the Stack.
 
2519
 
 
2520
        This is where we guarantee that the mutable section is lazily loaded:
 
2521
        this means we won't load the corresponding store before setting a value
 
2522
        or deleting an option. In practice the store will often be loaded but
 
2523
        this allows helps catching some programming errors.
 
2524
        """
 
2525
        section = self.store.get_mutable_section(self.mutable_section_name)
 
2526
        return section
 
2527
 
 
2528
    def set(self, name, value):
 
2529
        """Set a new value for the option."""
 
2530
        section = self._get_mutable_section()
 
2531
        section.set(name, value)
 
2532
 
 
2533
    def remove(self, name):
 
2534
        """Remove an existing option."""
 
2535
        section = self._get_mutable_section()
 
2536
        section.remove(name)
 
2537
 
 
2538
    def __repr__(self):
 
2539
        # Mostly for debugging use
 
2540
        return "<config.%s(%s)>" % (self.__class__.__name__, id(self))
 
2541
 
 
2542
 
 
2543
class GlobalStack(Stack):
 
2544
 
 
2545
    def __init__(self):
 
2546
        # Get a GlobalStore
 
2547
        gstore = GlobalStore()
 
2548
        super(GlobalStack, self).__init__([gstore.get_sections], gstore)
 
2549
 
 
2550
 
 
2551
class LocationStack(Stack):
 
2552
 
 
2553
    def __init__(self, location):
 
2554
        lstore = LocationStore()
 
2555
        matcher = LocationMatcher(lstore, location)
 
2556
        gstore = GlobalStore()
 
2557
        super(LocationStack, self).__init__(
 
2558
            [matcher.get_sections, gstore.get_sections], lstore)
 
2559
 
 
2560
 
 
2561
class BranchStack(Stack):
 
2562
 
 
2563
    def __init__(self, branch):
 
2564
        bstore = BranchStore(branch)
 
2565
        lstore = LocationStore()
 
2566
        matcher = LocationMatcher(lstore, branch.base)
 
2567
        gstore = GlobalStore()
 
2568
        super(BranchStack, self).__init__(
 
2569
            [matcher.get_sections, bstore.get_sections, gstore.get_sections],
 
2570
            bstore)
 
2571
 
 
2572
 
1996
2573
class cmd_config(commands.Command):
1997
2574
    __doc__ = """Display, set or remove a configuration option.
1998
2575