=== htdocs/stable-bct-trac_banner.png
==================================================================
Cannot display: file marked as a binary type.

Property changes on: htdocs/stable-bct-trac_banner.png
___________________________________________________________________
Name: svn:executable
 +*
Name: svn:mime-type
 +image/x-png

=== htdocs/css/trac.css
==================================================================
--- htdocs/css/trac.css   (/mirrors/trac/trunk)   (revision 1120)
+++ htdocs/css/trac.css   (/local/trunk/trac)   (revision 1120)
@@ -240,6 +240,8 @@
 a.missing:link,a.missing:visited { background: #fafaf0; color: #998 }
 a.missing:hover { color: #000; }
 
+a.external:link,a.external:visited { color: #00b }
+
 #content.wiki { line-height: 140% }
 .wikitoolbar {
  border: solid #d7d7d7;
@@ -345,6 +347,26 @@
 #content.error pre, div.system-message pre { margin-left: 1em; overflow: auto }
 div.system-message p { margin: 0; }
 div.system-message p.system-message-title { font-weight: bold; }
+div.sidebar, div.contents { 
+ background: #f7f7f0;
+ border: 1px outset #998;
+ float: right;
+ font-size: 90%;
+ padding: .8em;
+ margin: 0 1em 1em;
+ width: 26em;margin: 0; 
+}
+p.sidebar-title, p.topic-title { font-weight: bold; }
+div.note { 
+ background: #d7d7f0;
+ border: 1px outset #998;
+ float: left
+ font-size: 90%;
+ padding: .8em;
+ margin: 0 1em 1em;
+ width: 26em;
+}
+p.admonition-title { font-weight: bold;text-align: center }
 
 /* Styles for search word highlighting */
 .searchword0 { background: #ff9 }
=== htdocs/css/browser.css
==================================================================
--- htdocs/css/browser.css   (/mirrors/trac/trunk)   (revision 1120)
+++ htdocs/css/browser.css   (/local/trunk/trac)   (revision 1120)
@@ -40,6 +40,20 @@
 #chglist td.rev a, #chglist td.chgset a { border-bottom: none }
 #chglist td.summary { width: 100% }
 
+/* Styles for the diff forms inside the log table */
+#chglist input {
+ font-size: 10px;
+ margin: 0;
+ padding: 0;
+ text-align: center;
+ vertical-align: middle;
+}
+#chglist form {
+ margin: 0;
+ padding: 0;
+ white-space: nowrap;
+}
+
 /* Styles for the revision info in the file module */
 #info {
  background: #f7f7f0;
=== htdocs/css/ticket.css
==================================================================
--- htdocs/css/ticket.css   (/mirrors/trac/trunk)   (revision 1120)
+++ htdocs/css/ticket.css   (/local/trunk/trac)   (revision 1120)
@@ -21,6 +21,8 @@
 #ticket th { color: #996; font-weight: normal; text-align: left }
 #ticket hr { color: #dd9; border-color: #dd9; border-width: 1px 0 0; height: 1px; margin: .5em 0 0 }
 
+#ticket .category { font-size: 200% } 
+
 #attachments { border: 1px outset #996; padding: 1em }
 #attachments .attachments { list-style: square; margin-left: 2em; padding: 0 }
 
=== htdocs/css/timeline.css
==================================================================
--- htdocs/css/timeline.css   (/mirrors/trac/trunk)   (revision 1120)
+++ htdocs/css/timeline.css   (/local/trunk/trac)   (revision 1120)
@@ -27,13 +27,17 @@
 dt .time { color: #777 }
 dd { font-size: 80%; margin: 0 0 .5em 5em; padding: 0 }
 
+
 /* Apply icon background-image twice to avoid hover-flicker in IE/Win */
 dt.changeset, dt.changeset a { background-image: url(../changeset.png) !important }
 dt.newticket, dt.newticket a { background-image: url(../newticket.png) !important }
 dt.closedticket, dt.closedticket a { background-image: url(../closedticket.png) !important }
 dt.wiki, dt.wiki a { background-image: url(../wiki.png) !important }
 dt.milestone, dt.milestone a { background-image: url(../milestone.png) !important }
+dt.ticketcomment a { font-size: 80%; color: #555 }
+dd.ticketcomment { font-size: 70%; color: #555; margin: 0 0 .5em 5em; padding: 0 }
 
+
 .diff-unmod { color: #000 }
 .diff-rem { color: #e00 }
 .diff-add { color: #2e2 }
=== htdocs/exp-svk-trac_banner.png
==================================================================
Cannot display: file marked as a binary type.

Property changes on: htdocs/exp-svk-trac_banner.png
___________________________________________________________________
Name: svn:mime-type
 +image/x-png

=== htdocs/exp-bct-trac_banner.png
==================================================================
Cannot display: file marked as a binary type.

Property changes on: htdocs/exp-bct-trac_banner.png
___________________________________________________________________
Name: svn:executable
 +*
Name: svn:mime-type
 +image/x-png

=== htdocs/stable-svk-trac_banner.png
==================================================================
Cannot display: file marked as a binary type.

Property changes on: htdocs/stable-svk-trac_banner.png
___________________________________________________________________
Name: svn:mime-type
 +image/x-png

=== scripts/trac-admin
==================================================================
--- scripts/trac-admin   (/mirrors/trac/trunk)   (revision 1120)
+++ scripts/trac-admin   (/local/trunk/trac)   (revision 1120)
@@ -706,6 +706,23 @@
                 self._do_wiki_import(filename, page, cursor)
 
 
+    ## (Ticket) Category
+    _help_category = [('category list', 'Show possible ticket categories'),
+                       ('category add <value>', 'Add a category value option'),
+                       ('category change <value> <newvalue>',
+                        'Change a category value'),
+                       ('category remove <value>', 'Remove category value')]
+
+    def complete_category (self, text, line, begidx, endidx):
+        if begidx == 16:
+            comp = self.get_enum_list ('category')
+        elif begidx < 15:
+            comp = ['list','add','change','remove']
+        return self.word_complete(text, comp)
+
+    def do_category(self, line):
+        self._do_enum('category', line)
+
     ## (Ticket) Priority
     _help_priority = [('priority list', 'Show possible ticket priorities'),
                        ('priority add <value>', 'Add a priority value option'),
=== README.tracd
==================================================================
--- README.tracd   (/mirrors/trac/trunk)   (revision 1120)
+++ README.tracd   (/local/trunk/trac)   (revision 1120)
@@ -1,9 +1,11 @@
+========================
 Trac in stand-alone mode
 ========================
+
 Trac 0.7 introduces among many other important features, the capability to run
 Trac as a stand-alone server (daemon), without a web server.
 
-*** NOTE:  THIS IS STILL AN EXPERIMENTAL FEATURE ***
+.. NOTE::  THIS IS STILL AN EXPERIMENTAL FEATURE 
 
 As of writing, tracd is still experimental, but we encourage testing it and
 providing feedback so we can improve it. Tracd supports all features of
@@ -12,19 +14,29 @@
 
 Running tracd
 -------------
-tracd [options] <database> [database] ...
+
+tracd [options] <environment> [environment2 ...]
 Options:
+  
+  -a, --auth <project,htdigest_file,realm>        Use Digest Authentication
+  -p, --port <port>                        Port number to use (default: 80)
+  -b, --hostname <hostname>                     IP to bind to (default: '')
 
-    -a --auth [project],[htdigest_file],[realm]
-    -p --port [port]\t\tPort number to use (default: 80)
-    -b --hostname [hostname]\tIP to bind to (default: '')
 
 
-Example:
+Example 1, without authentication::
 
   $ tracd -p 9090 /var/trac/myproject
 
+Example 2, with authentication::
 
+  $ TRAC_ENV=/var/trac/myproject
+  $ tracd -a `basename $TRAC_ENV`,`dirname $TRAC_ENV`/passwd.digest,REALM \
+      -p 9090 $TRAC_ENV
+
+The file htdigest can be generated using the Apache2 tool htdigest.
+
+
 Feedback and bug reports
 ------------------------
 Please provide feedback on tracd using the issue tracker or the mailing list.
=== trac/core.py
==================================================================
--- trac/core.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/core.py   (/local/trunk/trac)   (revision 1120)
@@ -46,7 +46,7 @@
 #    name           (module_name, class_name, requires_svn)
     'log'         : ('Log', 'Log', 1),
     'file'        : ('File', 'File', 1),
-    'wiki'        : ('Wiki', 'WikiModule', 0),
+    'wiki'        : ('Wiki', 'WikiModule', 1), # support Svn backend for Wiki pages
     'about_trac'  : ('About', 'About', 0),
     'search'      : ('Search', 'Search', 0),
     'report'      : ('Report', 'Report', 0),
@@ -199,6 +199,8 @@
     pass
 
 def populate_hdf(hdf, env, db, req):
+    sql_to_hdf(db, "SELECT name FROM enum WHERE type='category' "
+               "ORDER BY value", hdf, 'enums.category')
     sql_to_hdf(db, "SELECT name FROM enum WHERE type='priority' "
                "ORDER BY value", hdf, 'enums.priority')
     sql_to_hdf(db, "SELECT name FROM enum WHERE type='severity' "
=== trac/db_default.py
==================================================================
--- trac/db_default.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/db_default.py   (/local/trunk/trac)   (revision 1120)
@@ -21,7 +21,7 @@
 
 
 # Database version identifier. Used for automatic upgrades.
-db_version = 7
+db_version = 8
 
 def __mkreports(reps):
     """Utility function used to create report data in same syntax as the
=== trac/Query.py
==================================================================
--- trac/Query.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/Query.py   (/local/trunk/trac)   (revision 1120)
@@ -59,6 +59,7 @@
             results.append({
                 'id': id,
                 'href': self.env.href.ticket(id),
+                'category': row['category'],
                 'summary': util.escape(row['summary'] or '(no summary)'),
                 'status': row['status'] or '',
                 'component': row['component'] or '',
@@ -78,6 +79,8 @@
         if self.args.has_key('search'):
             self.req.redirect(self.env.href.query(constraints, order, desc,
                                                   action='view'))
+        if self.args.has_key('clear'):
+            self.req.redirect(self.env.href.query())
 
         action = self.args.get('action')
         if not action and not constraints:
@@ -91,6 +94,7 @@
 
     def _render_editor(self, constraints, order, desc):
         self.req.hdf.setValue('title', 'Custom Query')
+        self.req.hdf.setValue('query.clear_href', self.env.href.query(action='edit'))
         util.add_to_hdf(constraints, self.req.hdf, 'query.constraints')
         self.req.hdf.setValue('query.order', order)
         if desc: self.req.hdf.setValue('query.desc', '1')
@@ -112,6 +116,8 @@
                 del constraints[field]
 
         cursor = self.db.cursor()
+        add_options('category', constraints, 'query.options.', cursor,
+                    "SELECT name FROM enum WHERE type='category' ORDER BY value")
         add_options('status', constraints, 'query.options.', cursor,
                     "SELECT name FROM enum WHERE type='status' ORDER BY value")
         add_options('resolution', constraints, 'query.options.', cursor,
@@ -129,7 +135,7 @@
 
         custom_fields = get_custom_fields(self.env)
         for custom in custom_fields:
-            if custom['type'] == 'select' or custom['type'] == 'radio':
+            if custom['type'] in ['select', 'radio', 'multi']:
                 check = constraints.has_key(custom['name'])
                 options = filter(None, custom['options'])
                 for i in range(len(options)):
@@ -146,7 +152,7 @@
 
         # FIXME: the user should be able to configure which columns should
         # be displayed
-        headers = [ 'id', 'summary', 'status', 'component', 'owner' ]
+        headers = [ 'category', 'id', 'summary', 'status', 'component', 'owner' ]
         cols = headers
         if not 'priority' in cols:
             cols.append('priority')
@@ -176,7 +182,7 @@
                       "(id=%s.ticket AND %s.name='%s')"
                       % (k, k, k, k))
 
-        for col in [c for c in ['status', 'resolution', 'priority', 'severity']
+        for col in [c for c in ['category', 'status', 'resolution', 'priority', 'severity']
                     if c in cols]:
             sql.append(" INNER JOIN (SELECT name AS %s_name, value AS %s_value " \
                                    "FROM enum WHERE type='%s')" \
@@ -194,7 +200,7 @@
         if clauses:
             sql.append(" WHERE " + " AND ".join(clauses))
 
-        if order in ['status', 'resolution', 'priority', 'severity']:
+        if order in ['category', 'status', 'resolution', 'priority', 'severity']:
             sql.append(" ORDER BY %s_value" % order)
         else:
             sql.append(" ORDER BY " + order)
=== trac/Timeline.py
==================================================================
--- trac/Timeline.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/Timeline.py   (/local/trunk/trac)   (revision 1120)
@@ -34,11 +34,12 @@
     template_name = 'timeline.cs'
     template_rss_name = 'timeline_rss.cs'
 
-    def get_info (self, start, stop, maxrows, tickets,
+    def get_info (self, start, stop, maxrows, tickets, ticket_comments,
                   changeset, wiki, milestone):
         cursor = self.db.cursor ()
 
         tickets = tickets and self.perm.has_permission(perm.TICKET_VIEW)
+        ticket_comments = ticket_comments and self.perm.has_permission(perm.TICKET_VIEW)
         changeset = changeset and self.perm.has_permission(perm.CHANGESET_VIEW)
         wiki = wiki and self.perm.has_permission(perm.WIKI_VIEW)
         milestone = milestone and self.perm.has_permission(perm.MILESTONE_VIEW)
@@ -52,27 +53,31 @@
         REOPENED_TICKET = 4
         WIKI = 5
         MILESTONE = 6
+        TICKET_COMMENT = 7
 
         q = []
         if changeset:
             q.append("SELECT time, rev AS idata, '' AS tdata, 1 AS type, "
-                     " message, author "
+                     " message, author, '' AS category "
                      "FROM revision WHERE time>=%s AND time<=%s" %
                      (start, stop))
         if tickets:
             q.append("SELECT time, id AS idata, '' AS tdata, 2 AS type, "
-                     "summary AS message, reporter AS author "
+                     " summary AS message, reporter AS author, category "
                      "FROM ticket WHERE time>=%s AND time<=%s" %
                      (start, stop))
-            q.append("SELECT time, ticket AS idata, '' AS tdata, 4 AS type, "
-                     "'' AS message, author "
-                     "FROM ticket_change WHERE field='status' "
-                     "AND newvalue='reopened' AND time>=%s AND time<=%s" %
+            q.append("SELECT t1.time, t1.ticket AS idata, '' AS tdata, 4 AS type, "
+                     " '' AS message, t1.author, t0.category AS category "
+                     "FROM ticket_change t1 "
+                     " LEFT JOIN ticket t0 ON t0.id = t1.ticket "
+                     "WHERE t1.field='status' "
+                     "AND t1.newvalue='reopened' AND t1.time>=%s AND t1.time<=%s" %
                      (start, stop))
             q.append("SELECT t1.time AS time, t1.ticket AS idata,"
                      "       t2.newvalue AS tdata, 3 AS type,"
-                     "       t3.newvalue AS message, t1.author AS author"
+                     "       t3.newvalue AS message, t1.author AS author, t0.category AS category"
                      " FROM ticket_change t1"
+                     "   LEFT JOIN ticket t0 ON t0.id = t1.ticket"
                      "   INNER JOIN ticket_change t2 ON t1.ticket = t2.ticket"
                      "     AND t1.time = t2.time"
                      "   LEFT OUTER JOIN ticket_change t3 ON t1.time = t3.time"
@@ -82,15 +87,23 @@
                      "   AND t1.time >= %s AND t1.time <= %s" % (start,stop))
         if wiki:
             q.append("SELECT time, -1 AS idata, name AS tdata, 5 AS type, "
-                     "comment AS message, author "
+                     "comment AS message, author, '' AS category "
                         "FROM wiki WHERE time>=%s AND time<=%s" %
                      (start, stop))
         if milestone:
             q.append("SELECT time, -1 AS idata, '' AS tdata, 6 AS type, "
-                     "name AS message, '' AS author " 
+                     "name AS message, '' AS author, '' AS category " 
                      "FROM milestone WHERE time>=%s AND time<=%s" %
                      (start, stop))
 
+        if ticket_comments:
+            q.append("SELECT t1.time AS time, t1.ticket AS idata, '' AS tdata, 7 AS type, "
+                     "t1.newvalue AS message, t1.author AS author, t0.category AS category "
+                     "FROM ticket_change t1"
+                     "   LEFT JOIN ticket t0 ON t0.id = t1.ticket "
+                     "WHERE t1.field = 'comment' "
+                     "AND t1.time>=%s AND t1.time<=%s" % (start, stop))
+
         q_str = string.join(q, ' UNION ALL ')
         q_str += ' ORDER BY time DESC'
         if maxrows:
@@ -100,6 +113,7 @@
 
         # Make the data more HDF-friendly
         info = []
+        tickets = {}
         while 1:
             row = cursor.fetchone()
             if not row:
@@ -113,6 +127,7 @@
                     'tdata': row['tdata'],
                     'type': int(row['type']),
                     'message': row['message'] or '',
+                    'category': row['category'],
                     'author': util.escape(row['author'] or 'anonymous')
                     }
 
@@ -168,8 +183,15 @@
             elif item['type'] == MILESTONE:
                 item['href'] = self.env.href.milestone(item['message'])
                 item['message'] = util.escape(item['message'])
-            else:               # TICKET
+            else:               # all the TICKET types
                 item['href'] = self.env.href.ticket(item['idata'])
+                if item['type'] == TICKET_COMMENT:
+                # The following would be ok if there would be a cost effective way
+                # to get that change number...
+                # item['href'] += '#change_%d' % item['change']
+                    pass
+                else:
+                    tickets[(item['idata'], item['time'])] = 1
                 msg = item['message']
                 item['shortmsg'] = util.escape(util.shorten_line(msg))
                 item['message'] = wiki_to_oneliner(
@@ -187,7 +209,13 @@
             item['message.rss'] = util.escape(item['message'] or '')
 
             info.append(item)
-        return info
+        # Ok, the following line is maybe not Python 2.1 friendly, I don't know...
+        # return [ i for i in info if i['type'] != 7 or not tickets.has_key((i['idata'], i['time'])) ]
+        info2 = []
+        for item in info:
+            if item['type'] != 7 or not tickets.has_key((item['idata'], item['time'])):
+                info2.append(item)
+        return info2
 
     def render (self):
         self.perm.assert_permission(perm.TIMELINE_VIEW)
@@ -221,6 +249,7 @@
 
         wiki = self.args.has_key('wiki') 
         ticket = self.args.has_key('ticket')
+        ticket_comments = self.args.has_key('ticket_comments')
         changeset = self.args.has_key('changeset')
         milestone = self.args.has_key('milestone')
         if not (wiki or ticket or changeset or milestone):
@@ -230,12 +259,14 @@
             self.req.hdf.setValue('timeline.wiki', 'checked')
         if ticket:
             self.req.hdf.setValue('timeline.ticket', 'checked')
+        if ticket_comments:
+            self.req.hdf.setValue('timeline.ticket_comments', 'checked')
         if changeset:
             self.req.hdf.setValue('timeline.changeset', 'checked')
         if milestone:
             self.req.hdf.setValue('timeline.milestone', 'checked')
 
-        info = self.get_info (start, stop, maxrows, ticket,
+        info = self.get_info (start, stop, maxrows, ticket, ticket_comments, 
                               changeset, wiki, milestone)
         util.add_dictlist_to_hdf(info, self.req.hdf, 'timeline.items')
         self.req.hdf.setValue('title', 'Timeline')
=== trac/tests/wiki-tests.txt
==================================================================
--- trac/tests/wiki-tests.txt   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/tests/wiki-tests.txt   (/local/trunk/trac)   (revision 1120)
@@ -127,6 +127,36 @@
 ------------------------------
 <h2>Heading with trailing white-space</h2>
 ==============================
+[[HelloWorld(hej hopp)]]
+------------------------------
+<p>
+<pre class="wiki">hej hopp</pre>
+</p>
+==============================
+[[HelloWorld(hej hopp) ]] # This shouldnt executed as macro since it contain whitespace between ) and ]
+------------------------------
+<p>
+[[HelloWorld(hej hopp) ]] # This shouldnt executed as macro since it contain whitespace between ) and ]
+</p>
+==============================
+[[HelloWorld(hej hopp))]] # Extra right brace and still executed
+------------------------------
+<p>
+<pre class="wiki">hej hopp)</pre> # Extra right brace and still executed
+</p>
+==============================
+[[HelloWorld(hej hopp)]] [[HelloWorld(hej hopp2)]] # Test non greedy match
+------------------------------
+<p>
+<pre class="wiki">hej hopp</pre> <pre class="wiki">hej hopp2</pre> # Test non greedy match
+</p>
+==============================
+Inline [[html(<B> Test </B>)]] text
+------------------------------
+<p>
+Inline <B> Test </B> text
+</p>
+==============================
 A0B1, ST62T53C6, IR32V1H000
 ------------------------------
 <p>
=== trac/tests/wiki.py
==================================================================
--- trac/tests/wiki.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/tests/wiki.py   (/local/trunk/trac)   (revision 1120)
@@ -4,6 +4,7 @@
 
 from Wiki import Formatter
 
+
 class WikiTestCase(unittest.TestCase):
     def __init__(self, input, correct):
         unittest.TestCase.__init__(self, 'test')
@@ -13,10 +14,12 @@
     def test(self):
         """Testing WikiFormatter"""
         import Href
+        import Mimeview
         class Environment:
             def __init__(self):
                 self.href = Href.Href('/')
                 self._wiki_pages = {}
+                self.mimeview = Mimeview.Mimeview(self)
         class Cursor:
             def execute(self, *kwargs): pass
             def fetchone(self): return []
@@ -28,7 +31,7 @@
         out = StringIO.StringIO()
         Formatter(None, Environment(), Connection()).format(self.input, out)
         if out.getvalue() != self.correct:
-            print "'%s' != '%s'" % (out.getvalue(), self.correct)
+            print "\n'%s' != \n'%s'" % (out.getvalue(), self.correct)
             assert self.correct == out.getvalue()
 
 def suite():
=== trac/util.py
==================================================================
--- trac/util.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/util.py   (/local/trunk/trac)   (revision 1120)
@@ -70,6 +70,17 @@
                     .replace('>', '&gt;') \
                     .replace('"', '&#34;')
 
+def unescape(text):
+    """Reverses Escapes &, <, > and \""""
+    if not text:
+        return ''
+    if type(text) is StringType:
+        text = text.replace('&#34;', '"') \
+               .replace('&gt;', '>') \
+               .replace('&lt;', '<') \
+               .replace('&amp;', '&') 
+    return text
+
 def get_first_line(text, maxlen):
     """
     returns the first line of text. If the line is longer then
=== trac/Log.py
==================================================================
--- trac/Log.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/Log.py   (/local/trunk/trac)   (revision 1120)
@@ -53,6 +53,7 @@
             'log.raw'  : util.escape(log),
             'log'      : wiki_to_oneliner(util.shorten_line(util.wiki_escape_newline(log)), self.req.hdf, self.env,self.db),
             'shortlog' : util.escape(shortlog),
+            'original_file_href': self.env.href.browser(self.path, rev),
             'file_href': self.env.href.browser(self.path, rev),
             'changeset_href': self.env.href.changeset(rev)
             }
@@ -71,13 +72,25 @@
         # Loop through all revisions and update the path
         # after each tag/branch/copy/rename.
         path = self.path
+        seq = len(self.log_info)
         for item in self.log_info:
+            item['seq'] = seq
+            seq -= 1
             item['file_href'] = self.env.href.browser(path, item['rev'])
+            item['intermediate_file_href'] = self.env.href.browser(path, item['rev'])
             if self.branch_info.has_key(item['rev']):
                 for info in self.branch_info[item['rev']]:
                     if path[:len(info[1])] == info[1]:
                         rel_path = path[len(info[1]):]
                         path = info[0]+rel_path
+        # Add suggested base revision for diffing
+        prev = None
+        log_info_backward = self.log_info[:]
+        log_info_backward.reverse()
+        for item in log_info_backward:
+            if prev:
+                item['diff'] = prev
+            prev = item['rev']
         return self.log_info
 
     def generate_path_links(self, rev, rev_specified):
=== trac/wikimacros/rst.py
==================================================================
--- trac/wikimacros/rst.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/wikimacros/rst.py   (/local/trunk/trac)   (revision 1120)
@@ -43,16 +43,20 @@
     raise EnvironmentError, 'Docutils version >= %s required, %s found' % (docutils_required, __version__)
 
 from trac.Href import Href
+from trac.WikiFormatter import WikiProcessor
 
 __docformat__ = 'reStructuredText'
 
-WIKI_LINK = re.compile(r'(?:wiki:)?(?P<w>[A-Za-z][\w\#\?]*[^\w\#\?]*)') # Links must begin with Letters, \# ? so we can link inside pages.
-#WIKI_LINK = re.compile(r'(?:wiki:)?(?P<w>(^|(?<=[^A-Za-z]))[!]?[A-Z][a-z/]+(?:[A-Z][a-z/]+)+)')
+WIKI_LINK = re.compile(r'(?:wiki:)?(.+)')
+#WIKI_LINK = re.compile(r'(?:wiki:)?(?P<wikilink>[A-Za-z][\w\-]*[^\w\#\?]*)')
 TICKET_LINK = re.compile(r'(?:#(\d+))|(?:ticket:(\d+))')
 REPORT_LINK = re.compile(r'(?:{(\d+)})|(?:report:(\d+))')
 CHANGESET_LINK = re.compile(r'(?:\[(\d+)\])|(?:changeset:(\d+))')
 FILE_LINK = re.compile(r'(?:browser|repos|source):([^#]+)#?(.*)')
 
+#import trac.Logging
+#log = trac.Logging.logger_factory(logtype='file', logfile="/tmp/debug_rst.txt", level='ALL')
+
 def _wikipage(href, args):
     return href.wiki(args[0])
 
@@ -71,29 +75,34 @@
     return href.browser(path, rev)
 
 # TracLink REs and callback functions
-LINKS = [(WIKI_LINK, _wikipage),
-         (TICKET_LINK, _ticket),
+LINKS = [(TICKET_LINK, _ticket),
          (REPORT_LINK, _report),
          (CHANGESET_LINK, _changeset),
-         (FILE_LINK, _browser)]
+         (FILE_LINK, _browser),
+         (WIKI_LINK, _wikipage)]
 
 
-def trac_get_reference(env, rawtext, text):
+def trac_get_reference(env, rawtext, link, text):
+
     for (pattern, function) in LINKS:
-        m = pattern.match(text)
+        m = pattern.match(link)
         if m:
             g = filter(None, m.groups())
             missing = False
+            if not text:
+                text = g[0]
             if pattern == WIKI_LINK:
-                if not (env._wiki_pages.has_key(g[0])):
+                pagename = re.search(r'^[^\#]+',g[0])
+                if not (env._wiki_pages.has_key(pagename.group())):
                         missing = True
-                        text = text + "?"
+                        text = text + "?"            
             uri = function(env.href, g)
             reference = nodes.reference(rawtext, text)
             reference['refuri']= uri
             if missing:
                 reference.set_class('missing')
             return reference
+        
     return None
 
 def trac(env, name, arguments, options, content, lineno,
@@ -119,8 +128,12 @@
 
     .. _TracLink: http://projects.edgewall.com/trac/wiki/TracLinks
     """
-    text = arguments[int(len(arguments) == 2)]
-    reference = trac_get_reference(env, block_text, text)
+    link = arguments[0]
+    if len(arguments) == 2:
+        text = arguments[1]
+    else:
+        text = None
+    reference = trac_get_reference(env, block_text, link, text)
     if reference:
         return reference
     # didn't find a match (invalid TracLink),
@@ -133,7 +146,13 @@
 
 
 def trac_role(env, name, rawtext, text, lineno, inliner, options={}, content=[]):
-    reference = trac_get_reference(env, rawtext, text)
+    args  = text.split(" ",1)
+    link = args[0]
+    if len(args)==2:
+        text = args[1]
+    else:
+        text = None
+    reference = trac_get_reference(env, rawtext, link, text)
     if reference:
         return [reference], []
     warning = nodes.warning(None,
@@ -159,6 +178,27 @@
     rst.roles.register_local_role('trac', do_trac_role)
 
     # The code_block could is taken from the leo plugin rst2
+    def code_formatter(language, text):
+        #log.debug("language '%s' args '%s'", % (language, arguments))
+        Format = WikiProcessor(env, language)
+        html = Format.process(hdf, text)        
+        #log.debug("language '%s' htmltext '%s'" % (language, html))
+        raw = nodes.raw('',html, format='html') #(self, rawsource='', text='', *children, **attributes):
+        return raw
+        
+    def code_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
+        args  = text.split(":",1)
+        language = args[0]
+        if len(args)==2:
+            text = args[1]
+        else:
+            text = ""
+        #log.debug("coderole '%s' text '%s'" % (language, text))
+        reference = code_formatter(language, text)
+        return [reference], []
+        
+
+      
     def code_block(name,arguments,options,content,lineno,content_offset,block_text,state,state_machine):
 
         """Create a code-block directive for docutils.
@@ -166,24 +206,11 @@
         Usage: .. code-block:: language
 
         If the language can be syntax highlighted it will be."""
-
-
-        
-        from trac.WikiFormatter import Formatter
-        
         language = arguments[0]
+        text = '\n'.join(content)        
+        reference = code_formatter(language, text)
+        return [reference]
 
-        code_processor = None
-        if  Formatter.builtin_processors.has_key(language):
-            code_processor = Formatter.builtin_processors[language]
-        else:
-            code_processor = Formatter.builtin_processors['default']
-
-
-        html = code_processor(hdf, '\n'.join(content), env)        
-        raw = nodes.raw('',html, format='html') #(self, rawsource='', text='', *children, **attributes):
-        return [raw]
-
     # These are documented at http://docutils.sourceforge.net/spec/howto/rst-directives.html.
     code_block.arguments = (
         1, # Number of required arguments.
@@ -199,6 +226,7 @@
     code_block.content = 1 # True if content is allowed.
     # Register the directive with docutils.
     rst.directives.register_directive('code-block',code_block)
+    rst.roles.register_local_role('code-block', code_role)
     
     
 
=== trac/Mimeview.py
==================================================================
--- trac/Mimeview.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/Mimeview.py   (/local/trunk/trac)   (revision 1120)
@@ -52,15 +52,16 @@
     'make':'text/x-makefile', 'mk':'text/x-makefile', 'Makefile':'text/x-makefile',
     'mail':'text/x-mail',
     'pas':'text/x-pascal',
-    'pl':'text/x-perl', 'pm':'text/x-perl', 'PL':'text/x-perl',
+    'pl':'text/x-perl', 'pm':'text/x-perl', 'PL':'text/x-perl', 'perl':'text/x-perl',
     'php':'text/x-php', 'php4':'text/x-php', 'php3':'text/x-php',
     'ps':'application/postscript',
     'psp':'text/x-psp',
-    'py':'text/x-python',
+    'py':'text/x-python', 'python':'text/x-python',
     'pyx':'text/x-pyrex',
     'nroff':'application/x-troff', 'roff':'application/x-troff', 'troff':'application/x-troff',
-    'rb':'text/x-ruby',
+    'rb':'text/x-ruby', 'ruby':'text/x-ruby',
     'rfc':'text/x-rfc',
+    'rst':'text/restructured',
     'scm':'text/x-scheme',
     'sh':'application/x-sh',
     'sql':'text/x-sql',
@@ -69,7 +70,7 @@
     'tex':'text/x-tex',
     'vba':'text/x-vba',
     'bas':'text/x-vba',
-    'v':'text/x-verilog',
+    'v':'text/x-verilog', 'verilog':'text/x-verilog',
     'vhd':'text/x-vhdl',
     'vrml':'model/vrml',
     'wrl':'model/vrml',
=== trac/upgrades/db8.py
==================================================================
--- trac/upgrades/db8.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/upgrades/db8.py   (/local/trunk/trac)   (revision 1120)
@@ -0,0 +1,35 @@
+sql = """
+-- Add category to 'ticket'
+CREATE TEMP TABLE ticket_old AS SELECT * FROM ticket;
+DROP TABLE ticket;
+CREATE TABLE ticket (
+        id              integer PRIMARY KEY,
+        category        text,           -- the nature of the ticket
+        time            integer,        -- the time it was created
+        changetime      integer,
+        component       text,
+        severity        text,
+        priority        text,
+        owner           text,           -- who is this ticket assigned to
+        reporter        text,
+        cc              text,           -- email addresses to notify
+        url             text,           -- url related to this ticket
+        version         text,           --
+        milestone       text,           --
+        status          text,
+        resolution      text,
+        summary         text,           -- one-line summary
+        description     text,           -- problem description (long)
+        keywords        text
+);
+INSERT INTO ticket(id, category, time, changetime, duetime, component,
+                   severity, priority, owner, reporter, cc, url, version,
+                   milestone, status, resolution, summary, description, keywords)
+  SELECT id, '', time, changetime, component,
+         severity, priority, owner, reporter, cc, url, version,
+         milestone, status, resolution, summary, description, keywords FROM ticket_old;
+
+"""
+
+def do_upgrade(env, ver, cursor):
+    cursor.execute(sql)

Property changes on: trac/upgrades/db8.py
___________________________________________________________________
Name: svn:executable
 +*

=== trac/Wiki.py
==================================================================
--- trac/Wiki.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/Wiki.py   (/local/trunk/trac)   (revision 1120)
@@ -24,7 +24,10 @@
 import urllib
 import difflib
 import StringIO
+import re
 
+import svn
+
 import perm
 from Module import Module
 from util import escape, TracError, get_reporter_id
@@ -51,10 +54,13 @@
 class WikiPage:
     """WikiPage: Represents a wiki page (new or existing).
     """
-    def __init__(self, name, version, perm, db):
-        self.db = db
+    def __init__(self, name, version, module):
+        self.db = module.db
+        self.perm = module.perm
+        self.fs_ptr = module.fs_ptr
+        self.pool = module.pool
         self.name = name
-        self.perm = perm
+        self.path = None
         cursor = self.db.cursor ()
         if version:
             cursor.execute ('SELECT version, text, readonly FROM wiki '
@@ -69,9 +75,31 @@
             self.version = int(row[0])
             self.text = row[1]
             self.readonly = row[2] and int(row[2]) or 0
+            # detect Subversion redirection
+            m_source = re.match('source:(\S+)', self.text)
+            if m_source:
+                self.path = m_source.group(1)
+                print "Subversion redirection detected to : %s" % self.path
+                if version:
+                    try:
+                        rev = int(version)
+                    except ValueError:
+                        rev = svn.fs.youngest_rev(self.fs_ptr, self.pool)
+                else:
+                    rev = svn.fs.youngest_rev(self.fs_ptr, self.pool)
+                try:
+                    root = svn.fs.revision_root(self.fs_ptr, rev, self.pool)
+                except svn.core.SubversionException:
+                    rev = svn.fs.youngest_rev(self.fs_ptr, self.pool)
+                    root = svn.fs.revision_root(self.fs_ptr, rev, self.pool)
+                print "revision is %d" % rev
+                length = svn.fs.file_length(root, self.path, self.pool)
+                fd = svn.fs.file_contents(root, self.path, self.pool)
+#                print "fd is %s (%s) length is %s (%s)" % (fd, type(fd), length, type(length))
+                self.text = svn.util.svn_stream_read(fd, int(length))
         else:
             self.version = 0
-	    self.new = 1
+            self.new = 1
 	    if not self.perm.has_permission( 'WIKI_CREATE' ):
 	        self.text = 'Wiki page %s not found' % name
 	        self.readonly = 1			   
@@ -102,14 +130,18 @@
             self.db.commit ()
             self.old_readonly = self.readonly
         elif self.modified:
-            cursor.execute ('INSERT INTO WIKI '
-                            '(name, version, time, author, ipnr, text, comment, readonly) '
-                            'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)',
-                            self.name, self.version, int(time.time()),
-                            author, remote_addr, self.text, comment, self.readonly)
-            self.db.commit ()
-            self.old_readonly = self.readonly
-            self.modified = 0
+            # Subversion redirection:
+            if self.path:
+                print "Should save the stuff in subversion..."
+            else:
+                cursor.execute ('INSERT INTO WIKI '
+                                '(name, version, time, author, ipnr, text, comment, readonly) '
+                                'VALUES (%s, %s, %s, %s, %s, %s, %s, %s)',
+                                self.name, self.version, int(time.time()),
+                                author, remote_addr, self.text, comment, self.readonly)
+                self.db.commit ()
+                self.old_readonly = self.readonly
+                self.modified = 0
         else:
             del cursor
             raise TracError('Page not modified')
@@ -207,11 +239,18 @@
         # Ask web spiders to not index old version
         if diff or version:
             self.req.hdf.setValue('html.norobots', '1')
-
+        
         if cancel:
             self.req.redirect(self.env.href.wiki(name))
             # Not reached
+        
+        # Retrieve the content of the page (maybe take it from Svn ...)
+        self.page = WikiPage(name, version, self)
 
+        if history and self.page.path:
+            self.req.redirect(self.env.href.log(self.page.path))
+            # Not reached
+
         if delete_ver and edit_version and name:
             # Delete only a specific page version
             self.perm.assert_permission(perm.WIKI_DELETE)
@@ -299,7 +338,6 @@
             self.env.get_attachments_hdf(self.db, 'wiki', name, self.req.hdf,
                                          'wiki.attachments')
 
-        self.page = WikiPage(name, version, self.perm, self.db)
         if self.args.has_key('text'):
             self.page.set_content (self.args.get('text'))
         else:
@@ -324,7 +362,9 @@
         if save and edit_version != str(self.page.version - 1):
             raise TracError('Sorry, Cannot create new version, this page has '
                             'already been modified by someone else.')
-
+        # note: instead of an error, one should present a diff between current text and latest!
+        #       (or between base and ours..., or a 3-way diff!)
+        
         if save:
             self.page.commit(author, comment, self.req.remote_addr)
             self.req.redirect(self.env.href.wiki(self.page.name))
@@ -335,10 +375,19 @@
                               self.env.href.wiki(self.page.name, history=1))
         self.req.hdf.setValue('wiki.page_name', self.page.name)
         self.req.hdf.setValue('wiki.page_source', escape(self.page.text))
-        out = StringIO.StringIO()
-        Formatter(self.req.hdf, self.env,self.db).format(self.page.text, out)
-        self.req.hdf.setValue('wiki.page_html', out.getvalue())
 
+        self.req.hdf.setValue('wiki.page_html', self.format_txt())
+
+    def format_txt(self):
+        fmt = Formatter(self.req.hdf, self.env, self.db)
+        if self.page.path and re.search('\.rst$', self.page.path):
+            return WikiProcessor(self.env, 'rst').process(self.req.hdf, self.page.text)
+        
+        else:
+            out = StringIO.StringIO()
+            fmt.format(self.page.text, out)
+            return out.getvalue()
+
     def display_txt(self):
         self.req.send_response(200)
         self.req.send_header('Content-Type', 'text/plain;charset=utf-8')
=== trac/Environment.py
==================================================================
--- trac/Environment.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/Environment.py   (/local/trunk/trac)   (revision 1120)
@@ -23,6 +23,7 @@
 #
 
 import os
+import imp
 import sys
 import time
 import shutil
@@ -210,7 +211,19 @@
 
     def setup_mimeviewer(self):
         self.mimeview = Mimeview.Mimeview(self)
-    
+
+    def load_macro(self, name):
+        # TODO: cache the macro once found?
+        # Look in envdir/wiki-macros/ first
+        try:
+            module = imp.load_source(name, os.path.join(self.path, 'wiki-macros', name+'.py'))
+        except IOError:
+            # fall back to site-wide macros
+            macros = __import__('wikimacros.' + name, globals(),  locals(), [])
+            module = getattr(macros, name)
+        return module.execute
+        
+
     def get_attachments_dir(self):
         return os.path.join(self.path, 'attachments')
 
=== trac/mimeviewers/__init__.py
==================================================================
--- trac/mimeviewers/__init__.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/mimeviewers/__init__.py   (/local/trunk/trac)   (revision 1120)
@@ -1 +1 @@
-__all__=['default', 'silvercity', 'test', 'image', 'enscript']
+__all__=['default', 'silvercity', 'test', 'image', 'enscript', 'wikis']
=== trac/mimeviewers/wikis.py
==================================================================
--- trac/mimeviewers/wikis.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/mimeviewers/wikis.py   (/local/trunk/trac)   (revision 1120)
@@ -0,0 +1,27 @@
+import StringIO
+
+from trac.WikiFormatter import Formatter, WikiProcessor
+
+supported_types = [(1, 'text/restructured'),(1, 'text/x-rst'),
+                   (1, 'text/tracwiki')]
+
+def display(data, mimetype, filename, rev, env):
+    env.log.debug("Mimetype: %s   filename: %s" % (mimetype, filename))
+    if not mimetype:
+        env.log.debug("rst mimeviewer: nothing to do with %s." % filename)
+        return ''
+    if mimetype[5:] in ['restructured', 'x-rst']:
+        env.log.debug("Using rst mimeviewer")
+        return WikiProcessor(env, 'rst').process(None, data)
+    elif mimetype[5:] in ['tracwiki']:
+        env.log.debug("Using trac wiki formatter")
+        out = StringIO.StringIO()
+        fmt = Formatter(None, env, None)
+        env.log.debug("got formatter..")
+        fmt.format(data, out)
+        return out.getvalue()
+    else:
+        env.log.debug("rst mimeviewer: nothing to do with %s (unhandled mimetype %s)."
+                      % (filename, mimetype))
+        return ''
+        
=== trac/Ticket.py
==================================================================
--- trac/Ticket.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/Ticket.py   (/local/trunk/trac)   (revision 1120)
@@ -37,7 +37,7 @@
 class Ticket(UserDict):
     std_fields = ['time', 'component', 'severity', 'priority', 'milestone',
                   'reporter', 'owner', 'cc', 'url', 'version', 'status', 'resolution',
-                  'keywords', 'summary', 'description']
+                  'keywords', 'summary', 'description', 'category']
 
     def __init__(self, *args):
         UserDict.__init__(self)
@@ -112,8 +112,11 @@
                        *std_values)
         id = db.db.sqlite_last_insert_rowid()
         for name in custom_fields:
+            value = self[name]
+            if isinstance(value, list):
+                value = '|'.join(value)
             cursor.execute('INSERT INTO ticket_custom(ticket,name,value)'
-                           ' VALUES(%d, %s, %s)', id, name[7:], self[name])
+                           ' VALUES(%d, %s, %s)', id, name[7:], value)
         db.commit()
         self['id'] = id
         self._forget_changes()
@@ -144,19 +147,22 @@
            
 
         for name in self._old.keys():
+            newvalue = self[name]
             if name[:7] == 'custom_':
                 fname = name[7:]
+                if isinstance(newvalue, list):
+                    newvalue = '|'.join(newvalue)
                 cursor.execute('REPLACE INTO ticket_custom(ticket,name,value)'
-                               ' VALUES(%s, %s, %s)', id, fname, self[name])
+                               ' VALUES(%s, %s, %s)', id, fname, newvalue)
             else:
                 fname = name
                 cursor.execute ('UPDATE ticket SET %s=%s WHERE id=%s',
-                                fname, self[name], id)
+                                fname, newvalue, id)
 
             cursor.execute ('INSERT INTO ticket_change '
                             '(ticket, time, author, field, oldvalue, newvalue) '
                             'VALUES (%s, %s, %s, %s, %s, %s)',
-                            id, when, author, fname, self._old[name], self[name])
+                            id, when, author, fname, self._old[name], newvalue)
         if comment:
             cursor.execute ('INSERT INTO ticket_change '
                             '(ticket,time,author,field,oldvalue,newvalue) '
@@ -226,7 +232,7 @@
             'label': items.get(name + '.label', ''),
             'value': items.get(name + '.value', '')
         }
-        if field['type'] == 'select' or field['type'] == 'radio':
+        if field['type'] in ['multi', 'select', 'radio']:
             field['options'] = items.get(name + '.options', '').split('|')
         elif field['type'] == 'textarea':
             field['width'] = items.get(name + '.cols', '')
@@ -248,12 +254,16 @@
         hdf.setValue('%s.type' % pfx, f['type'])
         hdf.setValue('%s.label' % pfx, f['label'])
         hdf.setValue('%s.value' % pfx, val)
-        if f['type'] == 'select' or f['type'] == 'radio':
+        if f['type'] in ['multi', 'select', 'radio']:
             j = 0
+            if f['type'] == 'multi':
+                multi = 1
+                val = val.split('|') # see Ticket.save_changes
             for option in f['options']:
-                hdf.setValue('%s.option.%d' % (pfx, j), option)
-                if val and (option == val or str(j) == val):
-                    hdf.setValue('%s.option.%i.selected' % (pfx, j), '1')
+                hdf.setValue('%s.option.%d.name' % (pfx, j), option)
+                if val:
+                    if (multi and option in val) or (option == val or str(j) == val):
+                        hdf.setValue('%s.option.%i.selected' % (pfx, j), '1')
                 j += 1
         elif f['type'] == 'checkbox':
             if val in util.TRUE:
@@ -283,6 +293,11 @@
             owner = cursor.fetchone()[0]
             ticket['owner'] = owner
 
+        # "Auto-assign":
+        # The status will be set to 'assigned' if the reporter is also the owner
+        if ticket['owner'] == ticket['reporter']:
+            ticket['status'] = 'assigned'
+
         tktid = ticket.insert(self.db)
 
         # Notify
@@ -303,6 +318,8 @@
                           self.env.get_config('ticket', 'default_component'))
         ticket.setdefault('milestone',
                           self.env.get_config('ticket', 'default_milestone'))
+        ticket.setdefault('category',
+                          self.env.get_config('ticket', 'default_category'))
         ticket.setdefault('priority',
                           self.env.get_config('ticket', 'default_priority'))
         ticket.setdefault('severity',
@@ -393,6 +410,7 @@
         util.hdf_add_if_missing(self.req.hdf, 'ticket.components', ticket['component'])
         util.hdf_add_if_missing(self.req.hdf, 'ticket.milestones', ticket['milestone'])
         util.hdf_add_if_missing(self.req.hdf, 'ticket.versions', ticket['version'])
+        util.hdf_add_if_missing(self.req.hdf, 'enums.category', ticket['category'])
         util.hdf_add_if_missing(self.req.hdf, 'enums.priority', ticket['priority'])
         util.hdf_add_if_missing(self.req.hdf, 'enums.severity', ticket['severity'])
         util.hdf_add_if_missing(self.req.hdf, 'enums.resolution', 'fixed')
=== trac/WikiFormatter.py
==================================================================
--- trac/WikiFormatter.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/WikiFormatter.py   (/local/trunk/trac)   (revision 1120)
@@ -1,4 +1,4 @@
- # -*- coding: iso8859-1 -*-
+# -*- coding: iso8859-1 -*-
 #
 # Copyright (C) 2003, 2004 Edgewall Software
 # Copyright (C) 2003, 2004 Jonas Borgström <jonas@edgewall.com>
@@ -21,16 +21,88 @@
 
 import re
 import os
-import imp
 import time
 import string
 import StringIO
 
 import util
+import Mimeview
 
-__all__ = ['Formatter', 'OneLinerFormatter', 'wiki_to_html', 'wiki_to_oneliner']
+__all__ = ['Formatter', 'OneLinerFormatter', 'wiki_to_html', 'wiki_to_oneliner', 'WikiProcessor']
 
 
+class WikiProcessor:
+
+    mime_type = ""
+
+    def __init__(self, env, name):
+        self.env = env
+        self.name = name
+	self.error = self.set_code_processor()
+    
+    def default_processor(hdf, text, env):
+        return '<pre class="wiki">' + util.escape(text) + '</pre>'
+    
+    def html_processor(hdf, text, env):
+        if Formatter._htmlproc_disallow_rule.search(text):
+            err = """\
+<div class="system-message">Error: HTML block contains disallowed tags.
+<pre>
+%s</pre>
+</div>""" % util.escape(text)
+            env.log.error(err)
+            return err
+        return text
+
+    def mime_processor(self, hdf, text, env):
+        return env.mimeview.display(text, self.mime_type)
+    
+    builtin_processors = { 'html': html_processor,
+                           'default': default_processor}
+
+    def process(self, hdf, text, inline=False):
+        text = self.code_processor(hdf, text, self.env)
+        if inline:
+            code_block_start = re.compile('^<div class="code-block">')
+            code_block_end = re.compile('</div>$')
+            text, nr = code_block_start.subn('<span class="code-block">', text, 1 )
+            if nr:
+                text, nr = code_block_end.subn('</span>', text, 1 )
+            return text
+        else:
+            return text
+    
+    def set_code_processor(self):
+        if  self.builtin_processors.has_key(self.name):
+            self.code_processor = self.builtin_processors[self.name]
+        else:
+            try:
+                self.code_processor = self.env.load_macro(self.name)
+            except Exception, e:
+                if Mimeview.MIME_MAP.has_key(self.name):
+                    self.name = Mimeview.MIME_MAP[self.name]
+                mimeviewer, exists = self.env.mimeview.get_viewer(self.name)
+                if exists != -1:
+                    self.mime_type = self.name
+                    self.code_processor = self.mime_processor
+                else:
+                    self.code_processor = self.builtin_processors['default']
+                    return 1
+        return 0
+
+    def error_message(self, context=''):
+        if self.error:
+            if re.search("/", self.name):
+                category = "mimeviewer for"
+            else:
+                category = "processor macro"
+            return "<div class='system-message'>Failed to load %s '%s': '%s' </div>" \
+                   % (category, self.name, context)
+        else:
+            return ""
+    
+
+
 class CommonFormatter:
     """This class contains the patterns common to both Formatter and
     OneLinerFormatter"""
@@ -41,10 +113,10 @@
               r"""(?P<strike>~~)""",
               r"""(?P<inlinecode>!?\{\{\{(?P<inline>.*?)\}\}\})""",
               r"""(?P<htmlescapeentity>!?&#\d+;)""",
-              r"""(?P<tickethref>!?#\d+)""",
-              r"""(?P<changesethref>!?\[\d+\])""",
+              r"""(?P<tickethref>!?#(?P<t_intertrac>[a-zA-z]?)\d+)""",
+              r"""(?P<changesethref>!?\[(?P<c_intertrac>[a-zA-z]?)\d+\])""",
               r"""(?P<reporthref>!?\{\d+\})""",
-              r"""(?P<modulehref>!?((?P<modulename>bug|ticket|browser|source|repos|report|changeset|wiki|milestone|search):(?P<moduleargs>(&#34;(.*?)&#34;|'(.*?)')|([^ ]*[^'~_\., \)]))))""",
+              r"""(?P<modulehref>!?((?P<modulename>bug|ticket|browser|source|repos|report|query|changeset|wiki|milestone|search):(?P<m_intertrac>[a-zA-z]:)?(?P<moduleargs>(&#34;(.*?)&#34;|'(.*?)')|([^ ]*[^'~_\., \)]))))""",
               r"""(?P<wikilink>!?(^|(?<=[^A-Za-z]))[A-Z][a-z]+(?:[A-Z][a-z]*[a-z/])+(?=\Z|\s|[.,;:!?\)}\]]))""",
               r"""(?P<fancylink>!?\[(?P<fancyurl>([a-z]+:[^ ]+)) (?P<linkname>.*?)\])"""]
 
@@ -112,30 +184,44 @@
         return match
 
     def _tickethref_formatter(self, match, fullmatch):
-        number = int(match[1:])
-        cursor = self.db.cursor ()
-        cursor.execute('SELECT summary,status FROM ticket WHERE id=%s', number)
-        row = cursor.fetchone ()
-        if not row:
-            return '<a class="missing" href="%s">#%d</a>' % (self._href.ticket(number), number)
+        intertrac = fullmatch.group('t_intertrac')
+        if intertrac:
+            remote_base, title, _class = self._intertrac_link('ticket', intertrac)
+            args = match[1+len(intertrac):]
+            href = self._href.ticket(args).replace(self._href.base, remote_base)
+            return '<a class="%s" title="%s" href="%s">#%s%s</a>' % (_class, title, href, intertrac, args)
         else:
-            summary =  util.escape(util.shorten_line(row[0]))
-            if row[1] == 'new':
-                return '<a href="%s" title="NEW : %s">#%d*</a>' % (self._href.ticket(number), summary, number)
-            elif row[1] == 'closed':
-                return '<a href="%s" title="CLOSED : %s"><del>#%d</del></a>' % (self._href.ticket(number), summary, number)
+            number = int(match[1:])
+            cursor = self.db.cursor ()
+            cursor.execute('SELECT summary,status FROM ticket WHERE id=%s', number)
+            row = cursor.fetchone ()
+            if not row:
+                return '<a class="missing" href="%s">#%d</a>' % (self._href.ticket(number), number)
             else:
-                return '<a href="%s" title="%s">#%d</a>' % (self._href.ticket(number), summary, number)
+                summary = util.escape(util.shorten_line(row[0]))
+                if row[1] == 'new':
+                    return '<a href="%s" title="NEW : %s">#%d*</a>' % (self._href.ticket(number), summary, number)
+                elif row[1] == 'closed':
+                    return '<a href="%s" title="CLOSED : %s"><del>#%d</del></a>' % (self._href.ticket(number), summary, number)
+                else:
+                    return '<a href="%s" title="%s">#%d</a>' % (self._href.ticket(number), summary, number)
 
     def _changesethref_formatter(self, match, fullmatch):
-        number = int(match[1:-1])
-        cursor = self.db.cursor ()
-        cursor.execute('SELECT message FROM revision WHERE rev=%d', number)
-        row = cursor.fetchone ()
-        if not row:
-            return '[<a class="missing" href="%s">%d</a>]' % (self._href.changeset(number), number)
+        intertrac = fullmatch.group('c_intertrac')
+        if intertrac:
+            remote_base, title, _class = self._intertrac_link('changeset', intertrac)
+            args = match[1+len(intertrac):-1]
+            href = self._href.changeset(args).replace(self._href.base, remote_base)
+            return '[<a class="%s" title="%s" href="%s">%s%s</a>]' % (_class, title, href, intertrac, args)
         else:
-            return '[<a title="%s" href="%s">%d</a>]' % ( util.escape(util.shorten_line(row[0])),self._href.changeset(number), number)
+            number = int(match[1:-1])
+            cursor = self.db.cursor ()
+            cursor.execute('SELECT message FROM revision WHERE rev=%d', number)
+            row = cursor.fetchone ()
+            if not row:
+                return '[<a class="missing" href="%s">%d</a>]' % (self._href.changeset(number), number)
+            else:
+                return '[<a title="%s" href="%s">%d</a>]' % (util.escape(util.shorten_line(row[0])), self._href.changeset(number), number)
 
     def _reporthref_formatter(self, match, fullmatch):
         number = int(match[1:-1])
@@ -147,8 +233,22 @@
             return None, None
         module = text[:sep]
         args = text[sep+1:]
+        sep2 = args.find(':')
+        if sep2 != -1:
+            intertrac = args[:sep2]
+            args = args[sep2+1:]
+            href, content, _, _ = self._generic_expand_module_link(intertrac, module, args)
+            remote_base, title, _class = self._intertrac_link(module, intertrac)
+            href = href.replace(self._href.base, remote_base)
+            return href, text, _class, title
+        else:
+            return self._generic_expand_module_link(None, module, args)
+
+    def _generic_expand_module_link(self, intertrac, module, args):
 	if module in ['bug', 'ticket']:
-	    cursor = self.db.cursor ()
+            if intertrac:
+                return self._href.ticket(args), '%s:%s' % (module, args), 0, ''
+            cursor = self.db.cursor ()
 	    cursor.execute('SELECT summary,status FROM ticket WHERE id=%s', args)
 	    row = cursor.fetchone ()				    
 	    if row:
@@ -168,7 +268,14 @@
                 return self._href.wiki(args), '%s:%s' % (module, args), 0, None
         elif module == 'report':
             return self._href.report(args), '%s:%s' % (module, args), 0, None
+        elif module == 'query':
+            if args and args[0] == '?':
+                return self._href.query() + args, '%s:%s' % (module, args), 0, None
+            else:
+                return self._href.query(), '%s:%s' % (module, args), 1, None
         elif module == 'changeset':
+            if intertrac:
+                return self._href.changeset(args), '%s:%s' % (module, args), 0, ''
 	    cursor = self.db.cursor ()
 	    cursor.execute('SELECT message FROM revision WHERE rev=%s', args)
 	    row = cursor.fetchone ()
@@ -195,9 +302,12 @@
             return None, None, 0, None
 
     def _modulehref_formatter(self, match, fullmatch):
-        link, text, missing, title = self._expand_module_link(match)
-        if link and missing:
-            return '<a title="%s" class="missing" href="%s">%s?</a>' % (title,link, text)
+        link, text, _class, title = self._expand_module_link(match)
+        if link and _class:
+            qmark = ''
+            if _class == 1:
+                _class, qmark = 'missing', '?'
+            return '<a title="%s" class="%s" href="%s">%s%s</a>' % (title, _class, link, text, qmark)
         elif link:
             return '<a title="%s" href="%s">%s</a>' % (title or '',link, text)
         else:
@@ -211,21 +321,34 @@
             return '<a href="%s">%s</a>' % (self._href.wiki(match), match)
 
     def _url_formatter(self, match, fullmatch):
-        return '<a href="%s">%s</a>' % (match, match)
+        return '<a class="external" href="%s">%s</a>' % (match, match)
 
     def _fancylink_formatter(self, match, fullmatch):
         link = fullmatch.group('fancyurl')
         name = fullmatch.group('linkname')
 
-        module_link, t, missing, title = self._expand_module_link(link)
-        if module_link and missing:
-            return '<a class="missing" href="%s">%s?</a>' % (module_link, name)
+        module_link, t, _class, title = self._expand_module_link(link)
+        if module_link and _class:
+            qmark = ''
+            if _class == 1:
+                _class, qmark = 'missing', '?'
+            return '<a class="%s" href="%s">%s%s</a>' % (_class, module_link, name, qmark)
         elif module_link:
             return '<a href="%s">%s</a>' % (module_link, name)
         else:
-            return '<a href="%s">%s</a>' % (link, name)
+            return '<a class="external" href="%s">%s</a>' % (link, name)
 
+    def _intertrac_link(self, module, intertrac):
+        href = self.env.get_config('intertrac', intertrac.upper() + '.href')
+        if href:
+            title = self.env.get_config('intertrac', intertrac.upper() + '.title')
+            _class = 'external'
+        else:
+            title = "Unknown intertrac link"
+            _class = 'missing'
+        return href, title, _class 
 
+
 class OneLinerFormatter(CommonFormatter):
     """
     A special version of the wiki formatter that only implement a
@@ -258,7 +381,7 @@
     """
     _rules = [r"""(?P<svnimg>(source|repos):([^ ]+)\.(PNG|png|JPG|jpg|JPEG|jpeg|GIF|gif))"""] + \
              CommonFormatter._rules + \
-             [r"""(?P<macro>!?\[\[(?P<macroname>[a-zA-Z]+)(\((?P<macroargs>[^\)]*)\))?\]\])""",
+             [r"""(?P<macro>!?\[\[(?P<macroname>[\w/+-]+)(\]\]|\((?P<macroargs>.*?)\)\]\]))""",
               r"""(?P<heading>^\s*(?P<hdepth>=+)\s.*\s(?P=hdepth)\s*$)""",
               r"""(?P<list>^(?P<ldepth>\s+)(?:\*|[0-9]+\.) )""",
               r"""(?P<indent>^(?P<idepth>\s+)(?=\S))""",
@@ -268,89 +391,26 @@
               r"""(?P<table_cell>\|\|)"""]
 
     _compiled_rules = re.compile('(?:' + string.join(_rules, '|') + ')')
-    _processor_re = re.compile('#\!([a-zA-Z0-9/+-]+)')
-    mime_type = ""
+    _processor_re = re.compile('#\!([\w/+-]+)')
     
 
     # RE patterns used by other patterna
     _helper_patterns = ('idepth', 'ldepth', 'hdepth', 'fancyurl',
                         'linkname', 'macroname', 'macroargs', 'inline',
-                        'modulename', 'moduleargs')
+                        'modulename', 'moduleargs',
+                        't_intertrac', 'c_intertrac', 'm_intertrac')
 
     _htmlproc_disallow_rule = re.compile('(?i)<(script|noscript|embed|object|iframe|frame|frameset|link|style|meta|param|doctype)')
 
-    def default_processor(hdf, text, env):
-        return '<pre class="wiki">' + util.escape(text) + '</pre>'
-    def asp_processor(hdf, text, env):
-        return env.mimeview.display(text, 'text/x-asp')
-    def c_processor(hdf, text, env):
-        return env.mimeview.display(text, 'text/x-csrc')
-    def css_processor(hdf, text, env):
-        return env.mimeview.display(text, 'text/css')
-    def java_processor(hdf, text, env):
-        return env.mimeview.display(text, 'text/x-java')
-    def cpp_processor(hdf, text, env):
-        return env.mimeview.display(text, 'text/x-c++src')
-    def perl_processor(hdf, text, env):
-        return env.mimeview.display(text, 'text/x-perl')
-    def php_processor(hdf, text, env):
-        return env.mimeview.display(text, 'text/x-php')
-    def python_processor(hdf, text, env):
-        return env.mimeview.display(text, 'text/x-python')
-    def ruby_processor(hdf, text, env):
-        return env.mimeview.display(text, 'text/x-ruby')
-    def sql_processor(hdf, text, env):
-        return env.mimeview.display(text, 'text/x-sql')
-    def xml_processor(hdf, text, env):
-        return env.mimeview.display(text, 'text/xml')
-    def verilog_processor(hdf, text, env):
-        return env.mimeview.display(text, 'text/x-verilog')
-    def html_processor(hdf, text, env):
-        if Formatter._htmlproc_disallow_rule.search(text):
-            err = """\
-<div class="system-message">
- <strong>Error: HTML block contains disallowed tags.</strong>
- <pre>%s</pre>
-</div>""" % util.escape(text)
-            env.log.error(err)
-            return err
-        return text
-    def mime_processor(self, hdf, text, env):
-        return env.mimeview.display(text, self.mime_type)
-
-    builtin_processors = { 'html': html_processor,
-                           'asp': asp_processor,
-                           'c': c_processor,
-                           'css': css_processor,
-                           'cpp': cpp_processor,
-                           'java': java_processor,
-                           'php': php_processor,
-                           'perl': perl_processor,
-                           'python': python_processor,
-                           'ruby': ruby_processor,
-                           'sql': sql_processor,
-                           'xml': xml_processor,
-                           'verilog': verilog_processor,
-                           'default': default_processor}
-
-    def load_macro(self, name):
-        # Look in envdir/wiki-macros/ first
-        try:
-            module = imp.load_source(name, os.path.join(self.env.path, 'wiki-macros', name+'.py'))
-        except IOError:
-            # fall back to site-wide macros
-            macros = __import__('wikimacros.' + name, globals(),  locals(), [])
-            module = getattr(macros, name)
-        return module.execute
-
     def _macro_formatter(self, match, fullmatch):
         name = fullmatch.group('macroname')
         if name in ['br', 'BR']:
             return '<br />'
         args = fullmatch.group('macroargs')
+        args = util.unescape(args)
         try:
-            macro = self.load_macro(name)
-            return macro(self.hdf, args, self.env)
+            macro = WikiProcessor(self.env, name)
+            return macro.process(self.hdf, args, True)
         except Exception, e:
             return '<div class="system-message"><strong>Error: Macro %s(%s) failed</strong><pre>%s</pre></div>' \
                    % (name, args, e)
@@ -496,39 +556,30 @@
             else:
                 self.code_text += line + os.linesep
                 if not self.code_processor:
-                    self.code_processor = Formatter.builtin_processors['default']
+                    self.code_processor = WikiProcessor(self.env, 'default')
         elif line.strip() == '}}}':
             self.in_code_block -= 1
             if self.in_code_block == 0 and self.code_processor:
                 self.close_paragraph()
                 self.close_table()
-                self.out.write(self.code_processor(self.hdf, self.code_text, self.env))
+                self.out.write(self.code_processor.process(self.hdf, self.code_text))
             else:
                 self.code_text += line + os.linesep
         elif not self.code_processor:
             match = Formatter._processor_re.search(line)
             if match:
                 name = match.group(1)
-                if  Formatter.builtin_processors.has_key(name):
-                    self.code_processor = Formatter.builtin_processors[name]
-                else:
-                    try:
-                        self.code_processor = self.load_macro(name)
-                    except Exception, e:
-                        mimeviewer, exists = self.env.mimeview.get_viewer(name)
-                        if exists != -1:
-                            self.mime_type = name
-                            self.code_processor = self.mime_processor
-                        else:
-                            self.code_text += line + os.linesep
-                            self.code_processor = Formatter.builtin_processors['default']
-                            self.out.write('<div class="system-message"><strong>Error: Failed to load processor <code>%s</code></strong>:<pre>%s</pre></div>' % (name, e))
+                self.code_processor = WikiProcessor(self.env, name)
+                if self.code_processor.error:
+                    self.out.write(self.code_processor.error_message(line))
+                    self.code_text += line + os.linesep
             else:
                 self.code_text += line + os.linesep 
-                self.code_processor = Formatter.builtin_processors['default']
+                self.code_processor = WikiProcessor(self.env, 'default')
         else:
             self.code_text += line + os.linesep
 
+
     def format(self, text, out):
         self.out = out
         self._open_tags = []
=== templates/diff_macros.cs
==================================================================
--- templates/diff_macros.cs   (/mirrors/trac/trunk)   (revision 1120)
+++ templates/diff_macros.cs   (/local/trunk/trac)   (revision 1120)
@@ -0,0 +1,174 @@
+
+<?cs def:diff_display(change, style) ?><?cs 
+ if:style == 'sidebyside' ?><?cs
+  each:block = change.blocks ?><?cs
+   if:block.type == 'unmod' ?><tbody class="unmod"><?cs
+    each:line = block.base.lines ?><tr>
+     <th class="base"><?cs var:#block.base.offset + name(line) + 1 ?></th>
+     <td class="base"><span><?cs var:line ?></span>&nbsp;</td>
+     <th class="chg"><?cs var:#block.changed.offset + name(line) + 1 ?></th>
+     <td class="chg"><span><?cs var:line ?></span>&nbsp;</td>
+    </tr><?cs /each ?>
+   </tbody><?cs
+   elif:block.type == 'mod' ?><tbody class="mod"><?cs
+    if:len(block.base.lines) >= len(block.changed.lines) ?><?cs
+     each:line = block.base.lines ?><tr>
+      <th class="base"><?cs var:#block.base.offset + name(line) + 1 ?></th>
+      <td class="base"><?cs var:line ?>&nbsp;</td><?cs
+      if:len(block.changed.lines) >= name(line) + 1 ?><?cs
+       each:changedline = block.changed.lines ?><?cs
+        if:name(changedline) == name(line) ?>
+         <th class="chg"><?cs var:#block.changed.offset + name(changedline) + 1 ?></th>
+         <td class="chg"><?cs var:changedline ?>&nbsp;</td><?cs
+        /if ?><?cs
+       /each ?><?cs
+      else ?>
+       <th class="chg">&nbsp;</th>
+       <td class="chg">&nbsp;</td><?cs
+      /if ?>
+     </tr><?cs /each ?><?cs
+    else ?><?cs
+     each:line = block.changed.lines ?><tr><?cs
+      if:len(block.base.lines) >= name(line) + 1 ?><?cs
+       each:baseline = block.base.lines ?><?cs
+        if:name(baseline) == name(line) ?>
+         <th class="base"><?cs var:#block.base.offset + name(baseline) + 1 ?></th>
+         <td class="base"><?cs var:baseline ?>&nbsp;</td><?cs
+        /if ?><?cs
+       /each ?><?cs
+      else ?>
+       <th class="base">&nbsp;</th>
+       <td class="base">&nbsp;</td><?cs
+      /if ?>
+      <th class="chg"><?cs var:#block.changed.offset + name(line) + 1 ?></th>
+      <td class="chg"><?cs var:line ?>&nbsp;</td>
+     </tr><?cs /each ?><?cs
+    /if ?>
+   </tbody><?cs
+   elif:block.type == 'add' ?><tbody class="add"><?cs
+    each:line = block.changed.lines ?><tr>
+     <th class="base">&nbsp;</th>
+     <td class="base">&nbsp;</td>
+     <th class="chg"><?cs var:#block.changed.offset + name(line) + 1 ?></th>
+     <td class="chg"><ins><?cs var:line ?></ins>&nbsp;</td>
+    </tr><?cs /each ?><?cs
+   elif:block.type == 'rem' ?><tbody class="rem"><?cs
+    each:line = block.base.lines ?><tr>
+     <th class="base"><?cs var:#block.base.offset + name(line) + 1 ?></th>
+     <td class="base"><del><?cs var:line ?></del>&nbsp;</td>
+     <th class="chg">&nbsp;</th>
+     <td class="chg">&nbsp;</td>
+    </tr><?cs /each ?><?cs
+   /if ?>
+  </tbody><?cs
+  /each ?><?cs
+ else ?><?cs
+  each:block = change.blocks ?>
+   <?cs if:block.type == 'unmod' ?><tbody class="unmod"><?cs
+    each:line = block.base.lines ?><tr>
+     <th class="base"><?cs var:#block.base.offset + name(line) + #1 ?></th>
+     <th class="chg"><?cs var:#block.changed.offset + name(line) + #1 ?></th>
+     <td class="base"><span><?cs var:line ?></span>&nbsp;</td>
+    </tr><?cs /each ?>
+   </tbody>
+   <?cs elif:block.type == 'mod' ?><tbody class="mod"><?cs
+    each:line = block.base.lines ?><tr class="<?cs
+      if:name(line) == 0 ?>first<?cs /if ?>">
+     <th class="base"><?cs var:#block.base.offset + name(line) + #1 ?></th>
+     <th class="chg">&nbsp;</th>
+     <td class="base"><?cs var:line ?>&nbsp;</td>
+    </tr><?cs /each ?><?cs
+    each:line = block.changed.lines ?><tr class="<?cs
+      if:name(line) + 1 == len(block.changed.lines) ?> last<?cs /if ?>">
+     <th class="base">&nbsp;</th>
+     <th class="chg"><?cs var:#block.changed.offset + name(line) + #1 ?></th>
+     <td class="chg"><?cs var:line ?>&nbsp;</td>
+    </tr><?cs /each ?>
+   </tbody>
+   <?cs elif:block.type == 'add' ?><tbody class="add"><?cs
+    each:line = block.changed.lines ?><tr class="<?cs
+      if:name(line) == 0 ?>first<?cs /if ?><?cs
+      if:name(line) + 1 == len(block.changed.lines) ?> last ?><?cs /if ?>">
+     <th class="base">&nbsp;</th>
+     <th class="chg"><?cs var:#block.changed.offset + name(line) + #1 ?></th>
+     <td class="chg"><ins><?cs var:line ?></ins>&nbsp;</td>
+    </tr><?cs /each ?>
+   </tbody>
+   <?cs elif:block.type == 'rem' ?><tbody class="rem"><?cs
+    each:line = block.base.lines ?><tr class="<?cs
+      if:name(line) == 0 ?>first<?cs /if ?><?cs
+      if:name(line) + 1 == len(block.base.lines) ?> last ?><?cs /if ?>">
+     <th class="base"><?cs var:#block.base.offset + name(line) + 1 ?></th>
+     <th class="chg">&nbsp;</th>
+     <td class="base"><del><?cs var:line ?></del>&nbsp;</td>
+    </tr><?cs /each ?>
+   </tbody>
+   <?cs /if ?><?cs
+  /each ?><?cs
+ /if ?><?cs
+/def ?>
+
+
+<?cs def:diff_file(file, diff) ?>
+  <?cs if:len(file.changes) ?>
+   <li id="file<?cs var:name(file) ?>">
+    <h2><a href="<?cs
+      var:file.browser_href.new ?>" title="Show version <?cs
+      var:file.rev.new ?> of this file in browser"><?cs
+      var:file.name.new ?></a></h2>
+    <?cs if:diff.style == 'sidebyside' ?>
+     <table class="sidebyside" summary="Differences" cellspacing="0">
+      <colgroup class="base">
+       <col class="lineno" /><col class="content" />
+      <colgroup class="chg">
+       <col class="lineno" /><col class="content" />
+      </colgroup>
+      <thead><tr>
+       <th colspan="2"><a href="<?cs var:file.browser_href.old ?>">Revision <?cs
+         var:file.rev.old ?></a></th>
+       <th colspan="2"><a href="<?cs var:file.browser_href.new ?>">Revision <?cs
+         var:file.rev.new ?></a></th>
+      </tr></thead>
+      <?cs each:change = file.changes ?>
+       <tbody>
+        <?cs call:diff_display(change, diff.style) ?>
+       </tbody>
+       <?cs if:name(change) < len(file.changes) - 1 ?>
+        <tbody class="skippedlines">
+         <tr><th>&hellip;</th><td>&nbsp;</td>
+         <th>&hellip;</th><td>&nbsp;</td></tr>
+        </tbody>
+       <?cs /if ?>
+      <?cs /each ?>
+     </table>
+    <?cs else ?>
+     <table class="inline" summary="Differences" cellspacing="0">
+      <colgroup>
+       <col class="lineno" />
+       <col class="lineno" />
+       <col class="content" />
+      </colgroup>
+      <thead><tr>
+       <th title="Revision <?cs var:file.rev.old ?>"><a href="<?cs
+         var:file.browser_href.old ?>" title="Show revision <?cs
+         var:file.rev.old ?> of this file in browser">r<?cs
+         var:file.rev.old ?></a></th>
+       <th title="Revision <?cs var:file.rev.new ?>"><a href="<?cs
+         var:file.browser_href.new ?>" title="Show revision <?cs
+         var:file.rev.new ?> of this file in browser">r<?cs
+         var:file.rev.new ?></a></th>
+       <th>&nbsp;</th>
+      </tr></thead>
+      <?cs each:change = file.changes ?>
+       <?cs call:diff_display(change, diff.style) ?>
+       <?cs if:name(change) < len(file.changes) - 1 ?>
+        <tbody class="skippedlines">
+         <tr><th>&hellip;</th><th>&hellip;</th><td>&nbsp;</td></tr>
+        </tbody>
+       <?cs /if ?>
+      <?cs /each ?>
+     </table>
+    <?cs /if ?>
+   </li>
+  <?cs /if ?>
+<?cs /def ?>

Property changes on: templates/diff_macros.cs
___________________________________________________________________
Name: svn:executable
 +*

=== templates/log.cs
==================================================================
--- templates/log.cs   (/mirrors/trac/trunk)   (revision 1120)
+++ templates/log.cs   (/local/trunk/trac)   (revision 1120)
@@ -30,6 +30,7 @@
     <th class="chgset">Chgset</th>
     <th class="author">Author</th>
     <th class="summary">Log Message</th>
+    <th class="diff">Diff to</th>
    </tr>
   </thead>
   <tbody>
@@ -37,13 +38,22 @@
     <tr class="<?cs if:name(item) % #2 ?>even<?cs else ?>odd<?cs /if ?>">
      <td class="date"><?cs var:item.date ?></td>
      <td class="rev">
-      <a href="<?cs var:item.file_href ?>"><?cs var:item.rev ?></a>
+      <a href="<?cs var:item.file_href ?>"><?cs var:item.seq ?></a>
      </td>
      <td class="chgset">
       <a class="block-link" href="<?cs var:item.changeset_href ?>"><?cs var:item.rev ?></a>
      </td>
      <td class="author"><?cs var:item.author ?></td>
      <td class="summary"><?cs var:item.log ?></td>
+     <td class="diff">
+      <form action="<?cs var:item.file_href ?>" method="get">
+       <div>
+        <input type="submit" value="&Delta;"/>
+        <input type="hidden" id="rev" name="rev" value="<?cs var:item.rev ?>" size="4" />
+        <input type="text" id="diff" name="diff" value="<?cs var:item.diff ?>" size="4" />
+       </div>
+      </form>
+     </td>
     </tr>
    <?cs /each ?>
   </tbody>
=== templates/ticket.cs
==================================================================
--- templates/ticket.cs   (/mirrors/trac/trunk)   (revision 1120)
+++ templates/ticket.cs   (/local/trunk/trac)   (revision 1120)
@@ -32,7 +32,7 @@
 
 <div id="ticket">
  <div class="date"><?cs var:ticket.opened ?></div>
- <h1>Ticket #<?cs var:ticket.id ?> <?cs
+ <h1><?cs var:ticket.category ?> Ticket #<?cs var:ticket.id ?> <?cs
  if:ticket.status == 'closed' ?>(Closed: <?cs var:ticket.resolution ?>)<?cs
  elif:ticket.status != 'new' ?>(<?cs var:ticket.status ?>)<?cs
  /if ?></h1>
@@ -166,6 +166,9 @@
      var:ticket.summary ?>" /><?cs
    if $trac.acl.TICKET_ADMIN ?>
     <br />
+    <label for="category">Category:</label><?cs
+    call:hdf_select(enums.category, "category", ticket.category) ?>
+    <br />
     <label for="description">Description:</label>
     <div style="float: left">
      <textarea id="description" name="description" rows="10" cols="68"><?cs
=== templates/macros.cs
==================================================================
--- templates/macros.cs   (/mirrors/trac/trunk)   (revision 1120)
+++ templates/macros.cs   (/local/trunk/trac)   (revision 1120)
@@ -35,115 +35,6 @@
  /if ?></h1>
 <?cs /def ?>
 
-<?cs def:diff_display(change, style) ?><?cs
- if:style == 'sidebyside' ?><?cs
-  each:block = change.blocks ?><?cs
-   if:block.type == 'unmod' ?><tbody class="unmod"><?cs
-    each:line = block.base.lines ?><tr>
-     <th class="base"><?cs var:#block.base.offset + name(line) + 1 ?></th>
-     <td class="base"><span><?cs var:line ?></span>&nbsp;</td>
-     <th class="chg"><?cs var:#block.changed.offset + name(line) + 1 ?></th>
-     <td class="chg"><span><?cs var:line ?></span>&nbsp;</td>
-    </tr><?cs /each ?>
-   </tbody><?cs
-   elif:block.type == 'mod' ?><tbody class="mod"><?cs
-    if:len(block.base.lines) >= len(block.changed.lines) ?><?cs
-     each:line = block.base.lines ?><tr>
-      <th class="base"><?cs var:#block.base.offset + name(line) + 1 ?></th>
-      <td class="base"><?cs var:line ?>&nbsp;</td><?cs
-      if:len(block.changed.lines) >= name(line) + 1 ?><?cs
-       each:changedline = block.changed.lines ?><?cs
-        if:name(changedline) == name(line) ?>
-         <th class="chg"><?cs var:#block.changed.offset + name(changedline) + 1 ?></th>
-         <td class="chg"><?cs var:changedline ?>&nbsp;</td><?cs
-        /if ?><?cs
-       /each ?><?cs
-      else ?>
-       <th class="chg">&nbsp;</th>
-       <td class="chg">&nbsp;</td><?cs
-      /if ?>
-     </tr><?cs /each ?><?cs
-    else ?><?cs
-     each:line = block.changed.lines ?><tr><?cs
-      if:len(block.base.lines) >= name(line) + 1 ?><?cs
-       each:baseline = block.base.lines ?><?cs
-        if:name(baseline) == name(line) ?>
-         <th class="base"><?cs var:#block.base.offset + name(baseline) + 1 ?></th>
-         <td class="base"><?cs var:baseline ?>&nbsp;</td><?cs
-        /if ?><?cs
-       /each ?><?cs
-      else ?>
-       <th class="base">&nbsp;</th>
-       <td class="base">&nbsp;</td><?cs
-      /if ?>
-      <th class="chg"><?cs var:#block.changed.offset + name(line) + 1 ?></th>
-      <td class="chg"><?cs var:line ?>&nbsp;</td>
-     </tr><?cs /each ?><?cs
-    /if ?>
-   </tbody><?cs
-   elif:block.type == 'add' ?><tbody class="add"><?cs
-    each:line = block.changed.lines ?><tr>
-     <th class="base">&nbsp;</th>
-     <td class="base">&nbsp;</td>
-     <th class="chg"><?cs var:#block.changed.offset + name(line) + 1 ?></th>
-     <td class="chg"><ins><?cs var:line ?></ins>&nbsp;</td>
-    </tr><?cs /each ?><?cs
-   elif:block.type == 'rem' ?><tbody class="rem"><?cs
-    each:line = block.base.lines ?><tr>
-     <th class="base"><?cs var:#block.base.offset + name(line) + 1 ?></th>
-     <td class="base"><del><?cs var:line ?></del>&nbsp;</td>
-     <th class="chg">&nbsp;</th>
-     <td class="chg">&nbsp;</td>
-    </tr><?cs /each ?><?cs
-   /if ?>
-  </tbody><?cs
-  /each ?><?cs
- else ?><?cs
-  each:block = change.blocks ?>
-   <?cs if:block.type == 'unmod' ?><tbody class="unmod"><?cs
-    each:line = block.base.lines ?><tr>
-     <th class="base"><?cs var:#block.base.offset + name(line) + #1 ?></th>
-     <th class="chg"><?cs var:#block.changed.offset + name(line) + #1 ?></th>
-     <td class="base"><span><?cs var:line ?></span>&nbsp;</td>
-    </tr><?cs /each ?>
-   </tbody>
-   <?cs elif:block.type == 'mod' ?><tbody class="mod"><?cs
-    each:line = block.base.lines ?><tr class="<?cs
-      if:name(line) == 0 ?>first<?cs /if ?>">
-     <th class="base"><?cs var:#block.base.offset + name(line) + #1 ?></th>
-     <th class="chg">&nbsp;</th>
-     <td class="base"><?cs var:line ?>&nbsp;</td>
-    </tr><?cs /each ?><?cs
-    each:line = block.changed.lines ?><tr class="<?cs
-      if:name(line) + 1 == len(block.changed.lines) ?> last<?cs /if ?>">
-     <th class="base">&nbsp;</th>
-     <th class="chg"><?cs var:#block.changed.offset + name(line) + #1 ?></th>
-     <td class="chg"><?cs var:line ?>&nbsp;</td>
-    </tr><?cs /each ?>
-   </tbody>
-   <?cs elif:block.type == 'add' ?><tbody class="add"><?cs
-    each:line = block.changed.lines ?><tr class="<?cs
-      if:name(line) == 0 ?>first<?cs /if ?><?cs
-      if:name(line) + 1 == len(block.changed.lines) ?> last ?><?cs /if ?>">
-     <th class="base">&nbsp;</th>
-     <th class="chg"><?cs var:#block.changed.offset + name(line) + #1 ?></th>
-     <td class="chg"><ins><?cs var:line ?></ins>&nbsp;</td>
-    </tr><?cs /each ?>
-   </tbody>
-   <?cs elif:block.type == 'rem' ?><tbody class="rem"><?cs
-    each:line = block.base.lines ?><tr class="<?cs
-      if:name(line) == 0 ?>first<?cs /if ?><?cs
-      if:name(line) + 1 == len(block.base.lines) ?> last ?><?cs /if ?>">
-     <th class="base"><?cs var:#block.base.offset + name(line) + 1 ?></th>
-     <th class="chg">&nbsp;</th>
-     <td class="base"><del><?cs var:line ?></del>&nbsp;</td>
-    </tr><?cs /each ?>
-   </tbody>
-   <?cs /if ?><?cs
-  /each ?><?cs
- /if ?><?cs
-/def ?>
-
 <?cs def:session_name_email() ?><?cs
   if trac.authname != "anonymous" ?><?cs 
      var:trac.authname ?><?cs 
@@ -179,10 +70,16 @@
       var c.name ?>" value="1" <?cs if c.selected ?>checked="checked"<?cs /if ?> />
     <label for="custom_<?cs var c.name ?>"><?cs alt c.label ?><?cs
       var c.name ?><?cs /alt ?></label><?cs
+   elif c.type == 'multi' ?>
+    <div style="float: left">
+     <label for="custom_<?cs var c.name ?>"><?cs alt c.label ?><?cs
+        var c.name ?><?cs /alt ?></label>:
+     <?cs call:hdf_select_multiple(c.option, 'custom_'+c.name, 4) ?>
+    </div><?cs
    elif c.type == 'select' ?>
     <select name="custom_<?cs var c.name ?>"><?cs each v = c.option ?>
      <option <?cs if v.selected ?>selected="selected"<?cs /if ?>><?cs
-       var v ?></option><?cs /each ?>
+       var v.name ?></option><?cs /each ?>
     </select><?cs
    elif c.type == 'radio' ?>
     <fieldset class="radio">
=== templates/newticket.cs
==================================================================
--- templates/newticket.cs   (/mirrors/trac/trunk)   (revision 1120)
+++ templates/newticket.cs   (/local/trunk/trac)   (revision 1120)
@@ -9,8 +9,10 @@
 
 <div id="content" class="ticket">
 
-<h3>Create New Ticket:</h3>
 <form id="newticket" action="<?cs var:cgi_location ?>#preview" method="post">
+ <h3>Create New Ticket of <label for="category">category:</label><?cs
+  call:hdf_select(enums.category, "category", newticket.category) ?>
+ </h3>
  <div class="field">
   <label for="reporter">Your email or username:</label><br />
   <input type="text" id="reporter" name="reporter" size="40" value="<?cs
=== templates/changeset.cs
==================================================================
--- templates/changeset.cs   (/mirrors/trac/trunk)   (revision 1120)
+++ templates/changeset.cs   (/local/trunk/trac)   (revision 1120)
@@ -1,6 +1,7 @@
 <?cs set:html.stylesheet = 'css/changeset.css' ?>
 <?cs include "header.cs"?>
 <?cs include "macros.cs"?>
+<?cs include "diff_macros.cs"?>
 
 <div id="ctxtnav" class="nav">
  <h2>Changeset Navigation</h2>
=== templates/wiki.cs
==================================================================
--- templates/wiki.cs   (/mirrors/trac/trunk)   (revision 1120)
+++ templates/wiki.cs   (/local/trunk/trac)   (revision 1120)
@@ -1,6 +1,7 @@
 <?cs set:html.stylesheet = 'css/wiki.css' ?>
 <?cs include "header.cs" ?>
 <?cs include "macros.cs" ?>
+<?cs include "diff_macros.cs" ?>
 
 <div id="ctxtnav" class="nav">
  <h2>Wiki Navigation</h2>
=== templates/query.cs
==================================================================
--- templates/query.cs   (/mirrors/trac/trunk)   (revision 1120)
+++ templates/query.cs   (/local/trunk/trac)   (revision 1120)
@@ -2,11 +2,16 @@
 <?cs include:"header.cs" ?>
 <?cs include:"macros.cs" ?>
 
-<div id="ctxtnav" class="nav"><?cs if:query.edit_href ?>
- <ul>
-  <li class="last"><a href="<?cs var:query.edit_href ?>">Refine Query</a></li>
+<div id="ctxtnav" class="nav">
+ <ul><?cs 
+  if:query.edit_href ?>
+   <li class="last"><a href="<?cs var:query.edit_href ?>">Refine Query</a></li><?cs 
+  /if ?><?cs 
+  if:query.clear_href ?>
+   <li class="last"><a href="<?cs var:query.clear_href ?>">Clear Query</a></li><?cs 
+  /if ?>
  </ul>
-<?cs /if ?></div>
+</div>
 
 <div id="content" class="query">
  <h1><?cs var:title ?></h1>
@@ -20,6 +25,10 @@
   <?cs if:query.desc ?><input type="hidden" name="desc" value="1" /><?cs /if ?>
   <legend>Ticket Properties</legend>
   <div>
+   <label for="category" accesskey="t">Category:</label>
+   <?cs call:hdf_select_multiple(query.options.category, 'category', 4) ?>
+  </div>
+  <div>
    <label for="component" accesskey="c">Component:</label>
    <?cs call:hdf_select_multiple(query.options.component, 'component', 4) ?>
   </div>
@@ -81,7 +90,7 @@
   </div>
   <?cs if:len(query.custom) ?><?cs set:idx = 0 ?><?cs
    each:custom = query.custom ?><?cs
-    if:custom.type == 'select' || custom.type == 'radio' ?>
+    if:custom.type == 'select' || custom.type == 'radio' || custom.type == 'multi' ?>
      <?cs if:idx == 0 ?><br /><?cs /if ?><div>
       <label for="<?cs var:custom.name ?>"><?cs var:custom.label ?></label>
       <?cs call:hdf_select_multiple(custom.options, custom.name, 4) ?>
@@ -112,7 +121,7 @@
  /if ?> matched this query.</p>
  <table id="tktlist" class="listing">
   <thead><tr><?cs each:header = query.headers ?><?cs
-   if:name(header) == 0 ?><th class="ticket<?cs
+   if:header.name == 'id' ?><th class="ticket<?cs
     if:header.order ?> <?cs var:header.order ?><?cs /if ?>">
     <a href="<?cs var:header.href ?>" title="Sort by ID (<?cs
       if:header.order == 'asc' ?>descending<?cs
@@ -131,8 +140,8 @@
      if:name(result) % 2 ?>odd<?cs else ?>even<?cs /if ?> <?cs
      var:result.priority ?>">
     <?cs each:header = query.headers ?><?cs
-     if:name(header) == 0 ?>
-      <td class="ticket"><a href="<?cs var:result.href ?>" title="View ticket"><?cs
+     if:header.name == 'id' ?>
+      <td class="ticket"><a href="<?cs var:result.href ?>" title="View ticket">#<?cs
         var:result.id ?></a></td><?cs
      else ?>
       <td><?cs if:header.name == 'summary' ?>
=== templates/timeline.cs
==================================================================
--- templates/timeline.cs   (/mirrors/trac/trunk)   (revision 1120)
+++ templates/timeline.cs   (/local/trunk/trac)   (revision 1120)
@@ -18,7 +18,10 @@
    <?cs if:trac.acl.TICKET_VIEW ?><div class="field">
     <input type="checkbox" id="ticket" name="ticket" <?cs
       if:timeline.ticket ?>checked="checked"<?cs /if ?> />
-    <label for="ticket">Ticket changes</label>
+    <label for="ticket">Ticket status changes</label>
+    <input type="checkbox" id="ticket_comments" name="ticket_comments" <?cs
+      if:timeline.ticket_comments ?>checked="checked"<?cs /if ?> />
+    <label for="ticket_comments">Ticket contributions</label>
    </div><?cs /if ?>
    <?cs if:trac.acl.CHANGESET_VIEW ?><div class="field">
     <input type="checkbox" id="changeset" name="changeset" <?cs
@@ -55,7 +58,7 @@
   <a href="<?cs var:url ?>"><span class="time"><?cs
     var:item.time ?> :</span> <?cs var:msg ?></a>
  </dt>
- <?cs if:descr ?><dd><?cs var:descr ?></dd><?cs /if ?>
+ <?cs if:descr ?><dd class="<?cs var:type ?>"><?cs var:descr ?></dd><?cs /if ?>
 <?cs /def ?>
 
 <?cs each:item = timeline.items ?>
@@ -65,7 +68,7 @@
     'Changeset <em>['+$item.idata+']</em> by '+$item.author,$item.node_list+item.message) ?>
  <?cs elif:item.type == #2 ?><!-- New ticket -->
   <?cs call:tlitem(item.href, 'newticket',
-    'Ticket <em>#'+$item.idata+'</em> created by '+$item.author, item.message) ?>
+    $item.category+' Ticket <em>#'+$item.idata+'</em> created by '+$item.author, item.message) ?>
  <?cs elif:item.type == #3 ?><!-- Closed ticket -->
   <?cs if:item.message ?>
    <?cs set:imessage = ' - ' + $item.message ?>
@@ -73,17 +76,20 @@
    <?cs set:imessage = '' ?>
   <?cs /if ?>
   <?cs call:tlitem(item.href, 'closedticket',
-    'Ticket <em>#'+$item.idata+'</em> resolved by '+$item.author, 
+    $item.category+' Ticket <em>#'+$item.idata+'</em> resolved by '+$item.author, 
     $item.tdata+$imessage) ?>
  <?cs elif:item.type == #4 ?><!-- Reopened ticket -->
   <?cs call:tlitem(item.href, 'newticket',
-    'Ticket <em>#'+$item.idata+'</em> reopened by '+$item.author, '') ?>
+    $item.category+' Ticket <em>#'+$item.idata+'</em> reopened by '+$item.author, '') ?>
  <?cs elif:item.type == #5 ?><!-- Wiki change -->
   <?cs call:tlitem(item.href, 'wiki',
     '<em>'+$item.tdata+'</em> edited by '+$item.author, item.message) ?>
  <?cs elif:item.type == #6 ?><!-- milestone -->
   <?cs call:tlitem(item.href, 'milestone',
     '<em>Milestone '+$item.message+'</em> reached', '') ?>
+ <?cs elif:item.type == #7 ?><!-- Ticket comment -->
+  <?cs call:tlitem(item.href, 'ticketcomment',
+    $item.category+' Ticket <em>#'+$item.idata+'</em> contribution by '+$item.author, $item.message) ?>
  <?cs /if ?>
 <?cs /each ?>
 <?cs if:len(timeline.items) ?></dl><?cs /if ?>
=== exp-tracd.sh
==================================================================
--- exp-tracd.sh   (/mirrors/trac/trunk)   (revision 1120)
+++ exp-tracd.sh   (/local/trunk/trac)   (revision 1120)
@@ -0,0 +1,30 @@
+TRAC_HOME=/opt/trac-0.8exp
+TRAC_ENV=/volume/srv/trac/exp-svk-trac
+TRAC_ENV2=/volume/srv/trac/exp-bct-trac
+TRAC_PORT=9999 
+
+TRAC_DIGEST=/volume/srv/trac/htdigest.BCT
+
+# This sets up the environment needed to run tracd
+export PYTHONPATH=\
+____:$TRAC_HOME/lib/python2.3/site-packages:\
+____:/opt/subversion-1.1.0/lib/svn-python:\
+____:/opt/SilverCity-0.9.5/lib/python2.3/site-packages:\
+____:$PYTHONPATH
+
+
+# Use SQLite3
+export SQLITE3_HOME=/opt/sqlite-3.0.8
+export PATH=$SQLITE3_HOME/bin:$PATH
+export LD_LIBRARY_PATH=$SQLITE3_HOME/lib:$LD_LIBRARY_PATH
+
+# Locate the proper tracd
+export PATH=$TRAC_HOME/bin:$PATH
+
+
+# Now, run tracd:
+tracd  -p $TRAC_PORT -a `basename $TRAC_ENV`,$TRAC_DIGEST,BCT \
+ -a `basename $TRAC_ENV2`,$TRAC_DIGEST,BCT \
+    $TRAC_ENV $TRAC_ENV2 
+
+

Property changes on: exp-tracd.sh
___________________________________________________________________
Name: svn:executable
 +*

=== BCT_install.sh
==================================================================
--- BCT_install.sh   (/mirrors/trac/trunk)   (revision 1120)
+++ BCT_install.sh   (/local/trunk/trac)   (revision 1120)
@@ -0,0 +1,4 @@
+TRAC_HOME=/opt/trac-0.8exp
+python setup.py install --prefix=$TRAC_HOME
+
+

Property changes on: BCT_install.sh
___________________________________________________________________
Name: svn:executable
 +*


Property changes on: 
___________________________________________________________________
Name: svk:merge
 +af82e41b-90c4-0310-8c96-b1721e28e2e2:/trunk:1060
  ecde85df-87dc-0310-ab16-a9b1c1177255:/local/trac/trunk:796
--- trac/upgrades/db8.py   (/mirrors/trac/trunk)   (revision 1120)
+++ trac/upgrades/db8.py   (/local/trunk/trac)   (revision 1120)
@@ -0,0 +1,35 @@
+sql = """
+-- Add category to 'ticket'
+CREATE TEMP TABLE ticket_old AS SELECT * FROM ticket;
+DROP TABLE ticket;
+CREATE TABLE ticket (
+        id              integer PRIMARY KEY,
+        category        text,           -- the nature of the ticket
+        time            integer,        -- the time it was created
+        changetime      integer,
+        component       text,
+        severity        text,
+        priority        text,
+        owner           text,           -- who is this ticket assigned to
+        reporter        text,
+        cc              text,           -- email addresses to notify
+        url             text,           -- url related to this ticket
+        version         text,           --
+        milestone       text,           --
+        status          text,
+        resolution      text,
+        summary         text,           -- one-line summary
+        description     text,           -- problem description (long)
+        keywords        text
+);
+INSERT INTO ticket(id, category, time, changetime, component,
+                   severity, priority, owner, reporter, cc, url, version,
+                   milestone, status, resolution, summary, description, keywords)
+  SELECT id, '', time, changetime, component,
+         severity, priority, owner, reporter, cc, url, version,
+         milestone, status, resolution, summary, description, keywords FROM ticket_old;
+
+"""
+
+def do_upgrade(env, ver, cursor):
+    cursor.execute(sql)


