~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/config.py

  • Committer: John Arbash Meinel
  • Date: 2011-05-06 15:15:44 UTC
  • mfrom: (5835 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5836.
  • Revision ID: john@arbash-meinel.com-20110506151544-atzxeezfwssnlacr
Merge bzr.dev 5835 in prep for release-notes updates

Show diffs side-by-side

added added

removed removed

Lines of Context:
848
848
        # LockableConfig for other kind of transports, we will need to reuse
849
849
        # whatever connection is already established -- vila 20100929
850
850
        self.transport = transport.get_transport(self.dir)
851
 
        self._lock = lockdir.LockDir(self.transport, 'lock')
 
851
        self._lock = lockdir.LockDir(self.transport, self.lock_name)
852
852
 
853
853
    def _create_from_string(self, unicode_bytes, save):
854
854
        super(LockableConfig, self)._create_from_string(unicode_bytes, False)
2094
2094
        self._transport.put_file(self._filename, out_file)
2095
2095
 
2096
2096
 
 
2097
class Section(object):
 
2098
    """A section defines a dict of options.
 
2099
 
 
2100
    This is merely a read-only dict which can add some knowledge about the
 
2101
    options. It is *not* a python dict object though and doesn't try to mimic
 
2102
    its API.
 
2103
    """
 
2104
 
 
2105
    def __init__(self, section_id, options):
 
2106
        self.id = section_id
 
2107
        # We re-use the dict-like object received
 
2108
        self.options = options
 
2109
 
 
2110
    def get(self, name, default=None):
 
2111
        return self.options.get(name, default)
 
2112
 
 
2113
    def __repr__(self):
 
2114
        # Mostly for debugging use
 
2115
        return "<config.%s id=%s>" % (self.__class__.__name__, self.id)
 
2116
 
 
2117
 
 
2118
_NewlyCreatedOption = object()
 
2119
"""Was the option created during the MutableSection lifetime"""
 
2120
 
 
2121
 
 
2122
class MutableSection(Section):
 
2123
    """A section allowing changes and keeping track of the original values."""
 
2124
 
 
2125
    def __init__(self, section_id, options):
 
2126
        super(MutableSection, self).__init__(section_id, options)
 
2127
        self.orig = {}
 
2128
 
 
2129
    def set(self, name, value):
 
2130
        if name not in self.options:
 
2131
            # This is a new option
 
2132
            self.orig[name] = _NewlyCreatedOption
 
2133
        elif name not in self.orig:
 
2134
            self.orig[name] = self.get(name, None)
 
2135
        self.options[name] = value
 
2136
 
 
2137
    def remove(self, name):
 
2138
        if name not in self.orig:
 
2139
            self.orig[name] = self.get(name, None)
 
2140
        del self.options[name]
 
2141
 
 
2142
 
 
2143
class Store(object):
 
2144
    """Abstract interface to persistent storage for configuration options."""
 
2145
 
 
2146
    readonly_section_class = Section
 
2147
    mutable_section_class = MutableSection
 
2148
 
 
2149
    def is_loaded(self):
 
2150
        """Returns True if the Store has been loaded.
 
2151
 
 
2152
        This is used to implement lazy loading and ensure the persistent
 
2153
        storage is queried only when needed.
 
2154
        """
 
2155
        raise NotImplementedError(self.is_loaded)
 
2156
 
 
2157
    def load(self):
 
2158
        """Loads the Store from persistent storage."""
 
2159
        raise NotImplementedError(self.load)
 
2160
 
 
2161
    def _load_from_string(self, str_or_unicode):
 
2162
        """Create a store from a string in configobj syntax.
 
2163
 
 
2164
        :param str_or_unicode: A string representing the file content. This will
 
2165
            be encoded to suit store needs internally.
 
2166
 
 
2167
        This is for tests and should not be used in production unless a
 
2168
        convincing use case can be demonstrated :)
 
2169
        """
 
2170
        raise NotImplementedError(self._load_from_string)
 
2171
 
 
2172
    def save(self):
 
2173
        """Saves the Store to persistent storage."""
 
2174
        raise NotImplementedError(self.save)
 
2175
 
 
2176
    def external_url(self):
 
2177
        raise NotImplementedError(self.external_url)
 
2178
 
 
2179
    def get_sections(self):
 
2180
        """Returns an ordered iterable of existing sections.
 
2181
 
 
2182
        :returns: An iterable of (name, dict).
 
2183
        """
 
2184
        raise NotImplementedError(self.get_sections)
 
2185
 
 
2186
    def get_mutable_section(self, section_name=None):
 
2187
        """Returns the specified mutable section.
 
2188
 
 
2189
        :param section_name: The section identifier
 
2190
        """
 
2191
        raise NotImplementedError(self.get_mutable_section)
 
2192
 
 
2193
    def __repr__(self):
 
2194
        # Mostly for debugging use
 
2195
        return "<config.%s(%s)>" % (self.__class__.__name__,
 
2196
                                    self.external_url())
 
2197
 
 
2198
 
 
2199
class IniFileStore(Store):
 
2200
    """A config Store using ConfigObj for storage.
 
2201
 
 
2202
    :ivar transport: The transport object where the config file is located.
 
2203
 
 
2204
    :ivar file_name: The config file basename in the transport directory.
 
2205
 
 
2206
    :ivar _config_obj: Private member to hold the ConfigObj instance used to
 
2207
        serialize/deserialize the config file.
 
2208
    """
 
2209
 
 
2210
    def __init__(self, transport, file_name):
 
2211
        """A config Store using ConfigObj for storage.
 
2212
 
 
2213
        :param transport: The transport object where the config file is located.
 
2214
 
 
2215
        :param file_name: The config file basename in the transport directory.
 
2216
        """
 
2217
        super(IniFileStore, self).__init__()
 
2218
        self.transport = transport
 
2219
        self.file_name = file_name
 
2220
        self._config_obj = None
 
2221
 
 
2222
    def is_loaded(self):
 
2223
        return self._config_obj != None
 
2224
 
 
2225
    def load(self):
 
2226
        """Load the store from the associated file."""
 
2227
        if self.is_loaded():
 
2228
            return
 
2229
        content = self.transport.get_bytes(self.file_name)
 
2230
        self._load_from_string(content)
 
2231
 
 
2232
    def _load_from_string(self, str_or_unicode):
 
2233
        """Create a config store from a string.
 
2234
 
 
2235
        :param str_or_unicode: A string representing the file content. This will
 
2236
            be utf-8 encoded internally.
 
2237
 
 
2238
        This is for tests and should not be used in production unless a
 
2239
        convincing use case can be demonstrated :)
 
2240
        """
 
2241
        if self.is_loaded():
 
2242
            raise AssertionError('Already loaded: %r' % (self._config_obj,))
 
2243
        co_input = StringIO(str_or_unicode.encode('utf-8'))
 
2244
        try:
 
2245
            # The config files are always stored utf8-encoded
 
2246
            self._config_obj = ConfigObj(co_input, encoding='utf-8')
 
2247
        except configobj.ConfigObjError, e:
 
2248
            self._config_obj = None
 
2249
            raise errors.ParseConfigError(e.errors, self.external_url())
 
2250
 
 
2251
    def save(self):
 
2252
        if not self.is_loaded():
 
2253
            # Nothing to save
 
2254
            return
 
2255
        out = StringIO()
 
2256
        self._config_obj.write(out)
 
2257
        self.transport.put_bytes(self.file_name, out.getvalue())
 
2258
 
 
2259
    def external_url(self):
 
2260
        # FIXME: external_url should really accepts an optional relpath
 
2261
        # parameter (bug #750169) :-/ -- vila 2011-04-04
 
2262
        # The following will do in the interim but maybe we don't want to
 
2263
        # expose a path here but rather a config ID and its associated
 
2264
        # object </hand wawe>.
 
2265
        return urlutils.join(self.transport.external_url(), self.file_name)
 
2266
 
 
2267
    def get_sections(self):
 
2268
        """Get the configobj section in the file order.
 
2269
 
 
2270
        :returns: An iterable of (name, dict).
 
2271
        """
 
2272
        # We need a loaded store
 
2273
        self.load()
 
2274
        cobj = self._config_obj
 
2275
        if cobj.scalars:
 
2276
            yield self.readonly_section_class(None, cobj)
 
2277
        for section_name in cobj.sections:
 
2278
            yield self.readonly_section_class(section_name, cobj[section_name])
 
2279
 
 
2280
    def get_mutable_section(self, section_name=None):
 
2281
        # We need a loaded store
 
2282
        try:
 
2283
            self.load()
 
2284
        except errors.NoSuchFile:
 
2285
            # The file doesn't exist, let's pretend it was empty
 
2286
            self._load_from_string('')
 
2287
        if section_name is None:
 
2288
            section = self._config_obj
 
2289
        else:
 
2290
            section = self._config_obj.setdefault(section_name, {})
 
2291
        return self.mutable_section_class(section_name, section)
 
2292
 
 
2293
 
 
2294
# Note that LockableConfigObjStore inherits from ConfigObjStore because we need
 
2295
# unlockable stores for use with objects that can already ensure the locking
 
2296
# (think branches). If different stores (not based on ConfigObj) are created,
 
2297
# they may face the same issue.
 
2298
 
 
2299
 
 
2300
class LockableIniFileStore(IniFileStore):
 
2301
    """A ConfigObjStore using locks on save to ensure store integrity."""
 
2302
 
 
2303
    def __init__(self, transport, file_name, lock_dir_name=None):
 
2304
        """A config Store using ConfigObj for storage.
 
2305
 
 
2306
        :param transport: The transport object where the config file is located.
 
2307
 
 
2308
        :param file_name: The config file basename in the transport directory.
 
2309
        """
 
2310
        if lock_dir_name is None:
 
2311
            lock_dir_name = 'lock'
 
2312
        self.lock_dir_name = lock_dir_name
 
2313
        super(LockableIniFileStore, self).__init__(transport, file_name)
 
2314
        self._lock = lockdir.LockDir(self.transport, self.lock_dir_name)
 
2315
 
 
2316
    def lock_write(self, token=None):
 
2317
        """Takes a write lock in the directory containing the config file.
 
2318
 
 
2319
        If the directory doesn't exist it is created.
 
2320
        """
 
2321
        # FIXME: This doesn't check the ownership of the created directories as
 
2322
        # ensure_config_dir_exists does. It should if the transport is local
 
2323
        # -- vila 2011-04-06
 
2324
        self.transport.create_prefix()
 
2325
        return self._lock.lock_write(token)
 
2326
 
 
2327
    def unlock(self):
 
2328
        self._lock.unlock()
 
2329
 
 
2330
    def break_lock(self):
 
2331
        self._lock.break_lock()
 
2332
 
 
2333
    @needs_write_lock
 
2334
    def save(self):
 
2335
        super(LockableIniFileStore, self).save()
 
2336
 
 
2337
 
 
2338
# FIXME: global, bazaar, shouldn't that be 'user' instead or even
 
2339
# 'user_defaults' as opposed to 'user_overrides', 'system_defaults'
 
2340
# (/etc/bzr/bazaar.conf) and 'system_overrides' ? -- vila 2011-04-05
 
2341
 
 
2342
# FIXME: Moreover, we shouldn't need classes for these stores either, factory
 
2343
# functions or a registry will make it easier and clearer for tests, focusing
 
2344
# on the relevant parts of the API that needs testing -- vila 20110503 (based
 
2345
# on a poolie's remark)
 
2346
class GlobalStore(LockableIniFileStore):
 
2347
 
 
2348
    def __init__(self, possible_transports=None):
 
2349
        t = transport.get_transport(config_dir(),
 
2350
                                    possible_transports=possible_transports)
 
2351
        super(GlobalStore, self).__init__(t, 'bazaar.conf')
 
2352
 
 
2353
 
 
2354
class LocationStore(LockableIniFileStore):
 
2355
 
 
2356
    def __init__(self, possible_transports=None):
 
2357
        t = transport.get_transport(config_dir(),
 
2358
                                    possible_transports=possible_transports)
 
2359
        super(LocationStore, self).__init__(t, 'locations.conf')
 
2360
 
 
2361
 
 
2362
class BranchStore(IniFileStore):
 
2363
 
 
2364
    def __init__(self, branch):
 
2365
        super(BranchStore, self).__init__(branch.control_transport,
 
2366
                                          'branch.conf')
 
2367
 
 
2368
class SectionMatcher(object):
 
2369
    """Select sections into a given Store.
 
2370
 
 
2371
    This intended to be used to postpone getting an iterable of sections from a
 
2372
    store.
 
2373
    """
 
2374
 
 
2375
    def __init__(self, store):
 
2376
        self.store = store
 
2377
 
 
2378
    def get_sections(self):
 
2379
        # This is where we require loading the store so we can see all defined
 
2380
        # sections.
 
2381
        sections = self.store.get_sections()
 
2382
        # Walk the revisions in the order provided
 
2383
        for s in sections:
 
2384
            if self.match(s):
 
2385
                yield s
 
2386
 
 
2387
    def match(self, secion):
 
2388
        raise NotImplementedError(self.match)
 
2389
 
 
2390
 
 
2391
class LocationSection(Section):
 
2392
 
 
2393
    def __init__(self, section, length, extra_path):
 
2394
        super(LocationSection, self).__init__(section.id, section.options)
 
2395
        self.length = length
 
2396
        self.extra_path = extra_path
 
2397
 
 
2398
    def get(self, name, default=None):
 
2399
        value = super(LocationSection, self).get(name, default)
 
2400
        if value is not None:
 
2401
            policy_name = self.get(name + ':policy', None)
 
2402
            policy = _policy_value.get(policy_name, POLICY_NONE)
 
2403
            if policy == POLICY_APPENDPATH:
 
2404
                value = urlutils.join(value, self.extra_path)
 
2405
        return value
 
2406
 
 
2407
 
 
2408
class LocationMatcher(SectionMatcher):
 
2409
 
 
2410
    def __init__(self, store, location):
 
2411
        super(LocationMatcher, self).__init__(store)
 
2412
        self.location = location
 
2413
 
 
2414
    def get_sections(self):
 
2415
        # Override the default implementation as we want to change the order
 
2416
 
 
2417
        # The following is a bit hackish but ensures compatibility with
 
2418
        # LocationConfig by reusing the same code
 
2419
        sections = list(self.store.get_sections())
 
2420
        filtered_sections = _iter_for_location_by_parts(
 
2421
            [s.id for s in sections], self.location)
 
2422
        iter_sections = iter(sections)
 
2423
        matching_sections = []
 
2424
        for section_id, extra_path, length in filtered_sections:
 
2425
            # a section id is unique for a given store so it's safe to iterate
 
2426
            # again
 
2427
            section = iter_sections.next()
 
2428
            if section_id == section.id:
 
2429
                matching_sections.append(
 
2430
                    LocationSection(section, length, extra_path))
 
2431
        # We want the longest (aka more specific) locations first
 
2432
        sections = sorted(matching_sections,
 
2433
                          key=lambda section: (section.length, section.id),
 
2434
                          reverse=True)
 
2435
        # Sections mentioning 'ignore_parents' restrict the selection
 
2436
        for section in sections:
 
2437
            # FIXME: We really want to use as_bool below -- vila 2011-04-07
 
2438
            ignore = section.get('ignore_parents', None)
 
2439
            if ignore is not None:
 
2440
                ignore = ui.bool_from_string(ignore)
 
2441
            if ignore:
 
2442
                break
 
2443
            # Finally, we have a valid section
 
2444
            yield section
 
2445
 
 
2446
 
 
2447
class Stack(object):
 
2448
    """A stack of configurations where an option can be defined"""
 
2449
 
 
2450
    def __init__(self, sections_def, store=None, mutable_section_name=None):
 
2451
        """Creates a stack of sections with an optional store for changes.
 
2452
 
 
2453
        :param sections_def: A list of Section or callables that returns an
 
2454
            iterable of Section. This defines the Sections for the Stack and
 
2455
            can be called repeatedly if needed.
 
2456
 
 
2457
        :param store: The optional Store where modifications will be
 
2458
            recorded. If none is specified, no modifications can be done.
 
2459
 
 
2460
        :param mutable_section_name: The name of the MutableSection where
 
2461
            changes are recorded. This requires the ``store`` parameter to be
 
2462
            specified.
 
2463
        """
 
2464
        self.sections_def = sections_def
 
2465
        self.store = store
 
2466
        self.mutable_section_name = mutable_section_name
 
2467
 
 
2468
    def get(self, name):
 
2469
        """Return the *first* option value found in the sections.
 
2470
 
 
2471
        This is where we guarantee that sections coming from Store are loaded
 
2472
        lazily: the loading is delayed until we need to either check that an
 
2473
        option exists or get its value, which in turn may require to discover
 
2474
        in which sections it can be defined. Both of these (section and option
 
2475
        existence) require loading the store (even partially).
 
2476
        """
 
2477
        # FIXME: No caching of options nor sections yet -- vila 20110503
 
2478
 
 
2479
        # Ensuring lazy loading is achieved by delaying section matching until
 
2480
        # it can be avoided anymore by using callables to describe (possibly
 
2481
        # empty) section lists.
 
2482
        for section_or_callable in self.sections_def:
 
2483
            # Each section can expand to multiple ones when a callable is used
 
2484
            if callable(section_or_callable):
 
2485
                sections = section_or_callable()
 
2486
            else:
 
2487
                sections = [section_or_callable]
 
2488
            for section in sections:
 
2489
                value = section.get(name)
 
2490
                if value is not None:
 
2491
                    return value
 
2492
        # No definition was found
 
2493
        return None
 
2494
 
 
2495
    def _get_mutable_section(self):
 
2496
        """Get the MutableSection for the Stack.
 
2497
 
 
2498
        This is where we guarantee that the mutable section is lazily loaded:
 
2499
        this means we won't load the corresponding store before setting a value
 
2500
        or deleting an option. In practice the store will often be loaded but
 
2501
        this allows catching some programming errors.
 
2502
        """
 
2503
        section = self.store.get_mutable_section(self.mutable_section_name)
 
2504
        return section
 
2505
 
 
2506
    def set(self, name, value):
 
2507
        """Set a new value for the option."""
 
2508
        section = self._get_mutable_section()
 
2509
        section.set(name, value)
 
2510
 
 
2511
    def remove(self, name):
 
2512
        """Remove an existing option."""
 
2513
        section = self._get_mutable_section()
 
2514
        section.remove(name)
 
2515
 
 
2516
    def __repr__(self):
 
2517
        # Mostly for debugging use
 
2518
        return "<config.%s(%s)>" % (self.__class__.__name__, id(self))
 
2519
 
 
2520
 
2097
2521
class cmd_config(commands.Command):
2098
2522
    __doc__ = """Display, set or remove a configuration option.
2099
2523