##// END OF EJS Templates
Added message tooltip into journal revisions in push....
marcink -
r899:d65843e0 beta
parent child Browse files
Show More
@@ -1,273 +1,268 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.files
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Files controller for RhodeCode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27 import tempfile
28 28 import logging
29 29 import rhodecode.lib.helpers as h
30 30
31 31 from mercurial import archival
32 32
33 33 from pylons import request, response, session, tmpl_context as c, url
34 34 from pylons.i18n.translation import _
35 35 from pylons.controllers.util import redirect
36 36
37 37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 38 from rhodecode.lib.base import BaseController, render
39 39 from rhodecode.lib.utils import EmptyChangeset
40 40 from rhodecode.model.scm import ScmModel
41 41
42 42 from vcs.exceptions import RepositoryError, ChangesetError, ChangesetDoesNotExistError
43 43 from vcs.nodes import FileNode
44 44 from vcs.utils import diffs as differ
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48 class FilesController(BaseController):
49 49
50 50 @LoginRequired()
51 51 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
52 52 'repository.admin')
53 53 def __before__(self):
54 54 super(FilesController, self).__before__()
55 55 c.cut_off_limit = self.cut_off_limit
56 56
57 57 def index(self, repo_name, revision, f_path):
58 58 hg_model = ScmModel()
59 59 c.repo = hg_model.get_repo(c.repo_name)
60 60
61 61 try:
62 62 #reditect to given revision from form
63 63 post_revision = request.POST.get('at_rev', None)
64 64 if post_revision:
65 65 post_revision = c.repo.get_changeset(post_revision).raw_id
66 66 redirect(url('files_home', repo_name=c.repo_name,
67 67 revision=post_revision, f_path=f_path))
68 68
69 69 c.branch = request.GET.get('branch', None)
70 70
71 71 c.f_path = f_path
72 72
73 73 c.changeset = c.repo.get_changeset(revision)
74 74 cur_rev = c.changeset.revision
75 75
76 76 #prev link
77 77 try:
78 78 prev_rev = c.repo.get_changeset(cur_rev).prev(c.branch).raw_id
79 79 c.url_prev = url('files_home', repo_name=c.repo_name,
80 80 revision=prev_rev, f_path=f_path)
81 81 if c.branch:
82 82 c.url_prev += '?branch=%s' % c.branch
83 83 except ChangesetDoesNotExistError:
84 84 c.url_prev = '#'
85 85
86 86 #next link
87 87 try:
88 88 next_rev = c.repo.get_changeset(cur_rev).next(c.branch).raw_id
89 89 c.url_next = url('files_home', repo_name=c.repo_name,
90 90 revision=next_rev, f_path=f_path)
91 91 if c.branch:
92 92 c.url_next += '?branch=%s' % c.branch
93 93 except ChangesetDoesNotExistError:
94 94 c.url_next = '#'
95 95
96 96 #files
97 97 try:
98 98 c.files_list = c.changeset.get_node(f_path)
99 99 c.file_history = self._get_history(c.repo, c.files_list, f_path)
100 100 except RepositoryError, e:
101 101 h.flash(str(e), category='warning')
102 102 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
103 103
104 104 except RepositoryError, e:
105 105 h.flash(str(e), category='warning')
106 106 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
107 107
108 108
109 109
110 110 return render('files/files.html')
111 111
112 112 def rawfile(self, repo_name, revision, f_path):
113 113 hg_model = ScmModel()
114 114 c.repo = hg_model.get_repo(c.repo_name)
115 115 file_node = c.repo.get_changeset(revision).get_node(f_path)
116 116 response.content_type = file_node.mimetype
117 117 response.content_disposition = 'attachment; filename=%s' \
118 118 % f_path.split('/')[-1]
119 119 return file_node.content
120 120
121 121 def raw(self, repo_name, revision, f_path):
122 122 hg_model = ScmModel()
123 123 c.repo = hg_model.get_repo(c.repo_name)
124 124 file_node = c.repo.get_changeset(revision).get_node(f_path)
125 125 response.content_type = 'text/plain'
126 126
127 127 return file_node.content
128 128
129 129 def annotate(self, repo_name, revision, f_path):
130 130 hg_model = ScmModel()
131 131 c.repo = hg_model.get_repo(c.repo_name)
132 132
133 133 try:
134 134 c.cs = c.repo.get_changeset(revision)
135 135 c.file = c.cs.get_node(f_path)
136 136 except RepositoryError, e:
137 137 h.flash(str(e), category='warning')
138 138 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
139 139
140 140 c.file_history = self._get_history(c.repo, c.file, f_path)
141 141
142 142 c.f_path = f_path
143 143
144 144 return render('files/files_annotate.html')
145 145
146 146 def archivefile(self, repo_name, fname):
147 147 info = fname.split('.')
148 148 revision, fileformat = info[0], '.' + '.'.join(info[1:])
149 149 archive_specs = {
150 150 '.tar.bz2': ('application/x-tar', 'tbz2'),
151 151 '.tar.gz': ('application/x-tar', 'tgz'),
152 152 '.zip': ('application/zip', 'zip'),
153 153 }
154 154 if not archive_specs.has_key(fileformat):
155 155 return _('Unknown archive type %s') % fileformat
156 156
157 157 repo = ScmModel().get_repo(repo_name)
158 158
159 159 try:
160 160 repo.get_changeset(revision)
161 161 except ChangesetDoesNotExistError:
162 162 return _('Unknown revision %s') % revision
163 163
164 164 archive = tempfile.TemporaryFile()
165 165 localrepo = repo.repo
166 166 fname = '%s-%s%s' % (repo_name, revision, fileformat)
167 167 archival.archive(localrepo, archive, revision, archive_specs[fileformat][1],
168 168 prefix='%s-%s' % (repo_name, revision))
169 169 response.content_type = archive_specs[fileformat][0]
170 170 response.content_disposition = 'attachment; filename=%s' % fname
171 171 archive.seek(0)
172 172
173 173 def read_in_chunks(file_object, chunk_size=1024 * 40):
174 174 """Lazy function (generator) to read a file piece by piece.
175 175 Default chunk size: 40k."""
176 176 while True:
177 177 data = file_object.read(chunk_size)
178 178 if not data:
179 179 break
180 180 yield data
181 181
182 182 return read_in_chunks(archive)
183 183
184 184 def diff(self, repo_name, f_path):
185 185 hg_model = ScmModel()
186 186 diff1 = request.GET.get('diff1')
187 187 diff2 = request.GET.get('diff2')
188 188 c.action = request.GET.get('diff')
189 189 c.no_changes = diff1 == diff2
190 190 c.f_path = f_path
191 191 c.repo = hg_model.get_repo(c.repo_name)
192 192
193 193 try:
194 194 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
195 195 c.changeset_1 = c.repo.get_changeset(diff1)
196 196 node1 = c.changeset_1.get_node(f_path)
197 197 else:
198 198 c.changeset_1 = EmptyChangeset()
199 199 node1 = FileNode('.', '', changeset=c.changeset_1)
200 200
201 201 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
202 202 c.changeset_2 = c.repo.get_changeset(diff2)
203 203 node2 = c.changeset_2.get_node(f_path)
204 204 else:
205 205 c.changeset_2 = EmptyChangeset()
206 206 node2 = FileNode('.', '', changeset=c.changeset_2)
207 207 except RepositoryError:
208 208 return redirect(url('files_home',
209 209 repo_name=c.repo_name, f_path=f_path))
210 210
211 211 f_udiff = differ.get_udiff(node1, node2)
212 212 diff = differ.DiffProcessor(f_udiff)
213 213
214 214 if c.action == 'download':
215 215 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
216 216 response.content_type = 'text/plain'
217 217 response.content_disposition = 'attachment; filename=%s' \
218 218 % diff_name
219 219 return diff.raw_diff()
220 220
221 221 elif c.action == 'raw':
222 222 response.content_type = 'text/plain'
223 223 return diff.raw_diff()
224 224
225 225 elif c.action == 'diff':
226 226 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
227 227 c.cur_diff = _('Diff is to big to display')
228 228 else:
229 229 c.cur_diff = diff.as_html()
230 230 else:
231 231 #default option
232 232 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
233 233 c.cur_diff = _('Diff is to big to display')
234 234 else:
235 235 c.cur_diff = diff.as_html()
236 236
237 237 if not c.cur_diff: c.no_changes = True
238 238 return render('files/file_diff.html')
239 239
240 240 def _get_history(self, repo, node, f_path):
241 241 from vcs.nodes import NodeKind
242 242 if not node.kind is NodeKind.FILE:
243 243 return []
244 244 changesets = node.history
245 245 hist_l = []
246 246
247 247 changesets_group = ([], _("Changesets"))
248 248 branches_group = ([], _("Branches"))
249 249 tags_group = ([], _("Tags"))
250 250
251 251 for chs in changesets:
252 252 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
253 253 changesets_group[0].append((chs.raw_id, n_desc,))
254 254
255 255 hist_l.append(changesets_group)
256 256
257 257 for name, chs in c.repository_branches.items():
258 258 #chs = chs.split(':')[-1]
259 259 branches_group[0].append((chs, name),)
260 260 hist_l.append(branches_group)
261 261
262 262 for name, chs in c.repository_tags.items():
263 263 #chs = chs.split(':')[-1]
264 264 tags_group[0].append((chs, name),)
265 265 hist_l.append(tags_group)
266 266
267 267 return hist_l
268 268
269 # [
270 # ([("u1", "User1"), ("u2", "User2")], "Users"),
271 # ([("g1", "Group1"), ("g2", "Group2")], "Groups")
272 # ]
273
@@ -1,546 +1,547 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 from pygments.formatters import HtmlFormatter
9 9 from pygments import highlight as code_highlight
10 10 from pylons import url, app_globals as g
11 11 from pylons.i18n.translation import _, ungettext
12 12 from vcs.utils.annotate import annotate_highlight
13 13 from webhelpers.html import literal, HTML, escape
14 14 from webhelpers.html.tools import *
15 15 from webhelpers.html.builder import make_tag
16 16 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
17 17 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
18 18 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
19 19 password, textarea, title, ul, xml_declaration, radio
20 20 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
21 21 mail_to, strip_links, strip_tags, tag_re
22 22 from webhelpers.number import format_byte_size, format_bit_size
23 23 from webhelpers.pylonslib import Flash as _Flash
24 24 from webhelpers.pylonslib.secure_form import secure_form
25 25 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
26 26 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
27 27 replace_whitespace, urlify, truncate, wrap_paragraphs
28 28 from webhelpers.date import time_ago_in_words
29 29
30 30 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
31 31 convert_boolean_attrs, NotGiven
32 32
33 33 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
34 """Reset button
35 """
34 36 _set_input_attrs(attrs, type, name, value)
35 37 _set_id_attr(attrs, id, name)
36 38 convert_boolean_attrs(attrs, ["disabled"])
37 39 return HTML.input(**attrs)
38 40
39 41 reset = _reset
40 42
41 43
42 44 def get_token():
43 45 """Return the current authentication token, creating one if one doesn't
44 46 already exist.
45 47 """
46 48 token_key = "_authentication_token"
47 49 from pylons import session
48 50 if not token_key in session:
49 51 try:
50 52 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
51 53 except AttributeError: # Python < 2.4
52 54 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
53 55 session[token_key] = token
54 56 if hasattr(session, 'save'):
55 57 session.save()
56 58 return session[token_key]
57 59
58
59 #Custom helpers here :)
60 class _Link(object):
61 '''
62 Make a url based on label and url with help of url_for
63 :param label:name of link if not defined url is used
64 :param url: the url for link
65 '''
66
67 def __call__(self, label='', *url_, **urlargs):
68 if label is None or '':
69 label = url
70 link_fn = link_to(label, url(*url_, **urlargs))
71 return link_fn
72
73 link = _Link()
74
75 60 class _GetError(object):
61 """Get error from form_errors, and represent it as span wrapped error
62 message
63
64 :param field_name: field to fetch errors for
65 :param form_errors: form errors dict
66 """
76 67
77 68 def __call__(self, field_name, form_errors):
78 69 tmpl = """<span class="error_msg">%s</span>"""
79 70 if form_errors and form_errors.has_key(field_name):
80 71 return literal(tmpl % form_errors.get(field_name))
81 72
82 73 get_error = _GetError()
83 74
84 75 def recursive_replace(str, replace=' '):
85 """
86 Recursive replace of given sign to just one instance
76 """Recursive replace of given sign to just one instance
77
87 78 :param str: given string
88 :param replace:char to find and replace multiple instances
79 :param replace: char to find and replace multiple instances
89 80
90 81 Examples::
91 82 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
92 83 'Mighty-Mighty-Bo-sstones'
93 84 """
94 85
95 86 if str.find(replace * 2) == -1:
96 87 return str
97 88 else:
98 89 str = str.replace(replace * 2, replace)
99 90 return recursive_replace(str, replace)
100 91
101 92 class _ToolTip(object):
102 93
103 94 def __call__(self, tooltip_title, trim_at=50):
104 95 """
105 96 Special function just to wrap our text into nice formatted autowrapped
106 97 text
107 98 :param tooltip_title:
108 99 """
109 100
110 101 return wrap_paragraphs(escape(tooltip_title), trim_at)\
111 102 .replace('\n', '<br/>')
112 103
113 104 def activate(self):
114 105 """
115 106 Adds tooltip mechanism to the given Html all tooltips have to have
116 107 set class tooltip and set attribute tooltip_title.
117 108 Then a tooltip will be generated based on that
118 109 All with yui js tooltip
119 110 """
120 111
121 112 js = '''
122 113 YAHOO.util.Event.onDOMReady(function(){
123 114 function toolTipsId(){
124 115 var ids = [];
125 116 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
126 117
127 118 for (var i = 0; i < tts.length; i++) {
128 119 //if element doesn't not have and id autgenerate one for tooltip
129 120
130 121 if (!tts[i].id){
131 122 tts[i].id='tt'+i*100;
132 123 }
133 124 ids.push(tts[i].id);
134 125 }
135 126 return ids
136 127 };
137 128 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
138 129 context: toolTipsId(),
139 130 monitorresize:false,
140 131 xyoffset :[0,0],
141 132 autodismissdelay:300000,
142 133 hidedelay:5,
143 134 showdelay:20,
144 135 });
145 136
146 137 //Mouse Over event disabled for new repositories since they don't
147 138 //have last commit message
148 139 myToolTips.contextMouseOverEvent.subscribe(
149 140 function(type, args) {
150 141 var context = args[0];
151 142 var txt = context.getAttribute('tooltip_title');
152 143 if(txt){
153 144 return true;
154 145 }
155 146 else{
156 147 return false;
157 148 }
158 149 });
159 150
160 151
161 152 // Set the text for the tooltip just before we display it. Lazy method
162 153 myToolTips.contextTriggerEvent.subscribe(
163 154 function(type, args) {
164 155
165 156
166 157 var context = args[0];
167 158
168 159 var txt = context.getAttribute('tooltip_title');
169 160 this.cfg.setProperty("text", txt);
170 161
171 162
172 163 // positioning of tooltip
173 164 var tt_w = this.element.clientWidth;
174 165 var tt_h = this.element.clientHeight;
175 166
176 167 var context_w = context.offsetWidth;
177 168 var context_h = context.offsetHeight;
178 169
179 170 var pos_x = YAHOO.util.Dom.getX(context);
180 171 var pos_y = YAHOO.util.Dom.getY(context);
181 172
182 173 var display_strategy = 'top';
183 174 var xy_pos = [0,0];
184 175 switch (display_strategy){
185 176
186 177 case 'top':
187 178 var cur_x = (pos_x+context_w/2)-(tt_w/2);
188 179 var cur_y = pos_y-tt_h-4;
189 180 xy_pos = [cur_x,cur_y];
190 181 break;
191 182 case 'bottom':
192 183 var cur_x = (pos_x+context_w/2)-(tt_w/2);
193 184 var cur_y = pos_y+context_h+4;
194 185 xy_pos = [cur_x,cur_y];
195 186 break;
196 187 case 'left':
197 188 var cur_x = (pos_x-tt_w-4);
198 189 var cur_y = pos_y-((tt_h/2)-context_h/2);
199 190 xy_pos = [cur_x,cur_y];
200 191 break;
201 192 case 'right':
202 193 var cur_x = (pos_x+context_w+4);
203 194 var cur_y = pos_y-((tt_h/2)-context_h/2);
204 195 xy_pos = [cur_x,cur_y];
205 196 break;
206 197 default:
207 198 var cur_x = (pos_x+context_w/2)-(tt_w/2);
208 199 var cur_y = pos_y-tt_h-4;
209 200 xy_pos = [cur_x,cur_y];
210 201 break;
211 202
212 203 }
213 204
214 205 this.cfg.setProperty("xy",xy_pos);
215 206
216 207 });
217 208
218 209 //Mouse out
219 210 myToolTips.contextMouseOutEvent.subscribe(
220 211 function(type, args) {
221 212 var context = args[0];
222 213
223 214 });
224 215 });
225 216 '''
226 217 return literal(js)
227 218
228 219 tooltip = _ToolTip()
229 220
230 221 class _FilesBreadCrumbs(object):
231 222
232 223 def __call__(self, repo_name, rev, paths):
233 224 url_l = [link_to(repo_name, url('files_home',
234 225 repo_name=repo_name,
235 226 revision=rev, f_path=''))]
236 227 paths_l = paths.split('/')
237 228
238 229 for cnt, p in enumerate(paths_l):
239 230 if p != '':
240 231 url_l.append(link_to(p, url('files_home',
241 232 repo_name=repo_name,
242 233 revision=rev,
243 234 f_path='/'.join(paths_l[:cnt + 1]))))
244 235
245 236 return literal('/'.join(url_l))
246 237
247 238 files_breadcrumbs = _FilesBreadCrumbs()
239
248 240 class CodeHtmlFormatter(HtmlFormatter):
249 241
250 242 def wrap(self, source, outfile):
251 243 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
252 244
253 245 def _wrap_code(self, source):
254 246 for cnt, it in enumerate(source):
255 247 i, t = it
256 248 t = '<div id="#S-%s">%s</div>' % (cnt + 1, t)
257 249 yield i, t
258 250 def pygmentize(filenode, **kwargs):
259 """
260 pygmentize function using pygments
251 """pygmentize function using pygments
252
261 253 :param filenode:
262 254 """
255
263 256 return literal(code_highlight(filenode.content,
264 257 filenode.lexer, CodeHtmlFormatter(**kwargs)))
265 258
266 259 def pygmentize_annotation(filenode, **kwargs):
267 """
268 pygmentize function for annotation
260 """pygmentize function for annotation
261
269 262 :param filenode:
270 263 """
271 264
272 265 color_dict = {}
273 266 def gen_color():
274 267 """generator for getting 10k of evenly distibuted colors using hsv color
275 268 and golden ratio.
276 269 """
277 270 import colorsys
278 271 n = 10000
279 272 golden_ratio = 0.618033988749895
280 273 h = 0.22717784590367374
281 274 #generate 10k nice web friendly colors in the same order
282 275 for c in xrange(n):
283 276 h += golden_ratio
284 277 h %= 1
285 278 HSV_tuple = [h, 0.95, 0.95]
286 279 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
287 280 yield map(lambda x:str(int(x * 256)), RGB_tuple)
288 281
289 282 cgenerator = gen_color()
290 283
291 284 def get_color_string(cs):
292 285 if color_dict.has_key(cs):
293 286 col = color_dict[cs]
294 287 else:
295 288 col = color_dict[cs] = cgenerator.next()
296 289 return "color: rgb(%s)! important;" % (', '.join(col))
297 290
298 291 def url_func(changeset):
299 292 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
300 293 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
301 294
302 295 tooltip_html = tooltip_html % (changeset.author,
303 296 changeset.date,
304 297 tooltip(changeset.message))
305 298 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
306 299 short_id(changeset.raw_id))
307 300 uri = link_to(
308 301 lnk_format,
309 302 url('changeset_home', repo_name=changeset.repository.name,
310 303 revision=changeset.raw_id),
311 304 style=get_color_string(changeset.raw_id),
312 305 class_='tooltip',
313 306 tooltip_title=tooltip_html
314 307 )
315 308
316 309 uri += '\n'
317 310 return uri
318 311 return literal(annotate_highlight(filenode, url_func, **kwargs))
319 312
320 313 def repo_name_slug(value):
321 314 """Return slug of name of repository
322 315 This function is called on each creation/modification
323 316 of repository to prevent bad names in repo
324 317 """
318
325 319 slug = remove_formatting(value)
326 320 slug = strip_tags(slug)
327 321
328 322 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
329 323 slug = slug.replace(c, '-')
330 324 slug = recursive_replace(slug, '-')
331 325 slug = collapse(slug, '-')
332 326 return slug
333 327
334 328 def get_changeset_safe(repo, rev):
335 329 from vcs.backends.base import BaseRepository
336 330 from vcs.exceptions import RepositoryError
337 331 if not isinstance(repo, BaseRepository):
338 332 raise Exception('You must pass an Repository '
339 333 'object as first argument got %s', type(repo))
340 334
341 335 try:
342 336 cs = repo.get_changeset(rev)
343 337 except RepositoryError:
344 338 from rhodecode.lib.utils import EmptyChangeset
345 339 cs = EmptyChangeset()
346 340 return cs
347 341
348 342
349 343 flash = _Flash()
350 344
351 345
352 346 #==============================================================================
353 347 # MERCURIAL FILTERS available via h.
354 348 #==============================================================================
355 349 from mercurial import util
356 350 from mercurial.templatefilters import person as _person
357 351
358
359
360 352 def _age(curdate):
361 353 """turns a datetime into an age string."""
362 354
363 355 if not curdate:
364 356 return ''
365 357
366 358 from datetime import timedelta, datetime
367 359
368 360 agescales = [("year", 3600 * 24 * 365),
369 361 ("month", 3600 * 24 * 30),
370 362 ("day", 3600 * 24),
371 363 ("hour", 3600),
372 364 ("minute", 60),
373 365 ("second", 1), ]
374 366
375 367 age = datetime.now() - curdate
376 368 age_seconds = (age.days * agescales[2][1]) + age.seconds
377 369 pos = 1
378 370 for scale in agescales:
379 371 if scale[1] <= age_seconds:
380 372 if pos == 6:pos = 5
381 373 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
382 374 pos += 1
383 375
384 376 return _('just now')
385 377
386 378 age = lambda x:_age(x)
387 379 capitalize = lambda x: x.capitalize()
388 380 email = util.email
389 381 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
390 382 person = lambda x: _person(x)
391 383 short_id = lambda x: x[:12]
392 384
393 385
394 386 def bool2icon(value):
395 """
396 Returns True/False values represented as small html image of true/false
387 """Returns True/False values represented as small html image of true/false
397 388 icons
389
398 390 :param value: bool value
399 391 """
400 392
401 393 if value is True:
402 394 return HTML.tag('img', src="/images/icons/accept.png", alt=_('True'))
403 395
404 396 if value is False:
405 397 return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False'))
406 398
407 399 return value
408 400
409 401
410 402 def action_parser(user_log):
411 """
412 This helper will map the specified string action into translated
403 """This helper will map the specified string action into translated
413 404 fancy names with icons and links
414 405
415 @param action:
406 :param user_log: user log instance
416 407 """
408
417 409 action = user_log.action
418 410 action_params = ' '
419 411
420 412 x = action.split(':')
421 413
422 414 if len(x) > 1:
423 415 action, action_params = x
424 416
425 417 def get_cs_links():
426 418 if action == 'push':
427 revs_limit = 5
419 revs_limit = 5 #display this amount always
420 revs_top_limit = 50 #show upto this amount of changesets hidden
428 421 revs = action_params.split(',')
429 cs_links = " " + ', '.join ([link(rev,
422 repo_name = user_log.repository.repo_name
423 from rhodecode.model.scm import ScmModel
424
425 message = lambda rev: get_changeset_safe(ScmModel().get(repo_name),
426 rev).message
427
428 cs_links = " " + ', '.join ([link_to(rev,
430 429 url('changeset_home',
431 repo_name=user_log.repository.repo_name,
432 revision=rev)) for rev in revs[:revs_limit] ])
430 repo_name=repo_name,
431 revision=rev), tooltip_title=message(rev),
432 class_='tooltip') for rev in revs[:revs_limit] ])
433 433 if len(revs) > revs_limit:
434 434 uniq_id = revs[0]
435 435 html_tmpl = ('<span> %s '
436 436 '<a class="show_more" id="_%s" href="#">%s</a> '
437 437 '%s</span>')
438 438 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
439 439 % (len(revs) - revs_limit),
440 440 _('revisions'))
441 441
442 442 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
443 cs_links += html_tmpl % (uniq_id, ', '.join([link(rev,
443 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
444 444 url('changeset_home',
445 repo_name=user_log.repository.repo_name,
446 revision=rev)) for rev in revs[revs_limit:] ]))
445 repo_name=repo_name, revision=rev),
446 tooltip_title=message(rev), class_='tooltip')
447 for rev in revs[revs_limit:revs_top_limit]]))
447 448
448 449 return cs_links
449 450 return ''
450 451
451 452 def get_fork_name():
452 453 if action == 'user_forked_repo':
453 454 from rhodecode.model.scm import ScmModel
454 455 repo_name = action_params
455 456 repo = ScmModel().get(repo_name)
456 457 if repo is None:
457 458 return repo_name
458 459 return link_to(action_params, url('summary_home',
459 460 repo_name=repo.name,),
460 461 title=repo.dbrepo.description)
461 462 return ''
462 463 map = {'user_deleted_repo':_('User [deleted] repository'),
463 464 'user_created_repo':_('User [created] repository'),
464 465 'user_forked_repo':_('User [forked] repository as: %s') % get_fork_name(),
465 466 'user_updated_repo':_('User [updated] repository'),
466 467 'admin_deleted_repo':_('Admin [delete] repository'),
467 468 'admin_created_repo':_('Admin [created] repository'),
468 469 'admin_forked_repo':_('Admin [forked] repository'),
469 470 'admin_updated_repo':_('Admin [updated] repository'),
470 471 'push':_('[Pushed] %s') % get_cs_links(),
471 472 'pull':_('[Pulled]'),
472 473 'started_following_repo':_('User [started following] repository'),
473 474 'stopped_following_repo':_('User [stopped following] repository'),
474 475 }
475 476
476 477 action_str = map.get(action, action)
477 478 return literal(action_str.replace('[', '<span class="journal_highlight">')\
478 479 .replace(']', '</span>'))
479 480
480 481 def action_parser_icon(user_log):
481 482 action = user_log.action
482 483 action_params = None
483 484 x = action.split(':')
484 485
485 486 if len(x) > 1:
486 487 action, action_params = x
487 488
488 489 tmpl = """<img src="/images/icons/%s" alt="%s"/>"""
489 490 map = {'user_deleted_repo':'database_delete.png',
490 491 'user_created_repo':'database_add.png',
491 492 'user_forked_repo':'arrow_divide.png',
492 493 'user_updated_repo':'database_edit.png',
493 494 'admin_deleted_repo':'database_delete.png',
494 'admin_created_repo':'database_ddd.png',
495 'admin_created_repo':'database_add.png',
495 496 'admin_forked_repo':'arrow_divide.png',
496 497 'admin_updated_repo':'database_edit.png',
497 498 'push':'script_add.png',
498 499 'pull':'down_16.png',
499 500 'started_following_repo':'heart_add.png',
500 501 'stopped_following_repo':'heart_delete.png',
501 502 }
502 503 return literal(tmpl % (map.get(action, action), action))
503 504
504 505
505 506 #==============================================================================
506 507 # PERMS
507 508 #==============================================================================
508 509 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
509 510 HasRepoPermissionAny, HasRepoPermissionAll
510 511
511 512 #==============================================================================
512 513 # GRAVATAR URL
513 514 #==============================================================================
514 515 import hashlib
515 516 import urllib
516 517 from pylons import request
517 518
518 519 def gravatar_url(email_address, size=30):
519 520 ssl_enabled = 'https' == request.environ.get('HTTP_X_URL_SCHEME')
520 521 default = 'identicon'
521 522 baseurl_nossl = "http://www.gravatar.com/avatar/"
522 523 baseurl_ssl = "https://secure.gravatar.com/avatar/"
523 524 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
524 525
525 526
526 527 # construct the url
527 528 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
528 529 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
529 530
530 531 return gravatar_url
531 532
532 533 def safe_unicode(str):
533 534 """safe unicode function. In case of UnicodeDecode error we try to return
534 535 unicode with errors replace, if this failes we return unicode with
535 536 string_escape decoding """
536 537
537 538 try:
538 539 u_str = unicode(str)
539 540 except UnicodeDecodeError:
540 541 try:
541 542 u_str = unicode(str, 'utf-8', 'replace')
542 543 except UnicodeDecodeError:
543 544 #incase we have a decode error just represent as byte string
544 545 u_str = unicode(str(str).encode('string_escape'))
545 546
546 547 return u_str
General Comments 0
You need to be logged in to leave comments. Login now