~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/config.py

(vila) Provide a config section matcher respecting the file order. (Vincent
 Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
3114
3114
class IniFileStore(Store):
3115
3115
    """A config Store using ConfigObj for storage.
3116
3116
 
3117
 
    :ivar transport: The transport object where the config file is located.
3118
 
 
3119
 
    :ivar file_name: The config file basename in the transport directory.
3120
 
 
3121
3117
    :ivar _config_obj: Private member to hold the ConfigObj instance used to
3122
3118
        serialize/deserialize the config file.
3123
3119
    """
3253
3249
            value = self._config_obj._unquote(value)
3254
3250
        return value
3255
3251
 
 
3252
    def external_url(self):
 
3253
        # Since an IniFileStore can be used without a file (at least in tests),
 
3254
        # it's better to provide something than raising a NotImplementedError.
 
3255
        # All daughter classes are supposed to provide an implementation
 
3256
        # anyway.
 
3257
        return 'In-Process Store, no URL'
3256
3258
 
3257
3259
class TransportIniFileStore(IniFileStore):
3258
3260
    """IniFileStore that loads files from a transport.
 
3261
 
 
3262
    :ivar transport: The transport object where the config file is located.
 
3263
 
 
3264
    :ivar file_name: The config file basename in the transport directory.
3259
3265
    """
3260
3266
 
3261
3267
    def __init__(self, transport, file_name):
3435
3441
 
3436
3442
class LocationSection(Section):
3437
3443
 
3438
 
    def __init__(self, section, length, extra_path):
 
3444
    def __init__(self, section, extra_path):
3439
3445
        super(LocationSection, self).__init__(section.id, section.options)
3440
 
        self.length = length
3441
3446
        self.extra_path = extra_path
3442
3447
        self.locals = {'relpath': extra_path,
3443
3448
                       'basename': urlutils.basename(extra_path)}
3465
3470
        return value
3466
3471
 
3467
3472
 
 
3473
class StartingPathMatcher(SectionMatcher):
 
3474
    """Select sections for a given location respecting the Store order."""
 
3475
 
 
3476
    # FIXME: Both local paths and urls can be used for section names as well as
 
3477
    # ``location`` to stay consistent with ``LocationMatcher`` which itself
 
3478
    # inherited the fuzziness from the previous ``LocationConfig``
 
3479
    # implementation. We probably need to revisit which encoding is allowed for
 
3480
    # both ``location`` and section names and how we normalize
 
3481
    # them. http://pad.lv/85479, http://pad.lv/437009 and http://359320 are
 
3482
    # related too. -- vila 2012-01-04
 
3483
 
 
3484
    def __init__(self, store, location):
 
3485
        super(StartingPathMatcher, self).__init__(store)
 
3486
        if location.startswith('file://'):
 
3487
            location = urlutils.local_path_from_url(location)
 
3488
        self.location = location
 
3489
 
 
3490
    def get_sections(self):
 
3491
        """Get all sections matching ``location`` in the store.
 
3492
 
 
3493
        The most generic sections are described first in the store, then more
 
3494
        specific ones can be provided for reduced scopes.
 
3495
 
 
3496
        The returned section are therefore returned in the reversed order so
 
3497
        the most specific ones can be found first.
 
3498
        """
 
3499
        location_parts = self.location.rstrip('/').split('/')
 
3500
        store = self.store
 
3501
        sections = []
 
3502
        # Later sections are more specific, they should be returned first
 
3503
        for _, section in reversed(list(store.get_sections())):
 
3504
            if section.id is None:
 
3505
                # The no-name section is always included if present
 
3506
                yield store, LocationSection(section, self.location)
 
3507
                continue
 
3508
            section_path = section.id
 
3509
            if section_path.startswith('file://'):
 
3510
                # the location is already a local path or URL, convert the
 
3511
                # section id to the same format
 
3512
                section_path = urlutils.local_path_from_url(section_path)
 
3513
            if (self.location.startswith(section_path)
 
3514
                or fnmatch.fnmatch(self.location, section_path)):
 
3515
                section_parts = section_path.rstrip('/').split('/')
 
3516
                extra_path = '/'.join(location_parts[len(section_parts):])
 
3517
                yield store, LocationSection(section, extra_path)
 
3518
 
 
3519
 
3468
3520
class LocationMatcher(SectionMatcher):
3469
3521
 
3470
3522
    def __init__(self, store, location):
3494
3546
        matching_sections = []
3495
3547
        if no_name_section is not None:
3496
3548
            matching_sections.append(
3497
 
                LocationSection(no_name_section, 0, self.location))
 
3549
                (0, LocationSection(no_name_section, self.location)))
3498
3550
        for section_id, extra_path, length in filtered_sections:
3499
3551
            # a section id is unique for a given store so it's safe to take the
3500
3552
            # first matching section while iterating. Also, all filtered
3504
3556
                section = iter_all_sections.next()
3505
3557
                if section_id == section.id:
3506
3558
                    matching_sections.append(
3507
 
                        LocationSection(section, length, extra_path))
 
3559
                        (length, LocationSection(section, extra_path)))
3508
3560
                    break
3509
3561
        return matching_sections
3510
3562
 
3513
3565
        matching_sections = self._get_matching_sections()
3514
3566
        # We want the longest (aka more specific) locations first
3515
3567
        sections = sorted(matching_sections,
3516
 
                          key=lambda section: (section.length, section.id),
 
3568
                          key=lambda (length, section): (length, section.id),
3517
3569
                          reverse=True)
3518
3570
        # Sections mentioning 'ignore_parents' restrict the selection
3519
 
        for section in sections:
 
3571
        for _, section in sections:
3520
3572
            # FIXME: We really want to use as_bool below -- vila 2011-04-07
3521
3573
            ignore = section.get('ignore_parents', None)
3522
3574
            if ignore is not None: