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