Christopher Lenz wrote:
Am 25.01.2006 um 14:28 schrieb Christian Boos:
I've synced the InterTrac branch with the latest trunk (r2828),
and now would like to integrate it on trunk.
Please tell me if you'd like to review it first or if I can go ahead.
The branch is here:
http://projects.edgewall.com/trac/browser/sandbox/intertrac?rev=2831
Can you please post an outline of the scope of the changes on this
branch?
No problem.
For a start, here's the early stage of the doc:
http://projects.edgewall.com/trac/browser/sandbox/intertrac/wiki-default/InterTrac?rev=2831
http://projects.edgewall.com/trac/browser/sandbox/intertrac/wiki-default/InterWiki?rev=2831
One can see the InterTrac thing as a more intimated kind of InterWiki:
not only do we know that an intertrac prefix corresponds to a remote site,
we know that it's a Trac site and we can take benefit of that, mostly by
being able to use the shorthand trac notation, like #T234 or [trac 2831].
There's even a more "intimate" and effective kind of intertrac support,
it's when the Trac server can have direct access to that other Trac
environment.
This happens when a server is running multiple projects, either by
using the TRAC_ENV_PARENT_DIR or because an explicit list
of environments is given (only tracd supports the latter).
The advantages of this advanced level of intertrac support are:
* no need to configure anything: those "sibling" environment
are known "out of the box"
* the links are richer, e.g. the title is set to the summary
for tickets, they are displayed as "missing" when the targets
are missing, etc.
Then, the changes themselves:
* trac/env.py:
self.siblings = {}
* trac/ticket/api.py:
wiki syntax for tickets support intertrac shorthand form
(i.e. #T234)
* trac/ticket/report.py:
wiki syntax for reports support intertrac shorthand form
(i.e. {trac 1} or {T1})
* trac/versioncontrol/web_ui/changeset.py:
wiki syntax for changesets support intertrac shorthand form
(i.e. [trac 1] or [T1])
* trac/wiki/formatter.py:
This contains most of the changes.
Where `_make_link` used to give up because it didn't know about
the link scheme, it nows try to create first an intertrac link,
then an interwiki link.
This file also contains the InterWikiMap component which
watches for changes in the InterMapTxt wiki page and builds
a mapping of InterWiki prefixes to (parameterized) URLs.
That component also provides an [[InterWiki]] macro which
can be used to display the known prefixes.
* trac/web/standalone.py:
tracd now supports TRAC_ENV_PARENT_DIR and does the setup
of sibling environments
* trac/web/modpython_frontend.py:
also does the setup of the sibling environments
* trac/web/main.py:
implements `setup_sibling_environments` function used by
the above files, which needed some refactoring in order
to be able to reuse the enumeration of available projects,
now given by `get_projects`.
... and for your reading pleasure, here's the diff against trunk :)
(wiki pages stripped)
-- Christian
There's also
Index: trac/env.py
===================================================================
--- trac/env.py (.../trunk) (revision 2831)
+++ trac/env.py (.../sandbox/intertrac) (revision 2831)
@@ -75,6 +75,7 @@
ComponentManager.__init__(self)
self.path = path
+ self.siblings = {}
self.load_config()
self.setup_log()
Index: trac/ticket/api.py
===================================================================
--- trac/ticket/api.py (.../trunk) (revision 2831)
+++ trac/ticket/api.py (.../sandbox/intertrac) (revision 2831)
@@ -17,7 +17,7 @@
from trac import util
from trac.core import *
from trac.perm import IPermissionRequestor
-from trac.wiki import IWikiSyntaxProvider
+from trac.wiki import IWikiSyntaxProvider, Formatter
from trac.Search import ISearchSource, query_to_sql, shorten_result
@@ -138,10 +138,18 @@
('ticket', self._format_link)]
def get_wiki_syntax(self):
- yield (r"!?(?<!&)#\d+", # #123 but not { (HTML entity)
- lambda x, y, z: self._format_link(x, 'ticket', y[1:], y))
+ yield (
+ # matches #... but not &#... (HTML entity)
+ r"!?(?<!&)#"
+ # optional intertrac shorthand #T... + digits
+ r"(?P<it_ticket>%s)?\d+" % Formatter.INTERTRAC_SCHEME,
+ lambda x, y, z: self._format_link(x, 'ticket', y[1:], y, z))
- def _format_link(self, formatter, ns, target, label):
+ def _format_link(self, formatter, ns, target, label, fullmatch=None):
+ intertrac = formatter.shorthand_intertrac_helper(ns, target, label,
+ fullmatch)
+ if intertrac:
+ return intertrac
cursor = formatter.db.cursor()
cursor.execute("SELECT summary,status FROM ticket WHERE id=%s",
(target,))
Index: trac/ticket/report.py
===================================================================
--- trac/ticket/report.py (.../trunk) (revision 2831)
+++ trac/ticket/report.py (.../sandbox/intertrac) (revision 2831)
@@ -23,7 +23,7 @@
from trac.perm import IPermissionRequestor
from trac.web import IRequestHandler
from trac.web.chrome import add_link, add_stylesheet, INavigationContributor
-from trac.wiki import wiki_to_html, IWikiSyntaxProvider
+from trac.wiki import wiki_to_html, IWikiSyntaxProvider, Formatter
dynvars_re = re.compile('\$([A-Z]+)')
@@ -499,9 +499,14 @@
yield ('report', self._format_link)
def get_wiki_syntax(self):
- yield (r"!?\{\d+\}", lambda x, y, z: self._format_link(x, 'report',
y[1:-1], y))
+ yield (r"!?\{(?P<it_report>%s\s*)?\d+\}" % Formatter.INTERTRAC_SCHEME,
+ lambda x, y, z: self._format_link(x, 'report', y[1:-1], y, z))
- def _format_link(self, formatter, ns, target, label):
+ def _format_link(self, formatter, ns, target, label, fullmatch=None):
+ intertrac = formatter.shorthand_intertrac_helper(ns, target, label,
+ fullmatch)
+ if intertrac:
+ return intertrac
report, args = target, ''
if '?' in target:
report, args = target.split('?')
Index: trac/__init__.py
===================================================================
--- trac/__init__.py (.../trunk) (revision 2831)
+++ trac/__init__.py (.../sandbox/intertrac) (revision 2831)
@@ -10,7 +10,7 @@
"""
__docformat__ = 'epytext en'
-__version__ = '0.10dev'
+__version__ = '0.10dev-intertrac'
__url__ = 'http://trac.edgewall.com/'
__copyright__ = '(C) 2003-2006 Edgewall Software'
__license__ = 'BSD'
Index: trac/versioncontrol/web_ui/changeset.py
===================================================================
--- trac/versioncontrol/web_ui/changeset.py (.../trunk) (revision 2831)
+++ trac/versioncontrol/web_ui/changeset.py (.../sandbox/intertrac)
(revision 2831)
@@ -34,7 +34,8 @@
from trac.versioncontrol.svn_authz import SubversionAuthorizer
from trac.web import IRequestHandler
from trac.web.chrome import INavigationContributor, add_link, add_stylesheet
-from trac.wiki import wiki_to_html, wiki_to_oneliner, IWikiSyntaxProvider
+from trac.wiki import wiki_to_html, wiki_to_oneliner, IWikiSyntaxProvider, \
+ Formatter
class DiffArgs(dict):
@@ -580,18 +581,28 @@
# IWikiSyntaxProvider methods
def get_wiki_syntax(self):
- yield (r"!?\[\d+(?:/[^\]]*)?\]|(?:\b|!)r\d+\b(?!:\d)",
- lambda x, y, z:
- self._format_changeset_link(x, 'changeset',
- y[0] == 'r' and y[1:] or y[1:-1],
- y, z))
+ yield (
+ # [...] form: start with optional intertrac: [T... or [trac ...
+ r"!?\[(?P<it_changeset>%s\s*)?" % Formatter.INTERTRAC_SCHEME +
+ # digits + optional path for the restricted changeset
+ r"\d+(?:/[^\]]*)?\]|"
+ # r... form: allow r1 but not r1:2 (handled by the log syntax)
+ r"(?:\b|!)r\d+\b(?!:\d)",
+ lambda x, y, z:
+ self._format_changeset_link(x, 'changeset',
+ y[0] == 'r' and y[1:] or y[1:-1],
+ y, z))
def get_link_resolvers(self):
yield ('changeset', self._format_changeset_link)
yield ('diff', self._format_diff_link)
def _format_changeset_link(self, formatter, ns, chgset, label,
- fullmatch=None):
+ fullmatch=None):
+ intertrac = formatter.shorthand_intertrac_helper(ns, chgset, label,
+ fullmatch)
+ if intertrac:
+ return intertrac
sep = chgset.find('/')
if sep > 0:
rev, path = chgset[:sep], chgset[sep:]
Index: trac/wiki/api.py
===================================================================
--- trac/wiki/api.py (.../trunk) (revision 2831)
+++ trac/wiki/api.py (.../sandbox/intertrac) (revision 2831)
@@ -198,7 +198,7 @@
def get_wiki_syntax(self):
ignore_missing = self.config.getbool('wiki', 'ignore_missing_pages')
yield (r"!?(?<!/)\b[A-Z][a-z]+(?:[A-Z][a-z]*[a-z/])+"
- "(?:#[A-Za-z0-9]+)?(?=\Z|\s|[.,;:!?\)}\]])",
+ "(?:#[A-Za-z0-9]+)?(?=:?\Z|:?\s|[.,;!?\)}\]])",
lambda x, y, z: self._format_link(x, 'wiki', y, y,
ignore_missing))
@@ -224,3 +224,4 @@
else:
return '<a class="wiki" href="%s">%s</a>' \
% (formatter.href.wiki(page) + anchor, label)
+
Index: trac/wiki/tests/wiki-tests.txt
===================================================================
--- trac/wiki/tests/wiki-tests.txt (.../trunk) (revision 2831)
+++ trac/wiki/tests/wiki-tests.txt (.../sandbox/intertrac) (revision 2831)
@@ -53,11 +53,13 @@
#1, {1}, #2
[1], r1
[1/README.txt]
+#12, [12], r12, {12}
------------------------------
<p>
<a class="missing ticket" href="/ticket/1" rel="nofollow">#1</a>, <a
class="report" href="/report/1">{1}</a>, <a class="missing ticket"
href="/ticket/2" rel="nofollow">#2</a>
<a class="missing changeset" href="/changeset/1" rel="nofollow">[1]</a>, <a
class="missing changeset" href="/changeset/1" rel="nofollow">r1</a>
<a class="missing changeset" href="/changeset/1/README.txt"
rel="nofollow">[1/README.txt]</a>
+<a class="missing ticket" href="/ticket/12" rel="nofollow">#12</a>, <a
class="missing changeset" href="/changeset/12" rel="nofollow">[12]</a>, <a
class="missing changeset" href="/changeset/12" rel="nofollow">r12</a>, <a
class="report" href="/report/12">{12}</a>
</p>
------------------------------
==============================
@@ -167,10 +169,10 @@
</p>
------------------------------
==============================
-CamelCase,CamelCase.CamelCase:CamelCase
+CamelCase,CamelCase.CamelCase: CamelCase
------------------------------
<p>
-<a class="missing wiki" href="/wiki/CamelCase"
rel="nofollow">CamelCase?</a>,<a class="missing wiki" href="/wiki/CamelCase"
rel="nofollow">CamelCase?</a>.CamelCase:CamelCase
+<a class="missing wiki" href="/wiki/CamelCase"
rel="nofollow">CamelCase?</a>,<a class="missing wiki" href="/wiki/CamelCase"
rel="nofollow">CamelCase?</a>.<a class="missing wiki" href="/wiki/CamelCase"
rel="nofollow">CamelCase?</a>: <a class="missing wiki" href="/wiki/CamelCase"
rel="nofollow">CamelCase?</a>
</p>
------------------------------
==============================
@@ -903,3 +905,78 @@
------------------------------
|| a ||
|| b ||
+==============================
+t:wiki:InterTrac
+trac:wiki:InterTrac
+[t:wiki:InterTrac intertrac]
+[trac:wiki:InterTrac intertrac]
+------------------------------
+<p>
+<a class="ext-link" href="http://projects.edgewall.com/trac/wiki/InterTrac"
title="wiki:InterTrac in Trac's Trac"><span
class="icon"></span>t:wiki:InterTrac</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/wiki/InterTrac"
title="wiki:InterTrac in Trac's Trac"><span
class="icon"></span>trac:wiki:InterTrac</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/wiki/InterTrac"
title="wiki:InterTrac in Trac's Trac"><span class="icon"></span>intertrac</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/wiki/InterTrac"
title="wiki:InterTrac in Trac's Trac"><span class="icon"></span>intertrac</a>
+</p>
+------------------------------
+==============================
+trac:ticket:2041
+[trac:ticket:2041 Trac #2041]
+#T2041
+#trac2041
+------------------------------
+<p>
+<a class="ext-link" href="http://projects.edgewall.com/trac/ticket/2041"
title="ticket:2041 in Trac's Trac"><span
class="icon"></span>trac:ticket:2041</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/ticket/2041"
title="ticket:2041 in Trac's Trac"><span class="icon"></span>Trac #2041</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/ticket/2041"
title="ticket:2041 in Trac's Trac"><span class="icon"></span>#T2041</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/ticket/2041"
title="ticket:2041 in Trac's Trac"><span class="icon"></span>#trac2041</a>
+</p>
+------------------------------
+==============================
+trac:changeset:2081
+[trac:changeset:2081 Trac r2081]
+[T2081]
+[trac2081]
+[trac 2081]
+------------------------------
+<p>
+<a class="ext-link" href="http://projects.edgewall.com/trac/changeset/2081"
title="changeset:2081 in Trac's Trac"><span
class="icon"></span>trac:changeset:2081</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/changeset/2081"
title="changeset:2081 in Trac's Trac"><span class="icon"></span>Trac r2081</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/changeset/2081"
title="changeset:2081 in Trac's Trac"><span class="icon"></span>[T2081]</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/changeset/2081"
title="changeset:2081 in Trac's Trac"><span class="icon"></span>[trac2081]</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/changeset/2081"
title="changeset:2081 in Trac's Trac"><span class="icon"></span>[trac 2081]</a>
+</p>
+------------------------------
+==============================
+trac:report:1
+[trac:report:1 Trac r1]
+{T1}
+{trac1}
+{trac 1}
+------------------------------
+<p>
+<a class="ext-link" href="http://projects.edgewall.com/trac/report/1"
title="report:1 in Trac's Trac"><span class="icon"></span>trac:report:1</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/report/1"
title="report:1 in Trac's Trac"><span class="icon"></span>Trac r1</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/report/1"
title="report:1 in Trac's Trac"><span class="icon"></span>{T1}</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/report/1"
title="report:1 in Trac's Trac"><span class="icon"></span>{trac1}</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/report/1"
title="report:1 in Trac's Trac"><span class="icon"></span>{trac 1}</a>
+</p>
+------------------------------
+==============================
+t:InterTrac
+trac:InterTrac
+[t:InterTrac intertrac]
+[trac:InterTrac intertrac]
+T:r2081
+T:#2041
+trac:#2041
+------------------------------
+<p>
+<a class="ext-link"
href="http://projects.edgewall.com/trac/search?q=InterTrac" title="InterTrac in
Trac's Trac"><span class="icon"></span>t:InterTrac</a>
+<a class="ext-link"
href="http://projects.edgewall.com/trac/search?q=InterTrac" title="InterTrac in
Trac's Trac"><span class="icon"></span>trac:InterTrac</a>
+<a class="ext-link"
href="http://projects.edgewall.com/trac/search?q=InterTrac" title="InterTrac in
Trac's Trac"><span class="icon"></span>intertrac</a>
+<a class="ext-link"
href="http://projects.edgewall.com/trac/search?q=InterTrac" title="InterTrac in
Trac's Trac"><span class="icon"></span>intertrac</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/search?q=r2081"
title="r2081 in Trac's Trac"><span class="icon"></span>T:r2081</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/search?q=%232041"
title="#2041 in Trac's Trac"><span class="icon"></span>T:#2041</a>
+<a class="ext-link" href="http://projects.edgewall.com/trac/search?q=%232041"
title="#2041 in Trac's Trac"><span class="icon"></span>trac:#2041</a>
+</p>
+------------------------------
Index: trac/wiki/tests/formatter.py
===================================================================
--- trac/wiki/tests/formatter.py (.../trunk) (revision 2831)
+++ trac/wiki/tests/formatter.py (.../sandbox/intertrac) (revision 2831)
@@ -54,8 +54,13 @@
self.config = Configuration(None)
self.href = Href('/')
self.abs_href = Href('http://www.example.com/')
- self._wiki_pages = {}
self.path = ''
+ # -- intertrac support
+ self.siblings = {}
+ self.config.set('intertrac', 'trac.title', "Trac's Trac")
+ self.config.set('intertrac', 'trac.url',
+ "http://projects.edgewall.com/trac")
+ self.config.set('intertrac', 't', 'trac')
def component_activated(self, component):
component.env = self
component.config = self.config
Index: trac/wiki/formatter.py
===================================================================
--- trac/wiki/formatter.py (.../trunk) (revision 2831)
+++ trac/wiki/formatter.py (.../sandbox/intertrac) (revision 2831)
@@ -27,10 +27,11 @@
from StringIO import StringIO
from trac import util
+from trac.core import *
from trac.mimeview import *
-from trac.wiki.api import WikiSystem
+from trac.wiki.api import WikiSystem, IWikiChangeListener, IWikiMacroProvider
-__all__ = ['wiki_to_html', 'wiki_to_oneliner', 'wiki_to_outline']
+__all__ = ['wiki_to_html', 'wiki_to_oneliner', 'wiki_to_outline', 'Formatter' ]
def system_message(msg, text):
@@ -131,6 +132,7 @@
INLINE_TOKEN = "`"
LINK_SCHEME = r"[\w.+-]+" # as per RFC 2396
+ INTERTRAC_SCHEME = r"[a-zA-Z.+-]+?" # no digits (support for shorthand
links)
QUOTED_STRING = r"'[^']+'|\"[^\"]+\""
@@ -289,14 +291,66 @@
return self._make_link(ns, target, match, label)
def _make_link(self, ns, target, match, label):
+ # check first for an alias defined in trac.ini
+ ns = self.env.config.get('intertrac', ns.upper(), ns)
if ns in self.link_resolvers:
return self.link_resolvers[ns](self, ns, target,
util.escape(label, False))
elif target.startswith('//') or ns == "mailto":
return self._make_ext_link(ns+':'+target, label)
else:
- return util.escape(match)
+ return self._make_intertrac_link(ns, target, label) or \
+ self._make_interwiki_link(ns, target, label) or \
+ match
+ def _make_intertrac_link(self, ns, target, label):
+ if self.env.siblings.has_key(ns):
+ sibling = self.env.siblings[ns]
+ # The following is currently needed because env.href is set
+ # in trac.web.main.dispatch_request: for an environment which
+ # has not yet been queried by a client, .href is not defined.
+ if not hasattr(sibling, 'href'):
+ from trac.web.href import Href
+ def xchg_base(base):
+ return '/'.join(base.split('/')[:-1] + [ns])
+ sibling.href = Href(xchg_base(self.env.href.base))
+ sibling.abs_href = Href(xchg_base(self.env.abs_href.base))
+ # EOKludge
+ ref = wiki_to_oneliner(target, sibling)
+ return ref.replace('>%s' % target, '>%s' % label)
+ url = self.env.config.get('intertrac', ns.upper()+'.url')
+ if url:
+ name = self.env.config.get('intertrac', ns.upper()+'.title',
+ 'Trac project %s' % ns)
+ sep = target.find(':')
+ if sep != -1:
+ url = '%s/%s/%s' % (url, target[:sep], target[sep+1:])
+ else:
+ url = '%s/search?q=%s' % (url, urllib.quote_plus(target))
+ return self._make_ext_link(url, label, '%s in %s' % (target, name))
+ else:
+ return None
+
+ def shorthand_intertrac_helper(self, ns, target, label, fullmatch):
+ if fullmatch: # short form
+ it_group = fullmatch.group('it_%s' % ns)
+ if it_group:
+ alias = it_group.strip()
+ intertrac = self.env.config.get('intertrac', alias.upper(),
+ alias)
+ target = '%s:%s' % (ns, target[len(it_group):])
+ return self._make_intertrac_link(intertrac, target, label) or \
+ label
+ return None
+
+ def _make_interwiki_link(self, ns, target, label):
+ interwiki = InterWikiMap(self.env)
+ if interwiki.has_key(ns):
+ url, title = interwiki.url(ns, target)
+ return self._make_ext_link(url, label, '%s in %s' % (target,
title))
+ else:
+ return None
+
def _make_ext_link(self, url, text, title=''):
url = util.escape(url)
text, title = util.escape(text), util.escape(title)
@@ -754,3 +808,98 @@
OutlineFormatter(env, absurls, db).format(wikitext, out, max_depth,
min_depth)
return util.Markup(out.getvalue())
+
+
+# -- InterWiki support
+
+class InterWikiMap(Component):
+
+ implements(IWikiChangeListener, IWikiMacroProvider)
+
+ _page_name = 'InterMapTxt'
+ _interwiki_re = re.compile(r"(\w+)[ \t]+([^ \t]+)(?:[ \t]+#(.*))?",
+ re.UNICODE)
+ _argspec_re = re.compile(r"\$\d")
+
+ def __init__(self):
+ self._interwiki_map = None
+ # This dictionary maps upper-cased namespaces
+ # to (namespace, prefix, title) values
+
+ def has_key(self, ns):
+ if not self._interwiki_map:
+ self._update()
+ return self._interwiki_map.has_key(ns.upper())
+
+ def url(self, ns, target):
+ ns, url, title = self._interwiki_map[ns.upper()]
+ args = target.split(':')
+ def setarg(match):
+ num = int(match.group()[1:])
+ return 0 < num <= len(args) and args[num-1] or ''
+ url_with_args = re.sub(InterWikiMap._argspec_re, setarg, url)
+ if url_with_args == url:
+ return url + target, title
+ else:
+ return url_with_args, title
+
+ # IWikiChangeListener methods
+
+ def wiki_page_added(self, page):
+ if page.name == InterWikiMap._page_name:
+ self._update()
+
+ def wiki_page_changed(self, page, version, t, comment, author, ipnr):
+ if page.name == InterWikiMap._page_name:
+ self._update()
+
+ def wiki_page_deleted(self, page):
+ if page.name == InterWikiMap._page_name:
+ self._interwiki_map.clear()
+
+ def _update(self):
+ from trac.wiki.model import WikiPage
+ self._interwiki_map = {}
+ content = WikiPage(self.env, InterWikiMap._page_name).text
+ in_map = False
+ for line in content.split('\n'):
+ if in_map:
+ if line.startswith('----'):
+ in_map = False
+ else:
+ m = re.match(InterWikiMap._interwiki_re, line)
+ if m:
+ prefix, url, title = m.groups()
+ url = url.strip()
+ title = title and title.strip() or prefix
+ self._interwiki_map[prefix.upper()] = (prefix, url,
+ title)
+ elif line.startswith('----'):
+ in_map = True
+
+ # IWikiMacroProvider
+
+ def get_macros(self):
+ yield 'InterWiki'
+
+ def get_macro_description(self, name):
+ return "Provide a description list for the known InterWiki prefixes."
+
+ def render_macro(self, req, name, content):
+ if not self._interwiki_map:
+ self._update()
+ keys = self._interwiki_map.keys()
+ keys.sort()
+ buf = StringIO()
+ buf.write('<table><tr><th>Prefix</th><td>Site</td></tr>\n')
+ for k in keys:
+ prefix, url, title = self._interwiki_map[k]
+ shortened_url = url and url[:-1]
+ description = title == prefix and shortened_url or title
+ buf.write('<tr>\n' +
+ ('<td><a href="%sRecentChanges">%s</a></td>'
+ '<td><a href="%s">%s</a></td>\n') \
+ % (url, prefix, shortened_url, description) +
+ '</tr>\n')
+ buf.write('</table>\n')
+ return buf.getvalue()
Index: trac/web/standalone.py
===================================================================
--- trac/web/standalone.py (.../trunk) (revision 2831)
+++ trac/web/standalone.py (.../sandbox/intertrac) (revision 2831)
@@ -24,6 +24,7 @@
from trac.web.api import Request
from trac.web.cgi_frontend import TracFieldStorage
from trac.web.main import dispatch_request, get_environment, \
+ setup_sibling_environments, \
send_pretty_error, send_project_index
from trac.util import md5crypt
@@ -201,35 +202,15 @@
else:
self.http_host = '%s:%d' % (self.server_name, self.server_port)
- self.env_parent_dir = env_parent_dir and {'TRAC_ENV_PARENT_DIR':
- env_parent_dir}
+ self.env_paths = env_paths
self.auths = auths
+ self.options = os.environ.copy()
+ if env_parent_dir:
+ self.options['TRAC_ENV_PARENT_DIR'] = env_parent_dir
+ self.projects = setup_sibling_environments(self.options,
self.env_paths)
- self.projects = {}
- for env_path in env_paths:
- # Remove trailing slashes
- while env_path and not os.path.split(env_path)[1]:
- env_path = os.path.split(env_path)[0]
- project = os.path.split(env_path)[1]
- if self.projects.has_key(project):
- print >>sys.stderr, 'Warning: Ignoring project "%s" since ' \
- 'it conflicts with project "%s"' \
- % (env_path, self.projects[project])
- else:
- self.projects[project] = env_path
-
- def get_env_opts(self, project=None):
- if self.env_parent_dir:
- opts = self.env_parent_dir.items()
- else:
- opts = [('TRAC_ENV', self.projects[project])]
- return dict(opts + os.environ.items())
-
def send_project_index(self, req):
- if self.env_parent_dir:
- return send_project_index(req, self.get_env_opts())
- else:
- return send_project_index(req, os.environ, self.projects.values())
+ return send_project_index(req, self.options, self.env_paths)
class TracHTTPRequestHandler(BaseHTTPRequestHandler):
@@ -275,14 +256,12 @@
path_info = urllib.unquote(path_info)
req = TracHTTPRequest(self, project_name, query_string)
- try:
- opts = self.server.get_env_opts(project_name)
- except KeyError:
- # unrecognized project
- self.server.send_project_index(req)
- return
+ env = None
+ if project_name in self.server.projects:
+ options = self.server.options.copy()
+ options['TRAC_ENV'] = self.server.projects[project_name]
+ env = get_environment(req, options)
- env = get_environment(req, opts)
if not env:
self.server.send_project_index(req)
return
Index: trac/web/modpython_frontend.py
===================================================================
--- trac/web/modpython_frontend.py (.../trunk) (revision 2831)
+++ trac/web/modpython_frontend.py (.../sandbox/intertrac) (revision 2831)
@@ -31,6 +31,7 @@
from trac.util import http_date
from trac.web.api import Request, RequestDone
from trac.web.main import dispatch_request, get_environment, \
+ setup_sibling_environments, \
send_pretty_error, send_project_index
@@ -196,6 +197,7 @@
('TracEnvParentDir', 'TRAC_ENV_PARENT_DIR'),
('TracEnvIndexTemplate', 'TRAC_ENV_INDEX_TEMPLATE'),
('TracTemplateVars', 'TRAC_TEMPLATE_VARS'))
+ setup_sibling_environments(project_opts)
env = get_environment(mpr, project_opts)
if not env:
send_project_index(mpr, project_opts)
Index: trac/web/main.py
===================================================================
--- trac/web/main.py (.../trunk) (revision 2831)
+++ trac/web/main.py (.../sandbox/intertrac) (revision 2831)
@@ -17,6 +17,8 @@
# Matthew Good <[EMAIL PROTECTED]>
import os
+import sys
+import dircache
from trac.core import *
from trac.env import open_environment
@@ -276,27 +278,20 @@
<body><h1>Available Projects</h1><ul><?cs
each:project = projects ?><li><?cs
if:project.href ?>
- <a href="<?cs var:project.href ?>" title="<?cs var:project.description ?>">
+ <a href="<?cs var:project.href ?>" title="<?cs var:project.description ?>">
<?cs var:project.name ?></a><?cs
else ?>
- <small><?cs var:project.name ?>: <em>Error</em> <br />
- (<?cs var:project.description ?>)</small><?cs
+ <?cs var:project.name ?>
+ <small><em><?cs var:project.description ?></em></small><?cs
/if ?>
</li><?cs
/each ?></ul></body>
</html>''')
- if not env_paths and 'TRAC_ENV_PARENT_DIR' in options:
- dir = options['TRAC_ENV_PARENT_DIR']
- env_paths = [os.path.join(dir, f) for f in os.listdir(dir)]
-
href = Href(req.idx_location)
try:
projects = []
- for env_path in env_paths:
- if not os.path.isdir(env_path):
- continue
- env_dir, project = os.path.split(env_path)
+ for project, env_path in get_projects(options, env_paths).items():
try:
env = _open_environment(env_path)
proj = {
@@ -322,7 +317,7 @@
elif 'TRAC_ENV_PARENT_DIR' in options:
env_parent_dir = options['TRAC_ENV_PARENT_DIR']
env_name = req.cgi_location.split('/')[-1]
- env_path = os.path.join(env_parent_dir, env_name)
+ env_path = os.path.join(os.path.normpath(env_parent_dir), env_name)
if not len(env_name) or not os.path.exists(env_path):
return None
else:
@@ -333,3 +328,53 @@
'the Trac environment(s).'
return _open_environment(env_path, threaded)
+
+
+def get_projects(options, env_paths, warn=False):
+ """Retrieve canonical project to environment path mapping.
+
+ The environments may not be all valid environments, though,
+ but they are serious candidates...
+ """
+ env_paths = env_paths or []
+ if 'TRAC_ENV_PARENT_DIR' in options:
+ env_parent_dir = os.path.normpath(options['TRAC_ENV_PARENT_DIR'])
+ if env_parent_dir:
+ paths = dircache.listdir(env_parent_dir)[:]
+ dircache.annotate(env_parent_dir, paths)
+ env_paths += [os.path.join(env_parent_dir, project) \
+ for project in paths if project[-1] == '/']
+ projects = {}
+ for env_path in env_paths:
+ env_path = os.path.normpath(env_path)
+ if not os.path.isdir(env_path):
+ continue
+ project = os.path.split(env_path)[1]
+ if project in projects:
+ if warn:
+ print >>sys.stderr,('Warning: Ignoring project "%s" since ' \
+ 'it conflicts with project "%s"' \
+ % (env_path, projects[project]))
+ else:
+ projects[project] = env_path
+ return projects
+
+def setup_sibling_environments(options, env_paths=None):
+ """Make each of the given environment know all the others.
+
+ The environments can be specified by a list of `env_paths`
+ and also from the `TRAC_ENV_PARENT_DIR` options.
+ Return a mapping of project names to environment paths.
+ """
+ siblings = {}
+ projects = get_projects(options, env_paths, warn=True)
+ options = options.copy()
+ for project, env_path in projects.items():
+ options['TRAC_ENV'] = env_path
+ try:
+ siblings[project] = get_environment(None, options)
+ except (TracError, IOError):
+ pass
+ for env in siblings.values():
+ env.siblings = siblings
+ return projects
_______________________________________________
Trac-dev mailing list
[email protected]
http://lists.edgewall.com/mailman/listinfo/trac-dev