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