| 1 | # svntrac
|
|---|
| 2 | #
|
|---|
| 3 | # Copyright (C) 2003 Xyche Software
|
|---|
| 4 | #
|
|---|
| 5 | # svntrac is free software; you can redistribute it and/or
|
|---|
| 6 | # modify it under the terms of the GNU General Public License as
|
|---|
| 7 | # published by the Free Software Foundation; either version 2 of the
|
|---|
| 8 | # License, or (at your option) any later version.
|
|---|
| 9 | #
|
|---|
| 10 | # svntrac is distributed in the hope that it will be useful,
|
|---|
| 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|---|
| 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|---|
| 13 | # General Public License for more details.
|
|---|
| 14 | #
|
|---|
| 15 | # You should have received a copy of the GNU General Public License
|
|---|
| 16 | # along with this program; if not, write to the Free Software
|
|---|
| 17 | # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|---|
| 18 | #
|
|---|
| 19 | # Author: Jonas Borgström <jonas@xyche.com>
|
|---|
| 20 |
|
|---|
| 21 | import re
|
|---|
| 22 | import time
|
|---|
| 23 | import os
|
|---|
| 24 | import StringIO
|
|---|
| 25 | import auth
|
|---|
| 26 | import perm
|
|---|
| 27 | from Module import Module
|
|---|
| 28 | from db import get_connection
|
|---|
| 29 | from util import *
|
|---|
| 30 | from xml.sax.saxutils import quoteattr, escape
|
|---|
| 31 |
|
|---|
| 32 |
|
|---|
| 33 | h1_re = re.compile ('!!!(.*)!!!')
|
|---|
| 34 | h2_re = re.compile ('!!(.*)!!')
|
|---|
| 35 | h3_re = re.compile ('!(.*)!')
|
|---|
| 36 | url_re = re.compile ('((((new|(ht|f)tp)s?://))([a-z0-9_-]+:[a-z0-9_-]+\@)?([a-z0-9]+(\-*[a-z0-9]+)*\.)+[a-z]{2,7}(/~?[a-z0-9_\.%\-]+)?(/[a-z0-9_\.%\-]+)*(/?[a-z0-9_\%\-]+(\.[a-z0-9]+)?(\#[a-zA-Z0-9_\.]+)?)(\?([a-z0-9_\.\%\-]+)\=[a-z0-9_\.\%\/\-]*)?(&([a-z0-9_\.\%\-]+)\=[a-z0-9_\.\%\/\-]*)?(#[a-z0-9]+)?)')
|
|---|
| 37 | oldstylelink_re = re.compile ('([[A-Z][a-z]*(?:[A-Z][a-z]+)+)')
|
|---|
| 38 | newstylelink_re = re.compile ('\[([^]*])\]')
|
|---|
| 39 | list_re = re.compile ('^(([\*#])\\2*) (.*)$')
|
|---|
| 40 | newline_re = re.compile ('(%%%)')
|
|---|
| 41 | strong_re = re.compile ('\__([^ ]+)\__')
|
|---|
| 42 | emph_re = re.compile ("''([^ ]+)''")
|
|---|
| 43 | empty_line_re = re.compile ("^[ ]*$")
|
|---|
| 44 | indented_re = re.compile ("^[ ]")
|
|---|
| 45 |
|
|---|
| 46 | def format_wiki (text, out):
|
|---|
| 47 | """
|
|---|
| 48 | some basic wiki style text formatting
|
|---|
| 49 | """
|
|---|
| 50 | def set_list_depth (stack, type, depth):
|
|---|
| 51 | listdepth = len(stack)
|
|---|
| 52 | diff = depth - listdepth
|
|---|
| 53 | if diff > 0:
|
|---|
| 54 | for i in range (diff):
|
|---|
| 55 | out.write ('<%s>' % type)
|
|---|
| 56 | stack.append('</%s>' % type)
|
|---|
| 57 | elif diff < 0:
|
|---|
| 58 | for i in range (-diff):
|
|---|
| 59 | out.write (stack.pop())
|
|---|
| 60 | if depth > 0 and stack[0][2:4] != type:
|
|---|
| 61 | out.write (stack.pop())
|
|---|
| 62 | out.write ('<%s>' % type)
|
|---|
| 63 | stack.append('</%s>' % type)
|
|---|
| 64 |
|
|---|
| 65 | def handle_links (line):
|
|---|
| 66 | line = oldstylelink_re.sub('<a href="?mode=wiki&page=\\1">\\1</a>', line)
|
|---|
| 67 | line = url_re.sub('<a href="\\1">\\1</a>', line)
|
|---|
| 68 | return line
|
|---|
| 69 |
|
|---|
| 70 | newp = 1
|
|---|
| 71 | inverb = 0
|
|---|
| 72 | liststack = []
|
|---|
| 73 |
|
|---|
| 74 | for line in text.splitlines():
|
|---|
| 75 | line = escape(line)
|
|---|
| 76 | if empty_line_re.match(line):
|
|---|
| 77 | if not newp:
|
|---|
| 78 | newp = 1
|
|---|
| 79 | set_list_depth (liststack, None, 0)
|
|---|
| 80 | if inverb:
|
|---|
| 81 | inverb = 0
|
|---|
| 82 | out.write ('</pre>\n')
|
|---|
| 83 | out.write ('<p>\n')
|
|---|
| 84 | continue
|
|---|
| 85 | if newp and indented_re.match(line):
|
|---|
| 86 | out.write('<pre>\n')
|
|---|
| 87 | inverb = 1
|
|---|
| 88 | newp = 0
|
|---|
| 89 |
|
|---|
| 90 | match = list_re.match(line)
|
|---|
| 91 | if match:
|
|---|
| 92 | depth = len(match.group(1))
|
|---|
| 93 | if match.group(2) == '#':
|
|---|
| 94 | type = 'ol'
|
|---|
| 95 | else:
|
|---|
| 96 | type = 'ul'
|
|---|
| 97 | set_list_depth(liststack, type, depth)
|
|---|
| 98 | line = list_re.sub('<li>\\3', line)
|
|---|
| 99 |
|
|---|
| 100 | line = newline_re.sub('<br>', line)
|
|---|
| 101 | line = handle_links(line)
|
|---|
| 102 | line = h1_re.sub('<h1>\\1</h1>', line)
|
|---|
| 103 | line = h2_re.sub('<h2>\\1</h2>', line)
|
|---|
| 104 | line = h3_re.sub('<h3>\\1</h3>', line)
|
|---|
| 105 | line = strong_re.sub('<strong>\\1</strong>', line)
|
|---|
| 106 | line = emph_re.sub('<em>\\1</em>', line)
|
|---|
| 107 | out.write(line + '\n')
|
|---|
| 108 | if inverb:
|
|---|
| 109 | out.write ('</pre>\n')
|
|---|
| 110 |
|
|---|
| 111 |
|
|---|
| 112 | class Page:
|
|---|
| 113 | def __init__(self, name, version):
|
|---|
| 114 |
|
|---|
| 115 | self.name = name
|
|---|
| 116 | cnx = get_connection ()
|
|---|
| 117 | cursor = cnx.cursor ()
|
|---|
| 118 | if version:
|
|---|
| 119 | cursor.execute ('SELECT version, text FROM wiki '
|
|---|
| 120 | 'WHERE name=%s AND version=%s',
|
|---|
| 121 | name, version)
|
|---|
| 122 | else:
|
|---|
| 123 | cursor.execute ('SELECT version, text FROM wiki '
|
|---|
| 124 | 'WHERE name=%s ORDER BY version DESC LIMIT 1', name)
|
|---|
| 125 | row = cursor.fetchone()
|
|---|
| 126 | if row:
|
|---|
| 127 | self.new = 0
|
|---|
| 128 | self.version = int(row[0])
|
|---|
| 129 | self.text = row[1]
|
|---|
| 130 | else:
|
|---|
| 131 | self.version = 0
|
|---|
| 132 | self.text = 'describe %s here' % name
|
|---|
| 133 | self.new = 1
|
|---|
| 134 |
|
|---|
| 135 | def set_content (self, text):
|
|---|
| 136 | self.text = text
|
|---|
| 137 | self.version = self.version + 1
|
|---|
| 138 |
|
|---|
| 139 | def commit (self):
|
|---|
| 140 | if self.new:
|
|---|
| 141 | perm.assert_permission (perm.WIKI_CREATE)
|
|---|
| 142 | else:
|
|---|
| 143 | perm.assert_permission (perm.WIKI_MODIFY)
|
|---|
| 144 | cnx = get_connection ()
|
|---|
| 145 | cursor = cnx.cursor ()
|
|---|
| 146 | cursor.execute ('SELECT MAX(version)+1 FROM '
|
|---|
| 147 | '(SELECT version FROM wiki WHERE name=%s '
|
|---|
| 148 | 'UNION ALL SELECT 0 as version)', self.name)
|
|---|
| 149 | row = cursor.fetchone()
|
|---|
| 150 | new_version = int(row[0])
|
|---|
| 151 |
|
|---|
| 152 | cursor.execute ('INSERT INTO WIKI '
|
|---|
| 153 | '(name, version, time, author, ipnr, locked, text) '
|
|---|
| 154 | 'VALUES (%s, %s, %s, %s, %s, %s, %s)',
|
|---|
| 155 | self.name, new_version, int(time.time()),
|
|---|
| 156 | auth.get_authname(), os.getenv('REMOTE_ADDR'),
|
|---|
| 157 | 0, self.text)
|
|---|
| 158 | cnx.commit ()
|
|---|
| 159 |
|
|---|
| 160 | def render_edit(self, out):
|
|---|
| 161 | perm.assert_permission (perm.WIKI_MODIFY)
|
|---|
| 162 | out.write ('<h3>source</h3>')
|
|---|
| 163 | out.write ('<form action="svntrac.cgi" method="POST">')
|
|---|
| 164 | out.write ('<input type="hidden" name="page" value="%s">' % self.name)
|
|---|
| 165 | out.write ('<input type="hidden" name="mode" value="wiki">')
|
|---|
| 166 | out.write ('<textarea name="text" rows="15" cols="80">')
|
|---|
| 167 | out.write(escape(self.text))
|
|---|
| 168 | out.write ('</textarea><p>')
|
|---|
| 169 | out.write ('<input type="submit" name="action" value="preview"> ')
|
|---|
| 170 | out.write ('<input type="submit" name="action" value="commit">')
|
|---|
| 171 | out.write ('</form>')
|
|---|
| 172 |
|
|---|
| 173 | def render_view(self, out, edit_button=1):
|
|---|
| 174 | self.render_history(out)
|
|---|
| 175 | perm.assert_permission (perm.WIKI_VIEW)
|
|---|
| 176 | out.write ('<div class="wikipage">')
|
|---|
| 177 | format_wiki(self.text, out)
|
|---|
| 178 | out.write ('</div>')
|
|---|
| 179 | if edit_button and perm.has_permission (perm.WIKI_MODIFY):
|
|---|
| 180 | out.write ('<form action="svntrac.cgi" method="POST">')
|
|---|
| 181 | out.write ('<input type="hidden" name="mode" value="wiki">')
|
|---|
| 182 | out.write ('<input type="hidden" name="page" value="%s">' % self.name)
|
|---|
| 183 | out.write ('<input type="submit" name="action" value=" edit ">')
|
|---|
| 184 | out.write ('</form>')
|
|---|
| 185 |
|
|---|
| 186 | def render_preview (self, out):
|
|---|
| 187 | perm.assert_permission (perm.WIKI_MODIFY)
|
|---|
| 188 |
|
|---|
| 189 | out.write ('<h3>preview</h3>')
|
|---|
| 190 | self.render_view (out, edit_button=0)
|
|---|
| 191 | self.render_edit (out)
|
|---|
| 192 |
|
|---|
| 193 | def render_history (self, out):
|
|---|
| 194 | cnx = get_connection ()
|
|---|
| 195 | cursor = cnx.cursor ()
|
|---|
| 196 |
|
|---|
| 197 | cursor.execute ('SELECT version, time, author, ipnr FROM wiki '
|
|---|
| 198 | 'WHERE name=%s ORDER BY version DESC', self.name)
|
|---|
| 199 |
|
|---|
| 200 | out.write ('<div align="right">'
|
|---|
| 201 | '<a href=\"javascript:view_history()\">show/hide history</a>'
|
|---|
| 202 | '</div>')
|
|---|
| 203 | out.write ('<table class="wiki-history" id="history">')
|
|---|
| 204 | out.write ('<tr class="wiki-history-header"><th>version</th>'
|
|---|
| 205 | '<th>time</th><th>author</th><th>ipnr</th></tr>')
|
|---|
| 206 | while 1:
|
|---|
| 207 | row = cursor.fetchone()
|
|---|
| 208 | if row == None:
|
|---|
| 209 | break
|
|---|
| 210 | # for row in cursor:
|
|---|
| 211 | t = int(row[1])
|
|---|
| 212 | if t:
|
|---|
| 213 | time_str = time.strftime('%F', time.localtime(t))
|
|---|
| 214 | else:
|
|---|
| 215 | time_str = ''
|
|---|
| 216 | out.write ('<tr><td><a href="%s">%s</a></td><td>%s</td>'
|
|---|
| 217 | '<td>%s</td><td>%s</td></tr>'
|
|---|
| 218 | % (wiki_href(self.name, row[0]), row[0], time_str,
|
|---|
| 219 | row[2], row[3]))
|
|---|
| 220 | out.write ('</table>')
|
|---|
| 221 |
|
|---|
| 222 | class Wiki (Module):
|
|---|
| 223 | template_key = 'wiki_template'
|
|---|
| 224 |
|
|---|
| 225 | def render(self):
|
|---|
| 226 | if self.args.has_key('page'):
|
|---|
| 227 | name = self.args['page']
|
|---|
| 228 | else:
|
|---|
| 229 | name = 'WikiStart'
|
|---|
| 230 | if self.args.has_key('version'):
|
|---|
| 231 | version = self.args['version']
|
|---|
| 232 | else:
|
|---|
| 233 | version = 0
|
|---|
| 234 |
|
|---|
| 235 | if self.args.has_key('action'):
|
|---|
| 236 | action = self.args['action']
|
|---|
| 237 | else:
|
|---|
| 238 | action = 'view'
|
|---|
| 239 |
|
|---|
| 240 | page = Page(name, version)
|
|---|
| 241 |
|
|---|
| 242 | if self.args.has_key('text'):
|
|---|
| 243 | page.set_content (self.args['text'])
|
|---|
| 244 |
|
|---|
| 245 | out = StringIO.StringIO()
|
|---|
| 246 | if action == 'commit':
|
|---|
| 247 | page.commit ()
|
|---|
| 248 | redirect (wiki_href (page.name))
|
|---|
| 249 | elif action == ' edit ':
|
|---|
| 250 | out.write ('<h2>edit <a href="%s">%s</a></h2>' %
|
|---|
| 251 | (wiki_href(page.name), page.name))
|
|---|
| 252 | page.render_edit (out)
|
|---|
| 253 | self.namespace['title'] = 'wiki - edit'
|
|---|
| 254 | elif action == 'preview':
|
|---|
| 255 | out.write ('<h2>edit <a href="%s">%s</a></h2>' %
|
|---|
| 256 | (wiki_href(page.name), page.name))
|
|---|
| 257 | page.render_preview (out)
|
|---|
| 258 | self.namespace['title'] = 'wiki - preview'
|
|---|
| 259 | else:
|
|---|
| 260 | out.write ('<h2><a href="%s">%s</a></h2>' %
|
|---|
| 261 | (wiki_href(page.name), page.name))
|
|---|
| 262 | page.render_view (out)
|
|---|
| 263 | self.namespace['title'] = 'wiki - view'
|
|---|
| 264 | self.namespace['content'] = out.getvalue()
|
|---|
| 265 |
|
|---|