##// END OF EJS Templates
Merged in domruf/rhodecode (pull request #66)
marcink -
r2735:9d8f63ff merge beta
parent child Browse files
Show More
@@ -1,330 +1,337 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 10 pdebug = false
11 11 ################################################################################
12 12 ## Uncomment and replace with the address which should receive ##
13 13 ## any error reports after application crash ##
14 14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 15 ################################################################################
16 16 #email_to = admin@localhost
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 20 #email_prefix = [RhodeCode]
21 21
22 22 #smtp_server = mail.server.com
23 23 #smtp_username =
24 24 #smtp_password =
25 25 #smtp_port =
26 26 #smtp_use_tls = false
27 27 #smtp_use_ssl = true
28 28 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 29 #smtp_auth =
30 30
31 31 [server:main]
32 32 ##nr of threads to spawn
33 33 #threadpool_workers = 5
34 34
35 35 ##max request before thread respawn
36 36 #threadpool_max_requests = 10
37 37
38 38 ##option to use threads of process
39 39 #use_threadpool = true
40 40
41 41 #use = egg:Paste#http
42 42 use = egg:waitress#main
43 43 host = 127.0.0.1
44 44 port = 5000
45 45
46 46 [filter:proxy-prefix]
47 47 # prefix middleware for rc
48 48 use = egg:PasteDeploy#prefix
49 49 prefix = /<your-prefix>
50 50
51 51 [app:main]
52 52 use = egg:rhodecode
53 53 #filter-with = proxy-prefix
54 54 full_stack = true
55 55 static_files = true
56 56 # Optional Languages
57 57 # en, fr, ja, pt_BR, zh_CN, zh_TW
58 58 lang = en
59 59 cache_dir = %(here)s/data
60 60 index_dir = %(here)s/data/index
61 61 app_instance_uuid = ${app_instance_uuid}
62 62 cut_off_limit = 256000
63 63 force_https = false
64 64 commit_parse_limit = 50
65 65 use_gravatar = true
66 ## alternative_gravatar_url allows you to use your own avatar server application
67 ## the following parts of the URL will be replaced
68 ## %(email)s user email
69 ## %(md5email)s md5 hash of the user email (like at gravatar.com)
70 ## %(size)s size of the image that is expected from the server application
71 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/%(email)s/%(size)s
72 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/%(md5email)s?s=%(size)s
66 73 container_auth_enabled = false
67 74 proxypass_auth_enabled = false
68 75 default_encoding = utf8
69 76
70 77 ## overwrite schema of clone url
71 78 ## available vars:
72 79 ## scheme - http/https
73 80 ## user - current user
74 81 ## pass - password
75 82 ## netloc - network location
76 83 ## path - usually repo_name
77 84
78 85 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
79 86
80 87 ## issue tracking mapping for commits messages
81 88 ## comment out issue_pat, issue_server, issue_prefix to enable
82 89
83 90 ## pattern to get the issues from commit messages
84 91 ## default one used here is #<numbers> with a regex passive group for `#`
85 92 ## {id} will be all groups matched from this pattern
86 93
87 94 issue_pat = (?:\s*#)(\d+)
88 95
89 96 ## server url to the issue, each {id} will be replaced with match
90 97 ## fetched from the regex and {repo} is replaced with full repository name
91 98 ## including groups {repo_name} is replaced with just name of repo
92 99
93 100 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
94 101
95 102 ## prefix to add to link to indicate it's an url
96 103 ## #314 will be replaced by <issue_prefix><id>
97 104
98 105 issue_prefix = #
99 106
100 107 ## instance-id prefix
101 108 ## a prefix key for this instance used for cache invalidation when running
102 109 ## multiple instances of rhodecode, make sure it's globally unique for
103 110 ## all running rhodecode instances. Leave empty if you don't use it
104 111 instance_id =
105 112
106 113 ## alternative return HTTP header for failed authentication. Default HTTP
107 114 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
108 115 ## handling that. Set this variable to 403 to return HTTPForbidden
109 116 auth_ret_code =
110 117
111 118 ####################################
112 119 ### CELERY CONFIG ####
113 120 ####################################
114 121 use_celery = false
115 122 broker.host = localhost
116 123 broker.vhost = rabbitmqhost
117 124 broker.port = 5672
118 125 broker.user = rabbitmq
119 126 broker.password = qweqwe
120 127
121 128 celery.imports = rhodecode.lib.celerylib.tasks
122 129
123 130 celery.result.backend = amqp
124 131 celery.result.dburi = amqp://
125 132 celery.result.serialier = json
126 133
127 134 #celery.send.task.error.emails = true
128 135 #celery.amqp.task.result.expires = 18000
129 136
130 137 celeryd.concurrency = 2
131 138 #celeryd.log.file = celeryd.log
132 139 celeryd.log.level = debug
133 140 celeryd.max.tasks.per.child = 1
134 141
135 142 #tasks will never be sent to the queue, but executed locally instead.
136 143 celery.always.eager = false
137 144
138 145 ####################################
139 146 ### BEAKER CACHE ####
140 147 ####################################
141 148 beaker.cache.data_dir=%(here)s/data/cache/data
142 149 beaker.cache.lock_dir=%(here)s/data/cache/lock
143 150
144 151 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
145 152
146 153 beaker.cache.super_short_term.type=memory
147 154 beaker.cache.super_short_term.expire=10
148 155 beaker.cache.super_short_term.key_length = 256
149 156
150 157 beaker.cache.short_term.type=memory
151 158 beaker.cache.short_term.expire=60
152 159 beaker.cache.short_term.key_length = 256
153 160
154 161 beaker.cache.long_term.type=memory
155 162 beaker.cache.long_term.expire=36000
156 163 beaker.cache.long_term.key_length = 256
157 164
158 165 beaker.cache.sql_cache_short.type=memory
159 166 beaker.cache.sql_cache_short.expire=10
160 167 beaker.cache.sql_cache_short.key_length = 256
161 168
162 169 beaker.cache.sql_cache_med.type=memory
163 170 beaker.cache.sql_cache_med.expire=360
164 171 beaker.cache.sql_cache_med.key_length = 256
165 172
166 173 beaker.cache.sql_cache_long.type=file
167 174 beaker.cache.sql_cache_long.expire=3600
168 175 beaker.cache.sql_cache_long.key_length = 256
169 176
170 177 ####################################
171 178 ### BEAKER SESSION ####
172 179 ####################################
173 180 ## Type of storage used for the session, current types are
174 181 ## dbm, file, memcached, database, and memory.
175 182 ## The storage uses the Container API
176 183 ## that is also used by the cache system.
177 184
178 185 ## db session ##
179 186 #beaker.session.type = ext:database
180 187 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
181 188 #beaker.session.table_name = db_session
182 189
183 190 ## encrypted cookie client side session, good for many instances ##
184 191 #beaker.session.type = cookie
185 192
186 193 ## file based cookies (default) ##
187 194 #beaker.session.type = file
188 195
189 196
190 197 beaker.session.key = rhodecode
191 198 ## secure cookie requires AES python libraries ##
192 199 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
193 200 #beaker.session.validate_key = 9712sds2212c--zxc123
194 201 ## sets session as invalid if it haven't been accessed for given amount of time
195 202 beaker.session.timeout = 2592000
196 203 beaker.session.httponly = true
197 204 #beaker.session.cookie_path = /<your-prefix>
198 205
199 206 ## uncomment for https secure cookie ##
200 207 beaker.session.secure = false
201 208
202 209 ## auto save the session to not to use .save() ##
203 210 beaker.session.auto = False
204 211
205 212 ## default cookie expiration time in seconds `true` expire at browser close ##
206 213 #beaker.session.cookie_expires = 3600
207 214
208 215
209 216 ################################################################################
210 217 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
211 218 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
212 219 ## execute malicious code after an exception is raised. ##
213 220 ################################################################################
214 221 set debug = false
215 222
216 223 ##################################
217 224 ### LOGVIEW CONFIG ###
218 225 ##################################
219 226 logview.sqlalchemy = #faa
220 227 logview.pylons.templating = #bfb
221 228 logview.pylons.util = #eee
222 229
223 230 #########################################################
224 231 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
225 232 #########################################################
226 233
227 234 # SQLITE [default]
228 235 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
229 236
230 237 # POSTGRESQL
231 238 # sqlalchemy.db1.url = postgresql://user:pass@localhost/rhodecode
232 239
233 240 # MySQL
234 241 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
235 242
236 243 # see sqlalchemy docs for others
237 244
238 245 sqlalchemy.db1.echo = false
239 246 sqlalchemy.db1.pool_recycle = 3600
240 247 sqlalchemy.db1.convert_unicode = true
241 248
242 249 ################################
243 250 ### LOGGING CONFIGURATION ####
244 251 ################################
245 252 [loggers]
246 253 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
247 254
248 255 [handlers]
249 256 keys = console, console_sql
250 257
251 258 [formatters]
252 259 keys = generic, color_formatter, color_formatter_sql
253 260
254 261 #############
255 262 ## LOGGERS ##
256 263 #############
257 264 [logger_root]
258 265 level = NOTSET
259 266 handlers = console
260 267
261 268 [logger_routes]
262 269 level = DEBUG
263 270 handlers =
264 271 qualname = routes.middleware
265 272 # "level = DEBUG" logs the route matched and routing variables.
266 273 propagate = 1
267 274
268 275 [logger_beaker]
269 276 level = DEBUG
270 277 handlers =
271 278 qualname = beaker.container
272 279 propagate = 1
273 280
274 281 [logger_templates]
275 282 level = INFO
276 283 handlers =
277 284 qualname = pylons.templating
278 285 propagate = 1
279 286
280 287 [logger_rhodecode]
281 288 level = DEBUG
282 289 handlers =
283 290 qualname = rhodecode
284 291 propagate = 1
285 292
286 293 [logger_sqlalchemy]
287 294 level = INFO
288 295 handlers = console_sql
289 296 qualname = sqlalchemy.engine
290 297 propagate = 0
291 298
292 299 [logger_whoosh_indexer]
293 300 level = DEBUG
294 301 handlers =
295 302 qualname = whoosh_indexer
296 303 propagate = 1
297 304
298 305 ##############
299 306 ## HANDLERS ##
300 307 ##############
301 308
302 309 [handler_console]
303 310 class = StreamHandler
304 311 args = (sys.stderr,)
305 312 level = INFO
306 313 formatter = generic
307 314
308 315 [handler_console_sql]
309 316 class = StreamHandler
310 317 args = (sys.stderr,)
311 318 level = WARN
312 319 formatter = generic
313 320
314 321 ################
315 322 ## FORMATTERS ##
316 323 ################
317 324
318 325 [formatter_generic]
319 326 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
320 327 datefmt = %Y-%m-%d %H:%M:%S
321 328
322 329 [formatter_color_formatter]
323 330 class=rhodecode.lib.colored_formatter.ColorFormatter
324 331 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
325 332 datefmt = %Y-%m-%d %H:%M:%S
326 333
327 334 [formatter_color_formatter_sql]
328 335 class=rhodecode.lib.colored_formatter.ColorFormatterSql
329 336 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
330 337 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,1064 +1,1069 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 import StringIO
9 9 import urllib
10 10 import math
11 11 import logging
12 12 import re
13 13
14 14 from datetime import datetime
15 15 from pygments.formatters.html import HtmlFormatter
16 16 from pygments import highlight as code_highlight
17 17 from pylons import url, request, config
18 18 from pylons.i18n.translation import _, ungettext
19 19 from hashlib import md5
20 20
21 21 from webhelpers.html import literal, HTML, escape
22 22 from webhelpers.html.tools import *
23 23 from webhelpers.html.builder import make_tag
24 24 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
25 25 end_form, file, form, hidden, image, javascript_link, link_to, \
26 26 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
27 27 submit, text, password, textarea, title, ul, xml_declaration, radio
28 28 from webhelpers.html.tools import auto_link, button_to, highlight, \
29 29 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
30 30 from webhelpers.number import format_byte_size, format_bit_size
31 31 from webhelpers.pylonslib import Flash as _Flash
32 32 from webhelpers.pylonslib.secure_form import secure_form
33 33 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
34 34 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
35 35 replace_whitespace, urlify, truncate, wrap_paragraphs
36 36 from webhelpers.date import time_ago_in_words
37 37 from webhelpers.paginate import Page
38 38 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
39 39 convert_boolean_attrs, NotGiven, _make_safe_id_component
40 40
41 41 from rhodecode.lib.annotate import annotate_highlight
42 42 from rhodecode.lib.utils import repo_name_slug
43 43 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
44 44 get_changeset_safe, datetime_to_time, time_to_datetime
45 45 from rhodecode.lib.markup_renderer import MarkupRenderer
46 46 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
47 47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 48 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
49 49 from rhodecode.model.changeset_status import ChangesetStatusModel
50 50 from rhodecode.model.db import URL_SEP, Permission
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 html_escape_table = {
56 56 "&": "&amp;",
57 57 '"': "&quot;",
58 58 "'": "&apos;",
59 59 ">": "&gt;",
60 60 "<": "&lt;",
61 61 }
62 62
63 63
64 64 def html_escape(text):
65 65 """Produce entities within text."""
66 66 return "".join(html_escape_table.get(c,c) for c in text)
67 67
68 68
69 69 def shorter(text, size=20):
70 70 postfix = '...'
71 71 if len(text) > size:
72 72 return text[:size - len(postfix)] + postfix
73 73 return text
74 74
75 75
76 76 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
77 77 """
78 78 Reset button
79 79 """
80 80 _set_input_attrs(attrs, type, name, value)
81 81 _set_id_attr(attrs, id, name)
82 82 convert_boolean_attrs(attrs, ["disabled"])
83 83 return HTML.input(**attrs)
84 84
85 85 reset = _reset
86 86 safeid = _make_safe_id_component
87 87
88 88
89 89 def FID(raw_id, path):
90 90 """
91 91 Creates a uniqe ID for filenode based on it's hash of path and revision
92 92 it's safe to use in urls
93 93
94 94 :param raw_id:
95 95 :param path:
96 96 """
97 97
98 98 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
99 99
100 100
101 101 def get_token():
102 102 """Return the current authentication token, creating one if one doesn't
103 103 already exist.
104 104 """
105 105 token_key = "_authentication_token"
106 106 from pylons import session
107 107 if not token_key in session:
108 108 try:
109 109 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
110 110 except AttributeError: # Python < 2.4
111 111 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
112 112 session[token_key] = token
113 113 if hasattr(session, 'save'):
114 114 session.save()
115 115 return session[token_key]
116 116
117 117
118 118 class _GetError(object):
119 119 """Get error from form_errors, and represent it as span wrapped error
120 120 message
121 121
122 122 :param field_name: field to fetch errors for
123 123 :param form_errors: form errors dict
124 124 """
125 125
126 126 def __call__(self, field_name, form_errors):
127 127 tmpl = """<span class="error_msg">%s</span>"""
128 128 if form_errors and field_name in form_errors:
129 129 return literal(tmpl % form_errors.get(field_name))
130 130
131 131 get_error = _GetError()
132 132
133 133
134 134 class _ToolTip(object):
135 135
136 136 def __call__(self, tooltip_title, trim_at=50):
137 137 """
138 138 Special function just to wrap our text into nice formatted
139 139 autowrapped text
140 140
141 141 :param tooltip_title:
142 142 """
143 143 tooltip_title = escape(tooltip_title)
144 144 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
145 145 return tooltip_title
146 146 tooltip = _ToolTip()
147 147
148 148
149 149 class _FilesBreadCrumbs(object):
150 150
151 151 def __call__(self, repo_name, rev, paths):
152 152 if isinstance(paths, str):
153 153 paths = safe_unicode(paths)
154 154 url_l = [link_to(repo_name, url('files_home',
155 155 repo_name=repo_name,
156 156 revision=rev, f_path=''),
157 157 class_='ypjax-link')]
158 158 paths_l = paths.split('/')
159 159 for cnt, p in enumerate(paths_l):
160 160 if p != '':
161 161 url_l.append(link_to(p,
162 162 url('files_home',
163 163 repo_name=repo_name,
164 164 revision=rev,
165 165 f_path='/'.join(paths_l[:cnt + 1])
166 166 ),
167 167 class_='ypjax-link'
168 168 )
169 169 )
170 170
171 171 return literal('/'.join(url_l))
172 172
173 173 files_breadcrumbs = _FilesBreadCrumbs()
174 174
175 175
176 176 class CodeHtmlFormatter(HtmlFormatter):
177 177 """
178 178 My code Html Formatter for source codes
179 179 """
180 180
181 181 def wrap(self, source, outfile):
182 182 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
183 183
184 184 def _wrap_code(self, source):
185 185 for cnt, it in enumerate(source):
186 186 i, t = it
187 187 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
188 188 yield i, t
189 189
190 190 def _wrap_tablelinenos(self, inner):
191 191 dummyoutfile = StringIO.StringIO()
192 192 lncount = 0
193 193 for t, line in inner:
194 194 if t:
195 195 lncount += 1
196 196 dummyoutfile.write(line)
197 197
198 198 fl = self.linenostart
199 199 mw = len(str(lncount + fl - 1))
200 200 sp = self.linenospecial
201 201 st = self.linenostep
202 202 la = self.lineanchors
203 203 aln = self.anchorlinenos
204 204 nocls = self.noclasses
205 205 if sp:
206 206 lines = []
207 207
208 208 for i in range(fl, fl + lncount):
209 209 if i % st == 0:
210 210 if i % sp == 0:
211 211 if aln:
212 212 lines.append('<a href="#%s%d" class="special">%*d</a>' %
213 213 (la, i, mw, i))
214 214 else:
215 215 lines.append('<span class="special">%*d</span>' % (mw, i))
216 216 else:
217 217 if aln:
218 218 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
219 219 else:
220 220 lines.append('%*d' % (mw, i))
221 221 else:
222 222 lines.append('')
223 223 ls = '\n'.join(lines)
224 224 else:
225 225 lines = []
226 226 for i in range(fl, fl + lncount):
227 227 if i % st == 0:
228 228 if aln:
229 229 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
230 230 else:
231 231 lines.append('%*d' % (mw, i))
232 232 else:
233 233 lines.append('')
234 234 ls = '\n'.join(lines)
235 235
236 236 # in case you wonder about the seemingly redundant <div> here: since the
237 237 # content in the other cell also is wrapped in a div, some browsers in
238 238 # some configurations seem to mess up the formatting...
239 239 if nocls:
240 240 yield 0, ('<table class="%stable">' % self.cssclass +
241 241 '<tr><td><div class="linenodiv" '
242 242 'style="background-color: #f0f0f0; padding-right: 10px">'
243 243 '<pre style="line-height: 125%">' +
244 244 ls + '</pre></div></td><td id="hlcode" class="code">')
245 245 else:
246 246 yield 0, ('<table class="%stable">' % self.cssclass +
247 247 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
248 248 ls + '</pre></div></td><td id="hlcode" class="code">')
249 249 yield 0, dummyoutfile.getvalue()
250 250 yield 0, '</td></tr></table>'
251 251
252 252
253 253 def pygmentize(filenode, **kwargs):
254 254 """pygmentize function using pygments
255 255
256 256 :param filenode:
257 257 """
258 258
259 259 return literal(code_highlight(filenode.content,
260 260 filenode.lexer, CodeHtmlFormatter(**kwargs)))
261 261
262 262
263 263 def pygmentize_annotation(repo_name, filenode, **kwargs):
264 264 """
265 265 pygmentize function for annotation
266 266
267 267 :param filenode:
268 268 """
269 269
270 270 color_dict = {}
271 271
272 272 def gen_color(n=10000):
273 273 """generator for getting n of evenly distributed colors using
274 274 hsv color and golden ratio. It always return same order of colors
275 275
276 276 :returns: RGB tuple
277 277 """
278 278
279 279 def hsv_to_rgb(h, s, v):
280 280 if s == 0.0:
281 281 return v, v, v
282 282 i = int(h * 6.0) # XXX assume int() truncates!
283 283 f = (h * 6.0) - i
284 284 p = v * (1.0 - s)
285 285 q = v * (1.0 - s * f)
286 286 t = v * (1.0 - s * (1.0 - f))
287 287 i = i % 6
288 288 if i == 0:
289 289 return v, t, p
290 290 if i == 1:
291 291 return q, v, p
292 292 if i == 2:
293 293 return p, v, t
294 294 if i == 3:
295 295 return p, q, v
296 296 if i == 4:
297 297 return t, p, v
298 298 if i == 5:
299 299 return v, p, q
300 300
301 301 golden_ratio = 0.618033988749895
302 302 h = 0.22717784590367374
303 303
304 304 for _ in xrange(n):
305 305 h += golden_ratio
306 306 h %= 1
307 307 HSV_tuple = [h, 0.95, 0.95]
308 308 RGB_tuple = hsv_to_rgb(*HSV_tuple)
309 309 yield map(lambda x: str(int(x * 256)), RGB_tuple)
310 310
311 311 cgenerator = gen_color()
312 312
313 313 def get_color_string(cs):
314 314 if cs in color_dict:
315 315 col = color_dict[cs]
316 316 else:
317 317 col = color_dict[cs] = cgenerator.next()
318 318 return "color: rgb(%s)! important;" % (', '.join(col))
319 319
320 320 def url_func(repo_name):
321 321
322 322 def _url_func(changeset):
323 323 author = changeset.author
324 324 date = changeset.date
325 325 message = tooltip(changeset.message)
326 326
327 327 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
328 328 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
329 329 "</b> %s<br/></div>")
330 330
331 331 tooltip_html = tooltip_html % (author, date, message)
332 332 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
333 333 short_id(changeset.raw_id))
334 334 uri = link_to(
335 335 lnk_format,
336 336 url('changeset_home', repo_name=repo_name,
337 337 revision=changeset.raw_id),
338 338 style=get_color_string(changeset.raw_id),
339 339 class_='tooltip',
340 340 title=tooltip_html
341 341 )
342 342
343 343 uri += '\n'
344 344 return uri
345 345 return _url_func
346 346
347 347 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
348 348
349 349
350 350 def is_following_repo(repo_name, user_id):
351 351 from rhodecode.model.scm import ScmModel
352 352 return ScmModel().is_following_repo(repo_name, user_id)
353 353
354 354 flash = _Flash()
355 355
356 356 #==============================================================================
357 357 # SCM FILTERS available via h.
358 358 #==============================================================================
359 359 from rhodecode.lib.vcs.utils import author_name, author_email
360 360 from rhodecode.lib.utils2 import credentials_filter, age as _age
361 361 from rhodecode.model.db import User, ChangesetStatus
362 362
363 363 age = lambda x: _age(x)
364 364 capitalize = lambda x: x.capitalize()
365 365 email = author_email
366 366 short_id = lambda x: x[:12]
367 367 hide_credentials = lambda x: ''.join(credentials_filter(x))
368 368
369 369
370 370 def fmt_date(date):
371 371 if date:
372 372 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
373 373 return date.strftime(_fmt).decode('utf8')
374 374
375 375 return ""
376 376
377 377
378 378 def is_git(repository):
379 379 if hasattr(repository, 'alias'):
380 380 _type = repository.alias
381 381 elif hasattr(repository, 'repo_type'):
382 382 _type = repository.repo_type
383 383 else:
384 384 _type = repository
385 385 return _type == 'git'
386 386
387 387
388 388 def is_hg(repository):
389 389 if hasattr(repository, 'alias'):
390 390 _type = repository.alias
391 391 elif hasattr(repository, 'repo_type'):
392 392 _type = repository.repo_type
393 393 else:
394 394 _type = repository
395 395 return _type == 'hg'
396 396
397 397
398 398 def email_or_none(author):
399 399 # extract email from the commit string
400 400 _email = email(author)
401 401 if _email != '':
402 402 # check it against RhodeCode database, and use the MAIN email for this
403 403 # user
404 404 user = User.get_by_email(_email, case_insensitive=True, cache=True)
405 405 if user is not None:
406 406 return user.email
407 407 return _email
408 408
409 409 # See if it contains a username we can get an email from
410 410 user = User.get_by_username(author_name(author), case_insensitive=True,
411 411 cache=True)
412 412 if user is not None:
413 413 return user.email
414 414
415 415 # No valid email, not a valid user in the system, none!
416 416 return None
417 417
418 418
419 419 def person(author, show_attr="username_and_name"):
420 420 # attr to return from fetched user
421 421 person_getter = lambda usr: getattr(usr, show_attr)
422 422
423 423 # Valid email in the attribute passed, see if they're in the system
424 424 _email = email(author)
425 425 if _email != '':
426 426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 427 if user is not None:
428 428 return person_getter(user)
429 429 return _email
430 430
431 431 # Maybe it's a username?
432 432 _author = author_name(author)
433 433 user = User.get_by_username(_author, case_insensitive=True,
434 434 cache=True)
435 435 if user is not None:
436 436 return person_getter(user)
437 437
438 438 # Still nothing? Just pass back the author name then
439 439 return _author
440 440
441 441
442 442 def person_by_id(id_, show_attr="username_and_name"):
443 443 # attr to return from fetched user
444 444 person_getter = lambda usr: getattr(usr, show_attr)
445 445
446 446 #maybe it's an ID ?
447 447 if str(id_).isdigit() or isinstance(id_, int):
448 448 id_ = int(id_)
449 449 user = User.get(id_)
450 450 if user is not None:
451 451 return person_getter(user)
452 452 return id_
453 453
454 454
455 455 def desc_stylize(value):
456 456 """
457 457 converts tags from value into html equivalent
458 458
459 459 :param value:
460 460 """
461 461 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
462 462 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
463 463 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
464 464 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
465 465 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
466 466 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
467 467 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
468 468 '<div class="metatag" tag="lang">\\2</div>', value)
469 469 value = re.sub(r'\[([a-z]+)\]',
470 470 '<div class="metatag" tag="\\1">\\1</div>', value)
471 471
472 472 return value
473 473
474 474
475 475 def bool2icon(value):
476 476 """Returns True/False values represented as small html image of true/false
477 477 icons
478 478
479 479 :param value: bool value
480 480 """
481 481
482 482 if value is True:
483 483 return HTML.tag('img', src=url("/images/icons/accept.png"),
484 484 alt=_('True'))
485 485
486 486 if value is False:
487 487 return HTML.tag('img', src=url("/images/icons/cancel.png"),
488 488 alt=_('False'))
489 489
490 490 return value
491 491
492 492
493 493 def action_parser(user_log, feed=False):
494 494 """
495 495 This helper will action_map the specified string action into translated
496 496 fancy names with icons and links
497 497
498 498 :param user_log: user log instance
499 499 :param feed: use output for feeds (no html and fancy icons)
500 500 """
501 501
502 502 action = user_log.action
503 503 action_params = ' '
504 504
505 505 x = action.split(':')
506 506
507 507 if len(x) > 1:
508 508 action, action_params = x
509 509
510 510 def get_cs_links():
511 511 revs_limit = 3 # display this amount always
512 512 revs_top_limit = 50 # show upto this amount of changesets hidden
513 513 revs_ids = action_params.split(',')
514 514 deleted = user_log.repository is None
515 515 if deleted:
516 516 return ','.join(revs_ids)
517 517
518 518 repo_name = user_log.repository.repo_name
519 519
520 520 repo = user_log.repository.scm_instance
521 521
522 522 def lnk(rev, repo_name):
523 523
524 524 if isinstance(rev, BaseChangeset):
525 525 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
526 526 _url = url('changeset_home', repo_name=repo_name,
527 527 revision=rev.raw_id)
528 528 title = tooltip(rev.message)
529 529 else:
530 530 lbl = '%s' % rev
531 531 _url = '#'
532 532 title = _('Changeset not found')
533 533
534 534 return link_to(lbl, _url, title=title, class_='tooltip',)
535 535
536 536 revs = []
537 537 if len(filter(lambda v: v != '', revs_ids)) > 0:
538 538 for rev in revs_ids[:revs_top_limit]:
539 539 try:
540 540 rev = repo.get_changeset(rev)
541 541 revs.append(rev)
542 542 except ChangesetDoesNotExistError:
543 543 log.error('cannot find revision %s in this repo' % rev)
544 544 revs.append(rev)
545 545 continue
546 546 cs_links = []
547 547 cs_links.append(" " + ', '.join(
548 548 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
549 549 )
550 550 )
551 551
552 552 compare_view = (
553 553 ' <div class="compare_view tooltip" title="%s">'
554 554 '<a href="%s">%s</a> </div>' % (
555 555 _('Show all combined changesets %s->%s') % (
556 556 revs_ids[0], revs_ids[-1]
557 557 ),
558 558 url('changeset_home', repo_name=repo_name,
559 559 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
560 560 ),
561 561 _('compare view')
562 562 )
563 563 )
564 564
565 565 # if we have exactly one more than normally displayed
566 566 # just display it, takes less space than displaying
567 567 # "and 1 more revisions"
568 568 if len(revs_ids) == revs_limit + 1:
569 569 rev = revs[revs_limit]
570 570 cs_links.append(", " + lnk(rev, repo_name))
571 571
572 572 # hidden-by-default ones
573 573 if len(revs_ids) > revs_limit + 1:
574 574 uniq_id = revs_ids[0]
575 575 html_tmpl = (
576 576 '<span> %s <a class="show_more" id="_%s" '
577 577 'href="#more">%s</a> %s</span>'
578 578 )
579 579 if not feed:
580 580 cs_links.append(html_tmpl % (
581 581 _('and'),
582 582 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
583 583 _('revisions')
584 584 )
585 585 )
586 586
587 587 if not feed:
588 588 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
589 589 else:
590 590 html_tmpl = '<span id="%s"> %s </span>'
591 591
592 592 morelinks = ', '.join(
593 593 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
594 594 )
595 595
596 596 if len(revs_ids) > revs_top_limit:
597 597 morelinks += ', ...'
598 598
599 599 cs_links.append(html_tmpl % (uniq_id, morelinks))
600 600 if len(revs) > 1:
601 601 cs_links.append(compare_view)
602 602 return ''.join(cs_links)
603 603
604 604 def get_fork_name():
605 605 repo_name = action_params
606 606 return _('fork name ') + str(link_to(action_params, url('summary_home',
607 607 repo_name=repo_name,)))
608 608
609 609 def get_user_name():
610 610 user_name = action_params
611 611 return user_name
612 612
613 613 def get_users_group():
614 614 group_name = action_params
615 615 return group_name
616 616
617 617 def get_pull_request():
618 618 pull_request_id = action_params
619 619 repo_name = user_log.repository.repo_name
620 620 return link_to(_('Pull request #%s') % pull_request_id,
621 621 url('pullrequest_show', repo_name=repo_name,
622 622 pull_request_id=pull_request_id))
623 623
624 624 # action : translated str, callback(extractor), icon
625 625 action_map = {
626 626 'user_deleted_repo': (_('[deleted] repository'),
627 627 None, 'database_delete.png'),
628 628 'user_created_repo': (_('[created] repository'),
629 629 None, 'database_add.png'),
630 630 'user_created_fork': (_('[created] repository as fork'),
631 631 None, 'arrow_divide.png'),
632 632 'user_forked_repo': (_('[forked] repository'),
633 633 get_fork_name, 'arrow_divide.png'),
634 634 'user_updated_repo': (_('[updated] repository'),
635 635 None, 'database_edit.png'),
636 636 'admin_deleted_repo': (_('[delete] repository'),
637 637 None, 'database_delete.png'),
638 638 'admin_created_repo': (_('[created] repository'),
639 639 None, 'database_add.png'),
640 640 'admin_forked_repo': (_('[forked] repository'),
641 641 None, 'arrow_divide.png'),
642 642 'admin_updated_repo': (_('[updated] repository'),
643 643 None, 'database_edit.png'),
644 644 'admin_created_user': (_('[created] user'),
645 645 get_user_name, 'user_add.png'),
646 646 'admin_updated_user': (_('[updated] user'),
647 647 get_user_name, 'user_edit.png'),
648 648 'admin_created_users_group': (_('[created] users group'),
649 649 get_users_group, 'group_add.png'),
650 650 'admin_updated_users_group': (_('[updated] users group'),
651 651 get_users_group, 'group_edit.png'),
652 652 'user_commented_revision': (_('[commented] on revision in repository'),
653 653 get_cs_links, 'comment_add.png'),
654 654 'user_commented_pull_request': (_('[commented] on pull request for'),
655 655 get_pull_request, 'comment_add.png'),
656 656 'user_closed_pull_request': (_('[closed] pull request for'),
657 657 get_pull_request, 'tick.png'),
658 658 'push': (_('[pushed] into'),
659 659 get_cs_links, 'script_add.png'),
660 660 'push_local': (_('[committed via RhodeCode] into repository'),
661 661 get_cs_links, 'script_edit.png'),
662 662 'push_remote': (_('[pulled from remote] into repository'),
663 663 get_cs_links, 'connect.png'),
664 664 'pull': (_('[pulled] from'),
665 665 None, 'down_16.png'),
666 666 'started_following_repo': (_('[started following] repository'),
667 667 None, 'heart_add.png'),
668 668 'stopped_following_repo': (_('[stopped following] repository'),
669 669 None, 'heart_delete.png'),
670 670 }
671 671
672 672 action_str = action_map.get(action, action)
673 673 if feed:
674 674 action = action_str[0].replace('[', '').replace(']', '')
675 675 else:
676 676 action = action_str[0]\
677 677 .replace('[', '<span class="journal_highlight">')\
678 678 .replace(']', '</span>')
679 679
680 680 action_params_func = lambda: ""
681 681
682 682 if callable(action_str[1]):
683 683 action_params_func = action_str[1]
684 684
685 685 def action_parser_icon():
686 686 action = user_log.action
687 687 action_params = None
688 688 x = action.split(':')
689 689
690 690 if len(x) > 1:
691 691 action, action_params = x
692 692
693 693 tmpl = """<img src="%s%s" alt="%s"/>"""
694 694 ico = action_map.get(action, ['', '', ''])[2]
695 695 return literal(tmpl % ((url('/images/icons/')), ico, action))
696 696
697 697 # returned callbacks we need to call to get
698 698 return [lambda: literal(action), action_params_func, action_parser_icon]
699 699
700 700
701 701
702 702 #==============================================================================
703 703 # PERMS
704 704 #==============================================================================
705 705 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
706 706 HasRepoPermissionAny, HasRepoPermissionAll
707 707
708 708
709 709 #==============================================================================
710 710 # GRAVATAR URL
711 711 #==============================================================================
712 712
713 713 def gravatar_url(email_address, size=30):
714 if(str2bool(config['app_conf'].get('use_gravatar')) and
715 config['app_conf'].get('alternative_gravatar_url')):
716 return config['app_conf'].get('alternative_gravatar_url') % {'email': email_address,
717 'md5email': hashlib.md5(email_address.lower()).hexdigest(),
718 'size': size}
714 719 if (not str2bool(config['app_conf'].get('use_gravatar')) or
715 720 not email_address or email_address == 'anonymous@rhodecode.org'):
716 721 f = lambda a, l: min(l, key=lambda x: abs(x - a))
717 722 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
718 723
719 724 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
720 725 default = 'identicon'
721 726 baseurl_nossl = "http://www.gravatar.com/avatar/"
722 727 baseurl_ssl = "https://secure.gravatar.com/avatar/"
723 728 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
724 729
725 730 if isinstance(email_address, unicode):
726 731 #hashlib crashes on unicode items
727 732 email_address = safe_str(email_address)
728 733 # construct the url
729 734 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
730 735 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
731 736
732 737 return gravatar_url
733 738
734 739
735 740 #==============================================================================
736 741 # REPO PAGER, PAGER FOR REPOSITORY
737 742 #==============================================================================
738 743 class RepoPage(Page):
739 744
740 745 def __init__(self, collection, page=1, items_per_page=20,
741 746 item_count=None, url=None, **kwargs):
742 747
743 748 """Create a "RepoPage" instance. special pager for paging
744 749 repository
745 750 """
746 751 self._url_generator = url
747 752
748 753 # Safe the kwargs class-wide so they can be used in the pager() method
749 754 self.kwargs = kwargs
750 755
751 756 # Save a reference to the collection
752 757 self.original_collection = collection
753 758
754 759 self.collection = collection
755 760
756 761 # The self.page is the number of the current page.
757 762 # The first page has the number 1!
758 763 try:
759 764 self.page = int(page) # make it int() if we get it as a string
760 765 except (ValueError, TypeError):
761 766 self.page = 1
762 767
763 768 self.items_per_page = items_per_page
764 769
765 770 # Unless the user tells us how many items the collections has
766 771 # we calculate that ourselves.
767 772 if item_count is not None:
768 773 self.item_count = item_count
769 774 else:
770 775 self.item_count = len(self.collection)
771 776
772 777 # Compute the number of the first and last available page
773 778 if self.item_count > 0:
774 779 self.first_page = 1
775 780 self.page_count = int(math.ceil(float(self.item_count) /
776 781 self.items_per_page))
777 782 self.last_page = self.first_page + self.page_count - 1
778 783
779 784 # Make sure that the requested page number is the range of
780 785 # valid pages
781 786 if self.page > self.last_page:
782 787 self.page = self.last_page
783 788 elif self.page < self.first_page:
784 789 self.page = self.first_page
785 790
786 791 # Note: the number of items on this page can be less than
787 792 # items_per_page if the last page is not full
788 793 self.first_item = max(0, (self.item_count) - (self.page *
789 794 items_per_page))
790 795 self.last_item = ((self.item_count - 1) - items_per_page *
791 796 (self.page - 1))
792 797
793 798 self.items = list(self.collection[self.first_item:self.last_item + 1])
794 799
795 800 # Links to previous and next page
796 801 if self.page > self.first_page:
797 802 self.previous_page = self.page - 1
798 803 else:
799 804 self.previous_page = None
800 805
801 806 if self.page < self.last_page:
802 807 self.next_page = self.page + 1
803 808 else:
804 809 self.next_page = None
805 810
806 811 # No items available
807 812 else:
808 813 self.first_page = None
809 814 self.page_count = 0
810 815 self.last_page = None
811 816 self.first_item = None
812 817 self.last_item = None
813 818 self.previous_page = None
814 819 self.next_page = None
815 820 self.items = []
816 821
817 822 # This is a subclass of the 'list' type. Initialise the list now.
818 823 list.__init__(self, reversed(self.items))
819 824
820 825
821 826 def changed_tooltip(nodes):
822 827 """
823 828 Generates a html string for changed nodes in changeset page.
824 829 It limits the output to 30 entries
825 830
826 831 :param nodes: LazyNodesGenerator
827 832 """
828 833 if nodes:
829 834 pref = ': <br/> '
830 835 suf = ''
831 836 if len(nodes) > 30:
832 837 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
833 838 return literal(pref + '<br/> '.join([safe_unicode(x.path)
834 839 for x in nodes[:30]]) + suf)
835 840 else:
836 841 return ': ' + _('No Files')
837 842
838 843
839 844 def repo_link(groups_and_repos):
840 845 """
841 846 Makes a breadcrumbs link to repo within a group
842 847 joins &raquo; on each group to create a fancy link
843 848
844 849 ex::
845 850 group >> subgroup >> repo
846 851
847 852 :param groups_and_repos:
848 853 """
849 854 groups, repo_name = groups_and_repos
850 855
851 856 if not groups:
852 857 return repo_name
853 858 else:
854 859 def make_link(group):
855 860 return link_to(group.name, url('repos_group_home',
856 861 group_name=group.group_name))
857 862 return literal(' &raquo; '.join(map(make_link, groups)) + \
858 863 " &raquo; " + repo_name)
859 864
860 865
861 866 def fancy_file_stats(stats):
862 867 """
863 868 Displays a fancy two colored bar for number of added/deleted
864 869 lines of code on file
865 870
866 871 :param stats: two element list of added/deleted lines of code
867 872 """
868 873
869 874 a, d, t = stats[0], stats[1], stats[0] + stats[1]
870 875 width = 100
871 876 unit = float(width) / (t or 1)
872 877
873 878 # needs > 9% of width to be visible or 0 to be hidden
874 879 a_p = max(9, unit * a) if a > 0 else 0
875 880 d_p = max(9, unit * d) if d > 0 else 0
876 881 p_sum = a_p + d_p
877 882
878 883 if p_sum > width:
879 884 #adjust the percentage to be == 100% since we adjusted to 9
880 885 if a_p > d_p:
881 886 a_p = a_p - (p_sum - width)
882 887 else:
883 888 d_p = d_p - (p_sum - width)
884 889
885 890 a_v = a if a > 0 else ''
886 891 d_v = d if d > 0 else ''
887 892
888 893 def cgen(l_type):
889 894 mapping = {'tr': 'top-right-rounded-corner-mid',
890 895 'tl': 'top-left-rounded-corner-mid',
891 896 'br': 'bottom-right-rounded-corner-mid',
892 897 'bl': 'bottom-left-rounded-corner-mid'}
893 898 map_getter = lambda x: mapping[x]
894 899
895 900 if l_type == 'a' and d_v:
896 901 #case when added and deleted are present
897 902 return ' '.join(map(map_getter, ['tl', 'bl']))
898 903
899 904 if l_type == 'a' and not d_v:
900 905 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
901 906
902 907 if l_type == 'd' and a_v:
903 908 return ' '.join(map(map_getter, ['tr', 'br']))
904 909
905 910 if l_type == 'd' and not a_v:
906 911 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
907 912
908 913 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
909 914 cgen('a'), a_p, a_v
910 915 )
911 916 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
912 917 cgen('d'), d_p, d_v
913 918 )
914 919 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
915 920
916 921
917 922 def urlify_text(text_):
918 923 import re
919 924
920 925 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
921 926 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
922 927
923 928 def url_func(match_obj):
924 929 url_full = match_obj.groups()[0]
925 930 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
926 931
927 932 return literal(url_pat.sub(url_func, text_))
928 933
929 934
930 935 def urlify_changesets(text_, repository):
931 936 """
932 937 Extract revision ids from changeset and make link from them
933 938
934 939 :param text_:
935 940 :param repository:
936 941 """
937 942 import re
938 943 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
939 944
940 945 def url_func(match_obj):
941 946 rev = match_obj.groups()[0]
942 947 pref = ''
943 948 if match_obj.group().startswith(' '):
944 949 pref = ' '
945 950 tmpl = (
946 951 '%(pref)s<a class="%(cls)s" href="%(url)s">'
947 952 '%(rev)s'
948 953 '</a>'
949 954 )
950 955 return tmpl % {
951 956 'pref': pref,
952 957 'cls': 'revision-link',
953 958 'url': url('changeset_home', repo_name=repository, revision=rev),
954 959 'rev': rev,
955 960 }
956 961
957 962 newtext = URL_PAT.sub(url_func, text_)
958 963
959 964 return newtext
960 965
961 966
962 967 def urlify_commit(text_, repository=None, link_=None):
963 968 """
964 969 Parses given text message and makes proper links.
965 970 issues are linked to given issue-server, and rest is a changeset link
966 971 if link_ is given, in other case it's a plain text
967 972
968 973 :param text_:
969 974 :param repository:
970 975 :param link_: changeset link
971 976 """
972 977 import re
973 978 import traceback
974 979
975 980 def escaper(string):
976 981 return string.replace('<', '&lt;').replace('>', '&gt;')
977 982
978 983 def linkify_others(t, l):
979 984 urls = re.compile(r'(\<a.*?\<\/a\>)',)
980 985 links = []
981 986 for e in urls.split(t):
982 987 if not urls.match(e):
983 988 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
984 989 else:
985 990 links.append(e)
986 991
987 992 return ''.join(links)
988 993
989 994 # urlify changesets - extrac revisions and make link out of them
990 995 text_ = urlify_changesets(escaper(text_), repository)
991 996
992 997 try:
993 998 conf = config['app_conf']
994 999
995 1000 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
996 1001
997 1002 if URL_PAT:
998 1003 ISSUE_SERVER_LNK = conf.get('issue_server_link')
999 1004 ISSUE_PREFIX = conf.get('issue_prefix')
1000 1005
1001 1006 def url_func(match_obj):
1002 1007 pref = ''
1003 1008 if match_obj.group().startswith(' '):
1004 1009 pref = ' '
1005 1010
1006 1011 issue_id = ''.join(match_obj.groups())
1007 1012 tmpl = (
1008 1013 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1009 1014 '%(issue-prefix)s%(id-repr)s'
1010 1015 '</a>'
1011 1016 )
1012 1017 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1013 1018 if repository:
1014 1019 url = url.replace('{repo}', repository)
1015 1020 repo_name = repository.split(URL_SEP)[-1]
1016 1021 url = url.replace('{repo_name}', repo_name)
1017 1022 return tmpl % {
1018 1023 'pref': pref,
1019 1024 'cls': 'issue-tracker-link',
1020 1025 'url': url,
1021 1026 'id-repr': issue_id,
1022 1027 'issue-prefix': ISSUE_PREFIX,
1023 1028 'serv': ISSUE_SERVER_LNK,
1024 1029 }
1025 1030
1026 1031 newtext = URL_PAT.sub(url_func, text_)
1027 1032
1028 1033 if link_:
1029 1034 # wrap not links into final link => link_
1030 1035 newtext = linkify_others(newtext, link_)
1031 1036
1032 1037 return literal(newtext)
1033 1038 except:
1034 1039 log.error(traceback.format_exc())
1035 1040 pass
1036 1041
1037 1042 return text_
1038 1043
1039 1044
1040 1045 def rst(source):
1041 1046 return literal('<div class="rst-block">%s</div>' %
1042 1047 MarkupRenderer.rst(source))
1043 1048
1044 1049
1045 1050 def rst_w_mentions(source):
1046 1051 """
1047 1052 Wrapped rst renderer with @mention highlighting
1048 1053
1049 1054 :param source:
1050 1055 """
1051 1056 return literal('<div class="rst-block">%s</div>' %
1052 1057 MarkupRenderer.rst_with_mentions(source))
1053 1058
1054 1059
1055 1060 def changeset_status(repo, revision):
1056 1061 return ChangesetStatusModel().get_status(repo, revision)
1057 1062
1058 1063
1059 1064 def changeset_status_lbl(changeset_status):
1060 1065 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1061 1066
1062 1067
1063 1068 def get_permission_name(key):
1064 1069 return dict(Permission.PERMS).get(key)
General Comments 0
You need to be logged in to leave comments. Login now