Edgewall Software

Ticket #1847: vc-refactoring_for_trac-0.9.patch

File vc-refactoring_for_trac-0.9.patch, 28.8 KB (added by cboos, 3 years ago)

The vc-refactoring changes , as of r2472 + r2490, which can be applied on Trac 0.9. Should handle the creation of new Trac environments correctly!

  • htdocs/css/trac.css

     
    257257.wikipage { padding-left: 18px } 
    258258.wikipage h1, .wikipage h2, .wikipage h3 { margin-left: -18px } 
    259259 
    260 a.missing:link, a.missing:visited { background: #fafaf0; color: #998 } 
     260a.missing:link, a.missing:visited, span.missing { background: #fafaf0; color: #998 } 
    261261a.missing:hover { color: #000; } 
    262262a.closed:link, a.closed:visited { text-decoration: line-through } 
    263263 
  • htdocs/css/browser.css

     
    22* html #prefs { width: 34em } /* Set width only for IE */ 
    33#prefs fieldset label { display: block } 
    44#prefs .buttons { margin-top: -1.6em } 
     5#prefs .button { margin-left: 1.6em } 
    56#prefs .choice { margin-top: -0.6em } 
     7#prefs .rev { align: right } 
    68 
    79#legend { clear: right; } 
    810 
     
    2426 color: #888; 
    2527 white-space: nowrap; 
    2628} 
     29#dirlist td.rev {  
     30 font-family: courier;   
     31 letter-spacing: -0.02em; 
     32 font-size: 90%; 
     33 text-align: right; 
     34} 
    2735#dirlist td.size {   
    2836 color: #888; 
    2937 white-space: nowrap; 
     
    5967#chglist td.old_path { font-style: italic } 
    6068#chglist td.date { font-size: 85%; vertical-align: top; padding-top: 0.55em; white-space: nowrap } 
    6169#chglist td.author { font-size: 85%; vertical-align: top; padding-top: 0.55em } 
    62 #chglist td.rev, #chglist td.chgset { text-align: right } 
     70#chglist td.rev, #chglist td.chgset {  
     71 font-family: courier;   
     72 letter-spacing: -0.02em; 
     73 font-size: 90%; 
     74 text-align: right;  
     75} 
    6376#chglist td.rev a, #chglist td.chgset a { border-bottom: none } 
    6477#chglist td.summary { width: 100%; font-size: 85%; vertical-align: middle; white-space: nowrap } 
    6578#chglist td.summary * { margin-top: 0; margin-bottom: 0 } 
  • trac/env.py

     
    2222from trac.config import Configuration 
    2323from trac.core import Component, ComponentManager, implements, Interface, \ 
    2424                      ExtensionPoint, TracError 
     25from trac.versioncontrol import ScmBackendManager 
    2526 
    2627__all__ = ['Environment', 'IEnvironmentSetupParticipant', 'open_environment'] 
    2728 
     
    120121                    and component_name.startswith(pattern[:-1]): 
    121122                return enabled 
    122123 
     124        # versioncontrol components are enabled if the repository is configured 
     125        if component_name.startswith('trac.versioncontrol'): 
     126            return self.config.get('trac', 'repository_dir') != '' 
     127 
    123128        # By default, all components in the trac package are enabled 
    124129        return component_name.startswith('trac.') 
    125130 
     
    146151        """Return the version control repository configured for this 
    147152        environment. 
    148153         
    149         The repository is wrapped in a `CachedRepository`. 
    150          
    151154        @param authname: user name for authorization 
    152155        """ 
    153         from trac.versioncontrol.cache import CachedRepository 
    154         from trac.versioncontrol.svn_authz import SubversionAuthorizer 
    155         from trac.versioncontrol.svn_fs import SubversionRepository 
    156156        repos_dir = self.config.get('trac', 'repository_dir') 
    157157        if not repos_dir: 
    158158            raise EnvironmentError, 'Path to repository not configured' 
    159         authz = None 
    160         if authname: 
    161             authz = SubversionAuthorizer(self, authname) 
    162         repos = SubversionRepository(repos_dir, authz, self.log) 
    163         return CachedRepository(self.get_db_cnx(), repos, authz, self.log) 
     159        return ScmBackendManager(self).get_repository(repos_dir, authname) 
    164160 
    165161    def create(self, db_str=None): 
    166162        """Create the basic directory structure of the environment, initialize 
  • trac/db_default.py

     
    445445                      'trac.ticket.roadmap', 'trac.ticket.web_ui', 
    446446                      'trac.Timeline', 
    447447                      'trac.versioncontrol.web_ui', 
     448                      'trac.versioncontrol.svn_fs', 
    448449                      'trac.wiki.macros', 'trac.wiki.web_ui', 
    449450                      'trac.web.auth') 
  • trac/versioncontrol/api.py

     
    1616 
    1717from __future__ import generators 
    1818from trac.perm import PermissionError 
     19from trac.core import * 
    1920 
     21### Source Configuration Management interface and manager 
     22 
     23class IScmBackend(Interface): 
     24    """SCM backend for Trac""" 
     25 
     26    def identifiers(self): 
     27        """SCM string prefixes that are supported by the backend, 
     28        and their relative priorities. 
     29 
     30        Highest number is highest priority. 
     31        """ 
     32 
     33    def repository(self, scheme, args, authname): 
     34        """Get a Repository object. 
     35 
     36        `scheme` is the scheme that was used to select this backend, 
     37        `args` is the remaining specification for the repository and 
     38        `authname` is the user name, for authentication purpose. 
     39        """ 
     40 
     41    def init_repository(self, **params): # TBD 
     42        """Initialize a repository""" 
     43 
     44 
     45class ScmBackendManager(Component): 
     46    """TODO: share some code with the DatabaseBackendManager""" 
     47 
     48    backends = ExtensionPoint(IScmBackend) 
     49 
     50    def __init__(self): 
     51        self._backend_map = None 
     52 
     53    def get_repository(self, repos_str, authname): 
     54        if ':' in repos_str and len(repos_str) > 2 and repos_str[1] != ':': 
     55            scheme, args = repos_str.split(':', 1) 
     56        else: 
     57            scheme, args = 'svn', repos_str 
     58        backend = self._get_backend(scheme) 
     59        return backend.repository(scheme, args, authname) 
     60 
     61    def _get_backend(self, scheme): 
     62        if not self._backend_map: 
     63            self._backend_map = {} 
     64            for backend in self.backends: 
     65                for ident, prio in backend.identifiers(): 
     66                    if ident in self._backend_map: 
     67                        highest = self._backend_map[ident][1] 
     68                    else: 
     69                        highest = 0 
     70                    if prio > highest: 
     71                        self._backend_map[ident] = (backend, prio) 
     72        if not scheme in self._backend_map: 
     73            raise TracError, 'Unsupported SCM "%s"' % scheme 
     74        return self._backend_map[scheme][0] 
     75 
     76 
     77### Abstract classes for the backends 
     78 
    2079class Repository(object): 
    2180    """ 
    2281    Base class for a repository provided by a version control system. 
     
    4099        """ 
    41100        raise NotImplementedError 
    42101 
     102    def get_changesets(self, start, stop): 
     103        """ 
     104        Generate Changeset belonging to the given time period (start, stop). 
     105        """ 
     106        rev = self.youngest_rev 
     107        while rev: 
     108            if self.authz.has_permission_for_changeset(rev): 
     109                chgset = self.get_changeset(rev) 
     110                if chgset.date < start: 
     111                    return 
     112                if chgset.date < stop: 
     113                    yield chgset 
     114            rev = self.previous_rev(rev) 
     115 
    43116    def has_node(self, path, rev): 
    44117        """ 
    45118        Tell if there's a node at the specified (path,rev) combination. 
     
    122195        'None' is a valid revision value and represents the youngest revision. 
    123196        """ 
    124197        return NotImplementedError 
     198 
     199    def short_rev(self, rev): 
     200        """ 
     201        Return a compact representation of a revision in the repos. 
     202        """ 
     203        return self.normalize_rev(rev) 
    125204         
    126205 
    127206class Node(object): 
  • trac/versioncontrol/tests/svn_fs.py

     
    100100        self.assertEqual(11, self.repos.normalize_rev(11)) 
    101101 
    102102    def test_rev_navigation(self): 
    103         self.assertEqual(0, self.repos.oldest_rev) 
     103        self.assertEqual(1, self.repos.oldest_rev) 
    104104        self.assertEqual(None, self.repos.previous_rev(0)) 
    105         self.assertEqual(0, self.repos.previous_rev(1)) 
     105        self.assertEqual(None, self.repos.previous_rev(1)) 
    106106        self.assertEqual(12, self.repos.youngest_rev) 
    107107        self.assertEqual(6, self.repos.next_rev(5)) 
    108108        self.assertEqual(7, self.repos.next_rev(6)) 
  • trac/versioncontrol/svn_fs.py

     
    1717from __future__ import generators 
    1818 
    1919from trac.util import TracError 
    20 from trac.versioncontrol import Changeset, Node, Repository 
     20from trac.versioncontrol import Changeset, Node, Repository, IScmBackend 
     21from trac.versioncontrol.cache import CachedRepository 
     22from trac.versioncontrol.svn_authz import SubversionAuthorizer 
     23from trac.versioncontrol.web_ui import ChangesetModule, BrowserModule 
     24from trac.core import * 
    2125 
    2226import os.path 
    2327import time 
    2428import weakref 
    2529import posixpath 
    2630 
    27 from svn import fs, repos, core, delta 
     31try: 
     32    from svn import fs, repos, core, delta 
     33    has_subversion = True 
     34except ImportError: 
     35    has_subversion = False 
     36    class dummy_svn(object): 
     37        svn_node_dir = 1 
     38        svn_node_file = 2 
     39        def apr_pool_destroy(): pass 
     40        def apr_terminate(): pass 
     41        def apr_pool_clear(): pass 
     42    core = dummy_svn() 
    2843 
    2944_kindmap = {core.svn_node_dir: Node.DIRECTORY, 
    3045            core.svn_node_file: Node.FILE} 
    3146 
     47 
     48### Components 
     49 
     50class SvnFsBackend(Component): 
     51 
     52    implements(IScmBackend) 
     53 
     54    def identifiers(self): 
     55        global has_subversion 
     56        if has_subversion: 
     57            yield ("direct-svn-fs", 8) 
     58            yield ("svn-fs", 4) 
     59            yield ("svn", 2) 
     60 
     61    def repository(self, scheme, args, authname): 
     62        """Return a `SubversionRepository`. 
     63 
     64        The repository is generally wrapped in a `CachedRepository`, 
     65        unless `direct-svn-fs` is the specified scheme. 
     66        """ 
     67        authz = None 
     68        if authname: 
     69            authz = SubversionAuthorizer(self.env, authname) 
     70        repos = SubversionRepository(args, authz, self.log) 
     71        if scheme == 'direct-svn-fs': 
     72            return repos 
     73        else: 
     74            db = self.env.get_db_cnx() 
     75            return CachedRepository(db, repos, authz, self.log) 
     76 
     77 
     78class SvnFsBrowserModule(BrowserModule): 
     79    pass 
     80 
     81class SvnFsChangesetModule(ChangesetModule): 
     82    pass 
     83 
     84 
     85### Helpers 
     86 
    3287application_pool = None 
    3388 
    3489     
     
    58113    """Remove leading "/", except for the root""" 
    59114    return path and path.strip('/') or '/' 
    60115 
    61 def _scoped_path(scope, fullpath): 
     116def _path_within_scope(scope, fullpath): 
    62117    """Remove the leading scope from repository paths""" 
    63118    if fullpath: 
    64119        if scope == '/': 
     
    168223                del self._weakref 
    169224 
    170225# Initialize application-level pool 
    171 Pool() 
     226if has_subversion: 
     227    Pool() 
    172228 
    173  
    174229class SubversionRepository(Repository): 
    175230    """ 
    176231    Repository implementation based on the svn.fs API. 
     
    185240        self.pool = Pool() 
    186241         
    187242        # Remove any trailing slash or else subversion might abort 
    188         if not os.path.split(path)[1]: 
    189             path = os.path.split(path)[0] 
     243        path = os.path.normpath(path).replace('\\', '/') 
    190244        self.path = repos.svn_repos_find_root_path(path, self.pool()) 
    191245        if self.path is None: 
    192246            raise TracError, \ 
     
    208262            self.scope = '/' 
    209263        self.log.debug("Opening subversion file-system at %s with scope %s" \ 
    210264                       % (self.path, self.scope)) 
     265        self.youngest = None 
     266        self.oldest = None 
    211267 
    212         self.rev = fs.youngest_rev(self.fs_ptr, self.pool()) 
    213  
    214         self.history = None 
    215         if self.scope != '/': 
    216             self.history = [] 
    217             for path,rev in _get_history(self.scope[1:], self.authz, 
    218                                          self.fs_ptr, self.pool, 0, self.rev): 
    219                 self.history.append(rev) 
    220  
    221268    def __del__(self): 
    222269        self.close() 
    223270 
     
    246293        self.log.debug("Closing subversion file-system at %s" % self.path) 
    247294        self.repos = None 
    248295        self.fs_ptr = None 
    249         self.rev = None 
    250296        self.pool = None 
    251297 
    252298    def get_changeset(self, rev): 
     
    263309        return SubversionNode(path, rev, self.authz, self.scope, self.fs_ptr, 
    264310                              self.pool) 
    265311 
     312    def _history(self, path, start, end, limit=None): 
     313        scoped_path = self.scope[1:]+path 
     314        return _get_history(scoped_path, self.authz, self.fs_ptr, self.pool, 
     315                            start, end, limit) 
     316 
    266317    def get_oldest_rev(self): 
    267         rev = 0 
    268         if self.scope == '/': 
    269             return rev 
    270         return self.history[-1] 
     318        if self.oldest is None: 
     319            self.oldest = 1 
     320            if self.scope != '/': 
     321                self.oldest = self.next_rev(0) 
     322        return self.oldest 
    271323 
    272324    def get_youngest_rev(self): 
    273         rev = self.rev 
    274         if self.scope == '/': 
    275             return rev 
    276         return self.history[0] 
     325        if not self.youngest: 
     326            self.youngest = fs.youngest_rev(self.fs_ptr, self.pool()) 
     327            if self.scope != '/': 
     328                for path, rev in self._history('', 0, self.youngest, limit=1): 
     329                    self.youngest = rev 
     330        return self.youngest 
    277331 
    278332    def previous_rev(self, rev): 
    279         rev = int(rev) 
    280         if rev == 0: 
    281             return None 
    282         if self.scope == '/': 
    283             return rev - 1 
    284         idx = self.history.index(rev) 
    285         if idx + 1 < len(self.history): 
    286             return self.history[idx + 1] 
     333        rev = self.normalize_rev(rev) 
     334        if rev > 1: # don't use oldest here, as it's too expensive 
     335            try: 
     336                for path, prev in self._history('', 0, rev-1, limit=1): 
     337                    return prev 
     338            except SystemError: 
     339                pass 
    287340        return None 
    288341 
    289342    def next_rev(self, rev): 
    290         rev = int(rev) 
    291         if rev == self.rev: 
    292             return None 
    293         if self.scope == '/': 
    294             return rev + 1 
    295         if rev == 0: 
    296             return self.oldest_rev 
    297         idx = self.history.index(rev) 
    298         if idx > 0: 
    299             return self.history[idx - 1] 
     343        rev = self.normalize_rev(rev) 
     344        next = rev + 1 
     345        youngest = self.youngest_rev 
     346        while next <= youngest: 
     347            try: 
     348                for path, next in self._history('', rev+1, next, limit=1): 
     349                    return next 
     350                next += 1 
     351            except SystemError: # i.e. "null arg to internal routine" 
     352                return next # a 'delete' event is also interesting...  
    300353        return None 
    301354 
    302355    def rev_older_than(self, rev1, rev2): 
     
    328381                older = None # 'older' is the currently examined history tuple 
    329382                for p, r in _get_history(self.scope + path, self.authz, 
    330383                                         self.fs_ptr, subpool, 0, rev, limit): 
    331                     older = (_scoped_path(self.scope, p), r, Changeset.ADD) 
     384                    older = (_path_within_scope(self.scope, p), r, 
     385                             Changeset.ADD) 
    332386                    rev = self.previous_rev(r) 
    333387                    if newer: 
    334388                        if older[0] == path: 
     
    405459        pool = Pool(self.pool) 
    406460        for path, rev in _get_history(self.scoped_path, self.authz, self.fs_ptr, 
    407461                                      pool, 0, self._requested_rev, limit): 
    408             scoped_path = _scoped_path(self.scope, path) 
    409             if rev > 0 and scoped_path: 
    410                 older = (scoped_path, rev, Changeset.ADD) 
     462            path = _path_within_scope(self.scope, path) 
     463            if rev > 0 and path: 
     464                older = (path, rev, Changeset.ADD) 
    411465                if newer: 
    412466                    change = newer[0] == older[0] and Changeset.EDIT or \ 
    413467                             Changeset.COPY 
     
    472526                continue 
    473527            if not path.startswith(self.scope[1:]): 
    474528                continue 
    475             base_path = _scoped_path(self.scope, change.base_path) 
     529            base_path = _path_within_scope(self.scope, change.base_path) 
    476530            action = '' 
    477531            if not change.path: 
    478532                action = Changeset.DELETE 
  • trac/versioncontrol/web_ui/changeset.py

     
    3535 
    3636class ChangesetModule(Component): 
    3737 
     38    abstract = True 
     39 
    3840    implements(INavigationContributor, IPermissionRequestor, IRequestHandler, 
    3941               ITimelineEventProvider, IWikiSyntaxProvider, ISearchSource) 
    4042 
     
    106108                                             'changeset_show_files')) 
    107109            db = self.env.get_db_cnx() 
    108110            repos = self.env.get_repository() 
    109             authzperm = SubversionAuthorizer(self.env, req.authname) 
    110             rev = repos.youngest_rev 
    111             while rev: 
    112                 if not authzperm.has_permission_for_changeset(rev): 
    113                     rev = repos.previous_rev(rev) 
    114                     continue 
     111            for chgset in repos.get_changesets(start, stop): 
     112                message = chgset.message or '--' 
     113                if format == 'rss': 
     114                    title = 'Changeset <em>[%s]</em>: %s' \ 
     115                            % (util.escape(chgset.rev), 
     116                               util.escape(util.shorten_line(message))) 
     117                    href = self.env.abs_href.changeset(chgset.rev) 
     118                    message = wiki_to_html(message, self.env, db, 
     119                                           absurls=True) 
     120                else: 
     121                    title = 'Changeset <em>[%s]</em> by %s' \ 
     122                            % (util.escape(chgset.rev), 
     123                               util.escape(chgset.author)) 
     124                    href = self.env.href.changeset(chgset.rev) 
     125                    message = wiki_to_oneliner(message, self.env, db, 
     126                                               shorten=True) 
     127                if show_files: 
     128                    files = [] 
     129                    for chg in chgset.get_changes(): 
     130                        if show_files > 0 and len(files) >= show_files: 
     131                            files.append('...') 
     132                            break 
     133                        files.append('<span class="%s">%s</span>' 
     134                                     % (chg[2], util.escape(chg[0]))) 
     135                    message = '<span class="changes">' + ', '.join(files) +\ 
     136                              '</span>: ' + message 
     137                yield 'changeset', href, title, chgset.date, chgset.author,\ 
     138                      message 
    115139 
    116                 chgset = repos.get_changeset(rev) 
    117                 if chgset.date < start: 
    118                     return 
    119                 if chgset.date < stop: 
    120                     message = chgset.message or '--' 
    121                     if format == 'rss': 
    122                         title = 'Changeset <em>[%s]</em>: %s' \ 
    123                                 % (util.escape(chgset.rev), 
    124                                    util.escape(util.shorten_line(message))) 
    125                         href = self.env.abs_href.changeset(chgset.rev) 
    126                         message = wiki_to_html(message, self.env, db, 
    127                                                absurls=True) 
    128                     else: 
    129                         title = 'Changeset <em>[%s]</em> by %s' \ 
    130                                 % (util.escape(chgset.rev), 
    131                                    util.escape(chgset.author)) 
    132                         href = self.env.href.changeset(chgset.rev) 
    133                         message = wiki_to_oneliner(message, self.env, db, 
    134                                                    shorten=True) 
    135                     if show_files: 
    136                         files = [] 
    137                         for chg in chgset.get_changes(): 
    138                             if show_files > 0 and len(files) >= show_files: 
    139                                 files.append('...') 
    140                                 break 
    141                             files.append('<span class="%s">%s</span>' 
    142                                          % (chg[2], util.escape(chg[0]))) 
    143                         message = '<span class="changes">' + ', '.join(files) +\ 
    144                                   '</span>: ' + message 
    145                     yield 'changeset', href, title, chgset.date, chgset.author,\ 
    146                           message 
    147                 rev = repos.previous_rev(rev) 
    148  
    149140    # Internal methods 
    150141 
    151142    def _render_html(self, req, repos, chgset, diff_options): 
     
    169160        youngest_rev = repos.youngest_rev 
    170161        if str(chgset.rev) != str(youngest_rev): 
    171162            next_rev = repos.next_rev(chgset.rev) 
    172             add_link(req, 'next', self.env.href.changeset(next_rev), 
    173                      'Changeset %s' % next_rev) 
     163            if next_rev: 
     164                add_link(req, 'next', self.env.href.changeset(next_rev), 
     165                         'Changeset %s' % next_rev) 
    174166            add_link(req, 'last', self.env.href.changeset(youngest_rev), 
    175167                     'Changeset %s' % youngest_rev) 
    176168 
     
    180172            info = {'change': change} 
    181173            if base_path: 
    182174                info['path.old'] = base_path 
    183                 info['rev.old'] = base_rev 
     175                info['rev.old'] = repos.short_rev(base_rev) 
    184176                info['browser_href.old'] = self.env.href.browser(base_path, 
    185177                                                                 rev=base_rev) 
    186178            if path: 
    187179                info['path.new'] = path 
    188                 info['rev.new'] = chgset.rev 
     180                info['rev.new'] = repos.short_rev(chgset.rev) 
    189181                info['browser_href.new'] = self.env.href.browser(path, 
    190182                                                                 rev=chgset.rev) 
    191183            if change in (Changeset.COPY, Changeset.EDIT, Changeset.MOVE): 
  • trac/versioncontrol/web_ui/browser.py

     
    5050 
    5151class BrowserModule(Component): 
    5252 
     53    abstract = True 
     54 
    5355    implements(INavigationContributor, IPermissionRequestor, IRequestHandler, 
    5456               IWikiSyntaxProvider) 
    5557 
  • trac/scripts/admin.py

     
    541541        prompt = 'Database connection string [%s]> ' % ddb 
    542542        returnvals.append(raw_input(prompt).strip()  or ddb) 
    543543        print 
    544         print ' Please specify the absolute path to the project Subversion repository.' 
    545         print ' Repository must be local, and trac-admin requires read+write' 
    546         print ' permission to initialize the Trac database.' 
     544        print ' Please specify the absolute path to the project SCM repository,' 
     545        print ' or leave it blank to use Trac without a repository.' 
    547546        print 
    548         drp = '/var/svn/test' 
    549         prompt = 'Path to repository [%s]> ' % drp 
    550         returnvals.append(raw_input(prompt).strip()  or drp) 
     547        print ' Also, the repository can be specified later.' 
     548        print  
     549        drp = 'svn:/path/to/repos/optional_scope_within_repos' 
     550        prompt = 'Path to repository, e.g. "%s"> ' % drp 
     551        returnvals.append(raw_input(prompt).strip()) 
    551552        print 
    552553        print ' Please enter location of Trac page templates.' 
    553554        print ' Default is the location of the site-wide templates installed with Trac.' 
     
    605606            self._do_wiki_load(default_dir('wiki'), cursor) 
    606607            cnx.commit() 
    607608 
    608             print ' Indexing repository' 
    609             repos = self.__env.get_repository() 
    610             repos.sync() 
     609            if repository_dir: 
     610                try: 
     611                    repos = self.__env.get_repository() 
     612                    if repos: 
     613                        print ' Indexing repository' 
     614                        repos.sync() 
     615                except util.TracError, e: 
     616                    print>>sys.stderr, """ 
     617Warning: %s 
    611618 
     619You should install the support libraries or the plugin needed 
     620for the SCM corresponding to: 
     621 
     622  %s 
     623""" % (str(e), repository_dir) 
    612624        except Exception, e: 
    613625            print 'Failed to initialize environment.', e 
    614626            traceback.print_exc() 
  • trac/loader.py

     
    4545    def enable_modules(egg_path, modules): 
    4646        """Automatically enable any components provided by plugins loaded from 
    4747        the environment plugins directory.""" 
    48         if os.path.dirname(egg_path) == os.path.realpath(plugins_dir): 
     48        plugins_path = os.path.realpath(plugins_dir) 
     49        if os.name == 'nt': 
     50            plugins_path = plugins_path.lower() 
     51        if os.path.dirname(egg_path) == plugins_path: 
    4952            for module in modules: 
    5053                env.config.setdefault('components', module + '.*', 'enabled') 
    5154 
  • templates/browser.cs