##// END OF EJS Templates
Removed all string concat for exchange of ''.join()
marcink -
r1359:54fc83f2 beta
parent child Browse files
Show More
@@ -1,116 +1,116 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.feed
3 rhodecode.controllers.feed
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Feed controller for rhodecode
6 Feed controller for rhodecode
7
7
8 :created_on: Apr 23, 2010
8 :created_on: Apr 23, 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 modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27
27
28 from pylons import url, response, tmpl_context as c
28 from pylons import url, response, tmpl_context as c
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30
30
31 from rhodecode.lib import safe_unicode
31 from rhodecode.lib import safe_unicode
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
32 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
33 from rhodecode.lib.base import BaseRepoController
33 from rhodecode.lib.base import BaseRepoController
34
34
35 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
35 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 class FeedController(BaseRepoController):
40 class FeedController(BaseRepoController):
41
41
42 @LoginRequired(api_access=True)
42 @LoginRequired(api_access=True)
43 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
43 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
44 'repository.admin')
44 'repository.admin')
45 def __before__(self):
45 def __before__(self):
46 super(FeedController, self).__before__()
46 super(FeedController, self).__before__()
47 #common values for feeds
47 #common values for feeds
48 self.description = _('Changes on %s repository')
48 self.description = _('Changes on %s repository')
49 self.title = self.title = _('%s %s feed') % (c.rhodecode_name, '%s')
49 self.title = self.title = _('%s %s feed') % (c.rhodecode_name, '%s')
50 self.language = 'en-us'
50 self.language = 'en-us'
51 self.ttl = "5"
51 self.ttl = "5"
52 self.feed_nr = 10
52 self.feed_nr = 10
53
53
54 def __changes(self, cs):
54 def __changes(self, cs):
55 changes = ''
55 changes = []
56
56
57 a = [safe_unicode(n.path) for n in cs.added]
57 a = [safe_unicode(n.path) for n in cs.added]
58 if a:
58 if a:
59 changes += '\nA ' + '\nA '.join(a)
59 changes.append('\nA ' + '\nA '.join(a))
60
60
61 m = [safe_unicode(n.path) for n in cs.changed]
61 m = [safe_unicode(n.path) for n in cs.changed]
62 if m:
62 if m:
63 changes += '\nM ' + '\nM '.join(m)
63 changes.append('\nM ' + '\nM '.join(m))
64
64
65 d = [safe_unicode(n.path) for n in cs.removed]
65 d = [safe_unicode(n.path) for n in cs.removed]
66 if d:
66 if d:
67 changes += '\nD ' + '\nD '.join(d)
67 changes.append('\nD ' + '\nD '.join(d))
68
68
69 changes += '</pre>'
69 changes.append('</pre>')
70
70
71 return changes
71 return ''.join(changes)
72
72
73 def atom(self, repo_name):
73 def atom(self, repo_name):
74 """Produce an atom-1.0 feed via feedgenerator module"""
74 """Produce an atom-1.0 feed via feedgenerator module"""
75 feed = Atom1Feed(title=self.title % repo_name,
75 feed = Atom1Feed(title=self.title % repo_name,
76 link=url('summary_home', repo_name=repo_name,
76 link=url('summary_home', repo_name=repo_name,
77 qualified=True),
77 qualified=True),
78 description=self.description % repo_name,
78 description=self.description % repo_name,
79 language=self.language,
79 language=self.language,
80 ttl=self.ttl)
80 ttl=self.ttl)
81
81 desc_msg = []
82 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
82 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
83 desc = '%s - %s<br/><pre>' % (cs.author, cs.date)
83 desc_msg.append('%s - %s<br/><pre>' % (cs.author, cs.date))
84 desc += self.__changes(cs)
84 desc_msg.append(self.__changes(cs))
85
85
86 feed.add_item(title=cs.message,
86 feed.add_item(title=cs.message,
87 link=url('changeset_home', repo_name=repo_name,
87 link=url('changeset_home', repo_name=repo_name,
88 revision=cs.raw_id, qualified=True),
88 revision=cs.raw_id, qualified=True),
89 author_name=cs.author,
89 author_name=cs.author,
90 description=desc)
90 description=''.join(desc_msg))
91
91
92 response.content_type = feed.mime_type
92 response.content_type = feed.mime_type
93 return feed.writeString('utf-8')
93 return feed.writeString('utf-8')
94
94
95 def rss(self, repo_name):
95 def rss(self, repo_name):
96 """Produce an rss2 feed via feedgenerator module"""
96 """Produce an rss2 feed via feedgenerator module"""
97 feed = Rss201rev2Feed(title=self.title % repo_name,
97 feed = Rss201rev2Feed(title=self.title % repo_name,
98 link=url('summary_home', repo_name=repo_name,
98 link=url('summary_home', repo_name=repo_name,
99 qualified=True),
99 qualified=True),
100 description=self.description % repo_name,
100 description=self.description % repo_name,
101 language=self.language,
101 language=self.language,
102 ttl=self.ttl)
102 ttl=self.ttl)
103
103 desc_msg = []
104 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
104 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
105 desc = '%s - %s<br/><pre>' % (cs.author, cs.date)
105 desc_msg.append('%s - %s<br/><pre>' % (cs.author, cs.date))
106 desc += self.__changes(cs)
106 desc_msg.append(self.__changes(cs))
107
107
108 feed.add_item(title=cs.message,
108 feed.add_item(title=cs.message,
109 link=url('changeset_home', repo_name=repo_name,
109 link=url('changeset_home', repo_name=repo_name,
110 revision=cs.raw_id, qualified=True),
110 revision=cs.raw_id, qualified=True),
111 author_name=cs.author,
111 author_name=cs.author,
112 description=desc,
112 description=''.join(desc_msg),
113 )
113 )
114
114
115 response.content_type = feed.mime_type
115 response.content_type = feed.mime_type
116 return feed.writeString('utf-8')
116 return feed.writeString('utf-8')
@@ -1,695 +1,695 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
10
11 from datetime import datetime
11 from datetime import datetime
12 from pygments.formatters import HtmlFormatter
12 from pygments.formatters import HtmlFormatter
13 from pygments import highlight as code_highlight
13 from pygments import highlight as code_highlight
14 from pylons import url, request, config
14 from pylons import url, request, config
15 from pylons.i18n.translation import _, ungettext
15 from pylons.i18n.translation import _, ungettext
16
16
17 from webhelpers.html import literal, HTML, escape
17 from webhelpers.html import literal, HTML, escape
18 from webhelpers.html.tools import *
18 from webhelpers.html.tools import *
19 from webhelpers.html.builder import make_tag
19 from webhelpers.html.builder import make_tag
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 password, textarea, title, ul, xml_declaration, radio
23 password, textarea, title, ul, xml_declaration, radio
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 mail_to, strip_links, strip_tags, tag_re
25 mail_to, strip_links, strip_tags, tag_re
26 from webhelpers.number import format_byte_size, format_bit_size
26 from webhelpers.number import format_byte_size, format_bit_size
27 from webhelpers.pylonslib import Flash as _Flash
27 from webhelpers.pylonslib import Flash as _Flash
28 from webhelpers.pylonslib.secure_form import secure_form
28 from webhelpers.pylonslib.secure_form import secure_form
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 replace_whitespace, urlify, truncate, wrap_paragraphs
31 replace_whitespace, urlify, truncate, wrap_paragraphs
32 from webhelpers.date import time_ago_in_words
32 from webhelpers.date import time_ago_in_words
33 from webhelpers.paginate import Page
33 from webhelpers.paginate import Page
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 convert_boolean_attrs, NotGiven
35 convert_boolean_attrs, NotGiven
36
36
37 from vcs.utils.annotate import annotate_highlight
37 from vcs.utils.annotate import annotate_highlight
38 from rhodecode.lib.utils import repo_name_slug
38 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib import str2bool, safe_unicode
39 from rhodecode.lib import str2bool, safe_unicode
40
40
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 """
42 """
43 Reset button
43 Reset button
44 """
44 """
45 _set_input_attrs(attrs, type, name, value)
45 _set_input_attrs(attrs, type, name, value)
46 _set_id_attr(attrs, id, name)
46 _set_id_attr(attrs, id, name)
47 convert_boolean_attrs(attrs, ["disabled"])
47 convert_boolean_attrs(attrs, ["disabled"])
48 return HTML.input(**attrs)
48 return HTML.input(**attrs)
49
49
50 reset = _reset
50 reset = _reset
51
51
52
52
53 def get_token():
53 def get_token():
54 """Return the current authentication token, creating one if one doesn't
54 """Return the current authentication token, creating one if one doesn't
55 already exist.
55 already exist.
56 """
56 """
57 token_key = "_authentication_token"
57 token_key = "_authentication_token"
58 from pylons import session
58 from pylons import session
59 if not token_key in session:
59 if not token_key in session:
60 try:
60 try:
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 except AttributeError: # Python < 2.4
62 except AttributeError: # Python < 2.4
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 session[token_key] = token
64 session[token_key] = token
65 if hasattr(session, 'save'):
65 if hasattr(session, 'save'):
66 session.save()
66 session.save()
67 return session[token_key]
67 return session[token_key]
68
68
69 class _GetError(object):
69 class _GetError(object):
70 """Get error from form_errors, and represent it as span wrapped error
70 """Get error from form_errors, and represent it as span wrapped error
71 message
71 message
72
72
73 :param field_name: field to fetch errors for
73 :param field_name: field to fetch errors for
74 :param form_errors: form errors dict
74 :param form_errors: form errors dict
75 """
75 """
76
76
77 def __call__(self, field_name, form_errors):
77 def __call__(self, field_name, form_errors):
78 tmpl = """<span class="error_msg">%s</span>"""
78 tmpl = """<span class="error_msg">%s</span>"""
79 if form_errors and form_errors.has_key(field_name):
79 if form_errors and form_errors.has_key(field_name):
80 return literal(tmpl % form_errors.get(field_name))
80 return literal(tmpl % form_errors.get(field_name))
81
81
82 get_error = _GetError()
82 get_error = _GetError()
83
83
84 class _ToolTip(object):
84 class _ToolTip(object):
85
85
86 def __call__(self, tooltip_title, trim_at=50):
86 def __call__(self, tooltip_title, trim_at=50):
87 """Special function just to wrap our text into nice formatted
87 """Special function just to wrap our text into nice formatted
88 autowrapped text
88 autowrapped text
89
89
90 :param tooltip_title:
90 :param tooltip_title:
91 """
91 """
92
92
93 return escape(tooltip_title)
93 return escape(tooltip_title)
94
94
95 def activate(self):
95 def activate(self):
96 """Adds tooltip mechanism to the given Html all tooltips have to have
96 """Adds tooltip mechanism to the given Html all tooltips have to have
97 set class `tooltip` and set attribute `tooltip_title`.
97 set class `tooltip` and set attribute `tooltip_title`.
98 Then a tooltip will be generated based on that. All with yui js tooltip
98 Then a tooltip will be generated based on that. All with yui js tooltip
99 """
99 """
100
100
101 js = '''
101 js = '''
102 YAHOO.util.Event.onDOMReady(function(){
102 YAHOO.util.Event.onDOMReady(function(){
103 function toolTipsId(){
103 function toolTipsId(){
104 var ids = [];
104 var ids = [];
105 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
105 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
106
106
107 for (var i = 0; i < tts.length; i++) {
107 for (var i = 0; i < tts.length; i++) {
108 //if element doesn't not have and id autogenerate one for tooltip
108 //if element doesn't not have and id autogenerate one for tooltip
109
109
110 if (!tts[i].id){
110 if (!tts[i].id){
111 tts[i].id='tt'+i*100;
111 tts[i].id='tt'+i*100;
112 }
112 }
113 ids.push(tts[i].id);
113 ids.push(tts[i].id);
114 }
114 }
115 return ids
115 return ids
116 };
116 };
117 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
117 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
118 context: [[toolTipsId()],"tl","bl",null,[0,5]],
118 context: [[toolTipsId()],"tl","bl",null,[0,5]],
119 monitorresize:false,
119 monitorresize:false,
120 xyoffset :[0,0],
120 xyoffset :[0,0],
121 autodismissdelay:300000,
121 autodismissdelay:300000,
122 hidedelay:5,
122 hidedelay:5,
123 showdelay:20,
123 showdelay:20,
124 });
124 });
125
125
126 });
126 });
127 '''
127 '''
128 return literal(js)
128 return literal(js)
129
129
130 tooltip = _ToolTip()
130 tooltip = _ToolTip()
131
131
132 class _FilesBreadCrumbs(object):
132 class _FilesBreadCrumbs(object):
133
133
134 def __call__(self, repo_name, rev, paths):
134 def __call__(self, repo_name, rev, paths):
135 if isinstance(paths, str):
135 if isinstance(paths, str):
136 paths = safe_unicode(paths)
136 paths = safe_unicode(paths)
137 url_l = [link_to(repo_name, url('files_home',
137 url_l = [link_to(repo_name, url('files_home',
138 repo_name=repo_name,
138 repo_name=repo_name,
139 revision=rev, f_path=''))]
139 revision=rev, f_path=''))]
140 paths_l = paths.split('/')
140 paths_l = paths.split('/')
141 for cnt, p in enumerate(paths_l):
141 for cnt, p in enumerate(paths_l):
142 if p != '':
142 if p != '':
143 url_l.append(link_to(p, url('files_home',
143 url_l.append(link_to(p, url('files_home',
144 repo_name=repo_name,
144 repo_name=repo_name,
145 revision=rev,
145 revision=rev,
146 f_path='/'.join(paths_l[:cnt + 1]))))
146 f_path='/'.join(paths_l[:cnt + 1]))))
147
147
148 return literal('/'.join(url_l))
148 return literal('/'.join(url_l))
149
149
150 files_breadcrumbs = _FilesBreadCrumbs()
150 files_breadcrumbs = _FilesBreadCrumbs()
151
151
152 class CodeHtmlFormatter(HtmlFormatter):
152 class CodeHtmlFormatter(HtmlFormatter):
153 """My code Html Formatter for source codes
153 """My code Html Formatter for source codes
154 """
154 """
155
155
156 def wrap(self, source, outfile):
156 def wrap(self, source, outfile):
157 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
157 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
158
158
159 def _wrap_code(self, source):
159 def _wrap_code(self, source):
160 for cnt, it in enumerate(source):
160 for cnt, it in enumerate(source):
161 i, t = it
161 i, t = it
162 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
162 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
163 yield i, t
163 yield i, t
164
164
165 def _wrap_tablelinenos(self, inner):
165 def _wrap_tablelinenos(self, inner):
166 dummyoutfile = StringIO.StringIO()
166 dummyoutfile = StringIO.StringIO()
167 lncount = 0
167 lncount = 0
168 for t, line in inner:
168 for t, line in inner:
169 if t:
169 if t:
170 lncount += 1
170 lncount += 1
171 dummyoutfile.write(line)
171 dummyoutfile.write(line)
172
172
173 fl = self.linenostart
173 fl = self.linenostart
174 mw = len(str(lncount + fl - 1))
174 mw = len(str(lncount + fl - 1))
175 sp = self.linenospecial
175 sp = self.linenospecial
176 st = self.linenostep
176 st = self.linenostep
177 la = self.lineanchors
177 la = self.lineanchors
178 aln = self.anchorlinenos
178 aln = self.anchorlinenos
179 nocls = self.noclasses
179 nocls = self.noclasses
180 if sp:
180 if sp:
181 lines = []
181 lines = []
182
182
183 for i in range(fl, fl + lncount):
183 for i in range(fl, fl + lncount):
184 if i % st == 0:
184 if i % st == 0:
185 if i % sp == 0:
185 if i % sp == 0:
186 if aln:
186 if aln:
187 lines.append('<a href="#%s%d" class="special">%*d</a>' %
187 lines.append('<a href="#%s%d" class="special">%*d</a>' %
188 (la, i, mw, i))
188 (la, i, mw, i))
189 else:
189 else:
190 lines.append('<span class="special">%*d</span>' % (mw, i))
190 lines.append('<span class="special">%*d</span>' % (mw, i))
191 else:
191 else:
192 if aln:
192 if aln:
193 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
193 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
194 else:
194 else:
195 lines.append('%*d' % (mw, i))
195 lines.append('%*d' % (mw, i))
196 else:
196 else:
197 lines.append('')
197 lines.append('')
198 ls = '\n'.join(lines)
198 ls = '\n'.join(lines)
199 else:
199 else:
200 lines = []
200 lines = []
201 for i in range(fl, fl + lncount):
201 for i in range(fl, fl + lncount):
202 if i % st == 0:
202 if i % st == 0:
203 if aln:
203 if aln:
204 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
204 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
205 else:
205 else:
206 lines.append('%*d' % (mw, i))
206 lines.append('%*d' % (mw, i))
207 else:
207 else:
208 lines.append('')
208 lines.append('')
209 ls = '\n'.join(lines)
209 ls = '\n'.join(lines)
210
210
211 # in case you wonder about the seemingly redundant <div> here: since the
211 # in case you wonder about the seemingly redundant <div> here: since the
212 # content in the other cell also is wrapped in a div, some browsers in
212 # content in the other cell also is wrapped in a div, some browsers in
213 # some configurations seem to mess up the formatting...
213 # some configurations seem to mess up the formatting...
214 if nocls:
214 if nocls:
215 yield 0, ('<table class="%stable">' % self.cssclass +
215 yield 0, ('<table class="%stable">' % self.cssclass +
216 '<tr><td><div class="linenodiv" '
216 '<tr><td><div class="linenodiv" '
217 'style="background-color: #f0f0f0; padding-right: 10px">'
217 'style="background-color: #f0f0f0; padding-right: 10px">'
218 '<pre style="line-height: 125%">' +
218 '<pre style="line-height: 125%">' +
219 ls + '</pre></div></td><td id="hlcode" class="code">')
219 ls + '</pre></div></td><td id="hlcode" class="code">')
220 else:
220 else:
221 yield 0, ('<table class="%stable">' % self.cssclass +
221 yield 0, ('<table class="%stable">' % self.cssclass +
222 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
222 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
223 ls + '</pre></div></td><td id="hlcode" class="code">')
223 ls + '</pre></div></td><td id="hlcode" class="code">')
224 yield 0, dummyoutfile.getvalue()
224 yield 0, dummyoutfile.getvalue()
225 yield 0, '</td></tr></table>'
225 yield 0, '</td></tr></table>'
226
226
227
227
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(repo_name, filenode, **kwargs):
237 def pygmentize_annotation(repo_name, 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 _ in xrange(n):
254 for _ 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(repo_name):
270 def url_func(repo_name):
271
271
272 def _url_func(changeset):
272 def _url_func(changeset):
273 author = changeset.author
273 author = changeset.author
274 date = changeset.date
274 date = changeset.date
275 message = tooltip(changeset.message)
275 message = tooltip(changeset.message)
276
276
277 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
277 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
278 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
278 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
279 "</b> %s<br/></div>")
279 "</b> %s<br/></div>")
280
280
281 tooltip_html = tooltip_html % (author, date, message)
281 tooltip_html = tooltip_html % (author, date, message)
282 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
282 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
283 short_id(changeset.raw_id))
283 short_id(changeset.raw_id))
284 uri = link_to(
284 uri = link_to(
285 lnk_format,
285 lnk_format,
286 url('changeset_home', repo_name=repo_name,
286 url('changeset_home', repo_name=repo_name,
287 revision=changeset.raw_id),
287 revision=changeset.raw_id),
288 style=get_color_string(changeset.raw_id),
288 style=get_color_string(changeset.raw_id),
289 class_='tooltip',
289 class_='tooltip',
290 title=tooltip_html
290 title=tooltip_html
291 )
291 )
292
292
293 uri += '\n'
293 uri += '\n'
294 return uri
294 return uri
295 return _url_func
295 return _url_func
296
296
297 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
297 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
298
298
299 def get_changeset_safe(repo, rev):
299 def get_changeset_safe(repo, rev):
300 from vcs.backends.base import BaseRepository
300 from vcs.backends.base import BaseRepository
301 from vcs.exceptions import RepositoryError
301 from vcs.exceptions import RepositoryError
302 if not isinstance(repo, BaseRepository):
302 if not isinstance(repo, BaseRepository):
303 raise Exception('You must pass an Repository '
303 raise Exception('You must pass an Repository '
304 'object as first argument got %s', type(repo))
304 'object as first argument got %s', type(repo))
305
305
306 try:
306 try:
307 cs = repo.get_changeset(rev)
307 cs = repo.get_changeset(rev)
308 except RepositoryError:
308 except RepositoryError:
309 from rhodecode.lib.utils import EmptyChangeset
309 from rhodecode.lib.utils import EmptyChangeset
310 cs = EmptyChangeset()
310 cs = EmptyChangeset()
311 return cs
311 return cs
312
312
313
313
314 def is_following_repo(repo_name, user_id):
314 def is_following_repo(repo_name, user_id):
315 from rhodecode.model.scm import ScmModel
315 from rhodecode.model.scm import ScmModel
316 return ScmModel().is_following_repo(repo_name, user_id)
316 return ScmModel().is_following_repo(repo_name, user_id)
317
317
318 flash = _Flash()
318 flash = _Flash()
319
319
320 #==============================================================================
320 #==============================================================================
321 # SCM FILTERS available via h.
321 # SCM FILTERS available via h.
322 #==============================================================================
322 #==============================================================================
323 from vcs.utils import author_name, author_email
323 from vcs.utils import author_name, author_email
324 from rhodecode.lib import credentials_hidder, age as _age
324 from rhodecode.lib import credentials_hidder, age as _age
325
325
326 age = lambda x:_age(x)
326 age = lambda x:_age(x)
327 capitalize = lambda x: x.capitalize()
327 capitalize = lambda x: x.capitalize()
328 email = author_email
328 email = author_email
329 email_or_none = lambda x: email(x) if email(x) != x else None
329 email_or_none = lambda x: email(x) if email(x) != x else None
330 person = lambda x: author_name(x)
330 person = lambda x: author_name(x)
331 short_id = lambda x: x[:12]
331 short_id = lambda x: x[:12]
332 hide_credentials = lambda x: ''.join(credentials_hidder(x))
332 hide_credentials = lambda x: ''.join(credentials_hidder(x))
333
333
334 def bool2icon(value):
334 def bool2icon(value):
335 """Returns True/False values represented as small html image of true/false
335 """Returns True/False values represented as small html image of true/false
336 icons
336 icons
337
337
338 :param value: bool value
338 :param value: bool value
339 """
339 """
340
340
341 if value is True:
341 if value is True:
342 return HTML.tag('img', src=url("/images/icons/accept.png"),
342 return HTML.tag('img', src=url("/images/icons/accept.png"),
343 alt=_('True'))
343 alt=_('True'))
344
344
345 if value is False:
345 if value is False:
346 return HTML.tag('img', src=url("/images/icons/cancel.png"),
346 return HTML.tag('img', src=url("/images/icons/cancel.png"),
347 alt=_('False'))
347 alt=_('False'))
348
348
349 return value
349 return value
350
350
351
351
352 def action_parser(user_log, feed=False):
352 def action_parser(user_log, feed=False):
353 """This helper will action_map the specified string action into translated
353 """This helper will action_map the specified string action into translated
354 fancy names with icons and links
354 fancy names with icons and links
355
355
356 :param user_log: user log instance
356 :param user_log: user log instance
357 :param feed: use output for feeds (no html and fancy icons)
357 :param feed: use output for feeds (no html and fancy icons)
358 """
358 """
359
359
360 action = user_log.action
360 action = user_log.action
361 action_params = ' '
361 action_params = ' '
362
362
363 x = action.split(':')
363 x = action.split(':')
364
364
365 if len(x) > 1:
365 if len(x) > 1:
366 action, action_params = x
366 action, action_params = x
367
367
368 def get_cs_links():
368 def get_cs_links():
369 revs_limit = 5 #display this amount always
369 revs_limit = 5 #display this amount always
370 revs_top_limit = 50 #show upto this amount of changesets hidden
370 revs_top_limit = 50 #show upto this amount of changesets hidden
371 revs = action_params.split(',')
371 revs = action_params.split(',')
372 repo_name = user_log.repository.repo_name
372 repo_name = user_log.repository.repo_name
373
373
374 from rhodecode.model.scm import ScmModel
374 from rhodecode.model.scm import ScmModel
375 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
375 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
376 invalidation_list=[])
376 invalidation_list=[])
377
377
378 message = lambda rev: get_changeset_safe(repo, rev).message
378 message = lambda rev: get_changeset_safe(repo, rev).message
379
379 cs_links = []
380 cs_links = " " + ', '.join ([link_to(rev,
380 cs_links.append(" " + ', '.join ([link_to(rev,
381 url('changeset_home',
381 url('changeset_home',
382 repo_name=repo_name,
382 repo_name=repo_name,
383 revision=rev), title=tooltip(message(rev)),
383 revision=rev), title=tooltip(message(rev)),
384 class_='tooltip') for rev in revs[:revs_limit] ])
384 class_='tooltip') for rev in revs[:revs_limit] ]))
385
385
386 compare_view = (' <div class="compare_view tooltip" title="%s">'
386 compare_view = (' <div class="compare_view tooltip" title="%s">'
387 '<a href="%s">%s</a> '
387 '<a href="%s">%s</a> '
388 '</div>' % (_('Show all combined changesets %s->%s' \
388 '</div>' % (_('Show all combined changesets %s->%s' \
389 % (revs[0], revs[-1])),
389 % (revs[0], revs[-1])),
390 url('changeset_home', repo_name=repo_name,
390 url('changeset_home', repo_name=repo_name,
391 revision='%s...%s' % (revs[0], revs[-1])
391 revision='%s...%s' % (revs[0], revs[-1])
392 ),
392 ),
393 _('compare view'))
393 _('compare view'))
394 )
394 )
395
395
396 if len(revs) > revs_limit:
396 if len(revs) > revs_limit:
397 uniq_id = revs[0]
397 uniq_id = revs[0]
398 html_tmpl = ('<span> %s '
398 html_tmpl = ('<span> %s '
399 '<a class="show_more" id="_%s" href="#more">%s</a> '
399 '<a class="show_more" id="_%s" href="#more">%s</a> '
400 '%s</span>')
400 '%s</span>')
401 if not feed:
401 if not feed:
402 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
402 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
403 % (len(revs) - revs_limit),
403 % (len(revs) - revs_limit),
404 _('revisions'))
404 _('revisions')))
405
405
406 if not feed:
406 if not feed:
407 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
407 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
408 else:
408 else:
409 html_tmpl = '<span id="%s"> %s </span>'
409 html_tmpl = '<span id="%s"> %s </span>'
410
410
411 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
411 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
412 url('changeset_home',
412 url('changeset_home',
413 repo_name=repo_name, revision=rev),
413 repo_name=repo_name, revision=rev),
414 title=message(rev), class_='tooltip')
414 title=message(rev), class_='tooltip')
415 for rev in revs[revs_limit:revs_top_limit]]))
415 for rev in revs[revs_limit:revs_top_limit]])))
416 if len(revs) > 1:
416 if len(revs) > 1:
417 cs_links += compare_view
417 cs_links.append(compare_view)
418 return cs_links
418 return ''.join(cs_links)
419
419
420 def get_fork_name():
420 def get_fork_name():
421 repo_name = action_params
421 repo_name = action_params
422 return _('fork name ') + str(link_to(action_params, url('summary_home',
422 return _('fork name ') + str(link_to(action_params, url('summary_home',
423 repo_name=repo_name,)))
423 repo_name=repo_name,)))
424
424
425 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
425 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
426 'user_created_repo':(_('[created] repository'), None),
426 'user_created_repo':(_('[created] repository'), None),
427 'user_forked_repo':(_('[forked] repository'), get_fork_name),
427 'user_forked_repo':(_('[forked] repository'), get_fork_name),
428 'user_updated_repo':(_('[updated] repository'), None),
428 'user_updated_repo':(_('[updated] repository'), None),
429 'admin_deleted_repo':(_('[delete] repository'), None),
429 'admin_deleted_repo':(_('[delete] repository'), None),
430 'admin_created_repo':(_('[created] repository'), None),
430 'admin_created_repo':(_('[created] repository'), None),
431 'admin_forked_repo':(_('[forked] repository'), None),
431 'admin_forked_repo':(_('[forked] repository'), None),
432 'admin_updated_repo':(_('[updated] repository'), None),
432 'admin_updated_repo':(_('[updated] repository'), None),
433 'push':(_('[pushed] into'), get_cs_links),
433 'push':(_('[pushed] into'), get_cs_links),
434 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
434 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
435 'push_remote':(_('[pulled from remote] into'), get_cs_links),
435 'push_remote':(_('[pulled from remote] into'), get_cs_links),
436 'pull':(_('[pulled] from'), None),
436 'pull':(_('[pulled] from'), None),
437 'started_following_repo':(_('[started following] repository'), None),
437 'started_following_repo':(_('[started following] repository'), None),
438 'stopped_following_repo':(_('[stopped following] repository'), None),
438 'stopped_following_repo':(_('[stopped following] repository'), None),
439 }
439 }
440
440
441 action_str = action_map.get(action, action)
441 action_str = action_map.get(action, action)
442 if feed:
442 if feed:
443 action = action_str[0].replace('[', '').replace(']', '')
443 action = action_str[0].replace('[', '').replace(']', '')
444 else:
444 else:
445 action = action_str[0].replace('[', '<span class="journal_highlight">')\
445 action = action_str[0].replace('[', '<span class="journal_highlight">')\
446 .replace(']', '</span>')
446 .replace(']', '</span>')
447
447
448 action_params_func = lambda :""
448 action_params_func = lambda :""
449
449
450 if callable(action_str[1]):
450 if callable(action_str[1]):
451 action_params_func = action_str[1]
451 action_params_func = action_str[1]
452
452
453 return [literal(action), action_params_func]
453 return [literal(action), action_params_func]
454
454
455 def action_parser_icon(user_log):
455 def action_parser_icon(user_log):
456 action = user_log.action
456 action = user_log.action
457 action_params = None
457 action_params = None
458 x = action.split(':')
458 x = action.split(':')
459
459
460 if len(x) > 1:
460 if len(x) > 1:
461 action, action_params = x
461 action, action_params = x
462
462
463 tmpl = """<img src="%s%s" alt="%s"/>"""
463 tmpl = """<img src="%s%s" alt="%s"/>"""
464 map = {'user_deleted_repo':'database_delete.png',
464 map = {'user_deleted_repo':'database_delete.png',
465 'user_created_repo':'database_add.png',
465 'user_created_repo':'database_add.png',
466 'user_forked_repo':'arrow_divide.png',
466 'user_forked_repo':'arrow_divide.png',
467 'user_updated_repo':'database_edit.png',
467 'user_updated_repo':'database_edit.png',
468 'admin_deleted_repo':'database_delete.png',
468 'admin_deleted_repo':'database_delete.png',
469 'admin_created_repo':'database_add.png',
469 'admin_created_repo':'database_add.png',
470 'admin_forked_repo':'arrow_divide.png',
470 'admin_forked_repo':'arrow_divide.png',
471 'admin_updated_repo':'database_edit.png',
471 'admin_updated_repo':'database_edit.png',
472 'push':'script_add.png',
472 'push':'script_add.png',
473 'push_local':'script_edit.png',
473 'push_local':'script_edit.png',
474 'push_remote':'connect.png',
474 'push_remote':'connect.png',
475 'pull':'down_16.png',
475 'pull':'down_16.png',
476 'started_following_repo':'heart_add.png',
476 'started_following_repo':'heart_add.png',
477 'stopped_following_repo':'heart_delete.png',
477 'stopped_following_repo':'heart_delete.png',
478 }
478 }
479 return literal(tmpl % ((url('/images/icons/')),
479 return literal(tmpl % ((url('/images/icons/')),
480 map.get(action, action), action))
480 map.get(action, action), action))
481
481
482
482
483 #==============================================================================
483 #==============================================================================
484 # PERMS
484 # PERMS
485 #==============================================================================
485 #==============================================================================
486 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
486 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
487 HasRepoPermissionAny, HasRepoPermissionAll
487 HasRepoPermissionAny, HasRepoPermissionAll
488
488
489 #==============================================================================
489 #==============================================================================
490 # GRAVATAR URL
490 # GRAVATAR URL
491 #==============================================================================
491 #==============================================================================
492
492
493 def gravatar_url(email_address, size=30):
493 def gravatar_url(email_address, size=30):
494 if not str2bool(config['app_conf'].get('use_gravatar')) or \
494 if not str2bool(config['app_conf'].get('use_gravatar')) or \
495 email_address == 'anonymous@rhodecode.org':
495 email_address == 'anonymous@rhodecode.org':
496 return "/images/user%s.png" % size
496 return "/images/user%s.png" % size
497
497
498 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
498 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
499 default = 'identicon'
499 default = 'identicon'
500 baseurl_nossl = "http://www.gravatar.com/avatar/"
500 baseurl_nossl = "http://www.gravatar.com/avatar/"
501 baseurl_ssl = "https://secure.gravatar.com/avatar/"
501 baseurl_ssl = "https://secure.gravatar.com/avatar/"
502 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
502 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
503
503
504 if isinstance(email_address, unicode):
504 if isinstance(email_address, unicode):
505 #hashlib crashes on unicode items
505 #hashlib crashes on unicode items
506 email_address = email_address.encode('utf8', 'replace')
506 email_address = email_address.encode('utf8', 'replace')
507 # construct the url
507 # construct the url
508 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
508 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
509 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
509 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
510
510
511 return gravatar_url
511 return gravatar_url
512
512
513
513
514 #==============================================================================
514 #==============================================================================
515 # REPO PAGER, PAGER FOR REPOSITORY
515 # REPO PAGER, PAGER FOR REPOSITORY
516 #==============================================================================
516 #==============================================================================
517 class RepoPage(Page):
517 class RepoPage(Page):
518
518
519 def __init__(self, collection, page=1, items_per_page=20,
519 def __init__(self, collection, page=1, items_per_page=20,
520 item_count=None, url=None, branch_name=None, **kwargs):
520 item_count=None, url=None, branch_name=None, **kwargs):
521
521
522 """Create a "RepoPage" instance. special pager for paging
522 """Create a "RepoPage" instance. special pager for paging
523 repository
523 repository
524 """
524 """
525 self._url_generator = url
525 self._url_generator = url
526
526
527 # Safe the kwargs class-wide so they can be used in the pager() method
527 # Safe the kwargs class-wide so they can be used in the pager() method
528 self.kwargs = kwargs
528 self.kwargs = kwargs
529
529
530 # Save a reference to the collection
530 # Save a reference to the collection
531 self.original_collection = collection
531 self.original_collection = collection
532
532
533 self.collection = collection
533 self.collection = collection
534
534
535 # The self.page is the number of the current page.
535 # The self.page is the number of the current page.
536 # The first page has the number 1!
536 # The first page has the number 1!
537 try:
537 try:
538 self.page = int(page) # make it int() if we get it as a string
538 self.page = int(page) # make it int() if we get it as a string
539 except (ValueError, TypeError):
539 except (ValueError, TypeError):
540 self.page = 1
540 self.page = 1
541
541
542 self.items_per_page = items_per_page
542 self.items_per_page = items_per_page
543
543
544 # Unless the user tells us how many items the collections has
544 # Unless the user tells us how many items the collections has
545 # we calculate that ourselves.
545 # we calculate that ourselves.
546 if item_count is not None:
546 if item_count is not None:
547 self.item_count = item_count
547 self.item_count = item_count
548 else:
548 else:
549 self.item_count = len(self.collection)
549 self.item_count = len(self.collection)
550
550
551 # Compute the number of the first and last available page
551 # Compute the number of the first and last available page
552 if self.item_count > 0:
552 if self.item_count > 0:
553 self.first_page = 1
553 self.first_page = 1
554 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
554 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
555 self.last_page = self.first_page + self.page_count - 1
555 self.last_page = self.first_page + self.page_count - 1
556
556
557 # Make sure that the requested page number is the range of valid pages
557 # Make sure that the requested page number is the range of valid pages
558 if self.page > self.last_page:
558 if self.page > self.last_page:
559 self.page = self.last_page
559 self.page = self.last_page
560 elif self.page < self.first_page:
560 elif self.page < self.first_page:
561 self.page = self.first_page
561 self.page = self.first_page
562
562
563 # Note: the number of items on this page can be less than
563 # Note: the number of items on this page can be less than
564 # items_per_page if the last page is not full
564 # items_per_page if the last page is not full
565 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
565 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
566 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
566 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
567
567
568 iterator = self.collection.get_changesets(start=self.first_item,
568 iterator = self.collection.get_changesets(start=self.first_item,
569 end=self.last_item,
569 end=self.last_item,
570 reverse=True,
570 reverse=True,
571 branch_name=branch_name)
571 branch_name=branch_name)
572 self.items = list(iterator)
572 self.items = list(iterator)
573
573
574 # Links to previous and next page
574 # Links to previous and next page
575 if self.page > self.first_page:
575 if self.page > self.first_page:
576 self.previous_page = self.page - 1
576 self.previous_page = self.page - 1
577 else:
577 else:
578 self.previous_page = None
578 self.previous_page = None
579
579
580 if self.page < self.last_page:
580 if self.page < self.last_page:
581 self.next_page = self.page + 1
581 self.next_page = self.page + 1
582 else:
582 else:
583 self.next_page = None
583 self.next_page = None
584
584
585 # No items available
585 # No items available
586 else:
586 else:
587 self.first_page = None
587 self.first_page = None
588 self.page_count = 0
588 self.page_count = 0
589 self.last_page = None
589 self.last_page = None
590 self.first_item = None
590 self.first_item = None
591 self.last_item = None
591 self.last_item = None
592 self.previous_page = None
592 self.previous_page = None
593 self.next_page = None
593 self.next_page = None
594 self.items = []
594 self.items = []
595
595
596 # This is a subclass of the 'list' type. Initialise the list now.
596 # This is a subclass of the 'list' type. Initialise the list now.
597 list.__init__(self, self.items)
597 list.__init__(self, self.items)
598
598
599
599
600 def changed_tooltip(nodes):
600 def changed_tooltip(nodes):
601 """
601 """
602 Generates a html string for changed nodes in changeset page.
602 Generates a html string for changed nodes in changeset page.
603 It limits the output to 30 entries
603 It limits the output to 30 entries
604
604
605 :param nodes: LazyNodesGenerator
605 :param nodes: LazyNodesGenerator
606 """
606 """
607 if nodes:
607 if nodes:
608 pref = ': <br/> '
608 pref = ': <br/> '
609 suf = ''
609 suf = ''
610 if len(nodes) > 30:
610 if len(nodes) > 30:
611 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
611 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
612 return literal(pref + '<br/> '.join([safe_unicode(x.path)
612 return literal(pref + '<br/> '.join([safe_unicode(x.path)
613 for x in nodes[:30]]) + suf)
613 for x in nodes[:30]]) + suf)
614 else:
614 else:
615 return ': ' + _('No Files')
615 return ': ' + _('No Files')
616
616
617
617
618
618
619 def repo_link(groups_and_repos):
619 def repo_link(groups_and_repos):
620 """
620 """
621 Makes a breadcrumbs link to repo within a group
621 Makes a breadcrumbs link to repo within a group
622 joins &raquo; on each group to create a fancy link
622 joins &raquo; on each group to create a fancy link
623
623
624 ex::
624 ex::
625 group >> subgroup >> repo
625 group >> subgroup >> repo
626
626
627 :param groups_and_repos:
627 :param groups_and_repos:
628 """
628 """
629 groups, repo_name = groups_and_repos
629 groups, repo_name = groups_and_repos
630
630
631 if not groups:
631 if not groups:
632 return repo_name
632 return repo_name
633 else:
633 else:
634 def make_link(group):
634 def make_link(group):
635 return link_to(group.group_name, url('repos_group',
635 return link_to(group.group_name, url('repos_group',
636 id=group.group_id))
636 id=group.group_id))
637 return literal(' &raquo; '.join(map(make_link, groups)) + \
637 return literal(' &raquo; '.join(map(make_link, groups)) + \
638 " &raquo; " + repo_name)
638 " &raquo; " + repo_name)
639
639
640
640
641 def fancy_file_stats(stats):
641 def fancy_file_stats(stats):
642 """
642 """
643 Displays a fancy two colored bar for number of added/deleted
643 Displays a fancy two colored bar for number of added/deleted
644 lines of code on file
644 lines of code on file
645
645
646 :param stats: two element list of added/deleted lines of code
646 :param stats: two element list of added/deleted lines of code
647 """
647 """
648
648
649 a, d, t = stats[0], stats[1], stats[0] + stats[1]
649 a, d, t = stats[0], stats[1], stats[0] + stats[1]
650 width = 100
650 width = 100
651 unit = float(width) / (t or 1)
651 unit = float(width) / (t or 1)
652
652
653 # needs > 9% of width to be visible or 0 to be hidden
653 # needs > 9% of width to be visible or 0 to be hidden
654 a_p = max(9, unit * a) if a > 0 else 0
654 a_p = max(9, unit * a) if a > 0 else 0
655 d_p = max(9, unit * d) if d > 0 else 0
655 d_p = max(9, unit * d) if d > 0 else 0
656 p_sum = a_p + d_p
656 p_sum = a_p + d_p
657
657
658 if p_sum > width:
658 if p_sum > width:
659 #adjust the percentage to be == 100% since we adjusted to 9
659 #adjust the percentage to be == 100% since we adjusted to 9
660 if a_p > d_p:
660 if a_p > d_p:
661 a_p = a_p - (p_sum - width)
661 a_p = a_p - (p_sum - width)
662 else:
662 else:
663 d_p = d_p - (p_sum - width)
663 d_p = d_p - (p_sum - width)
664
664
665 a_v = a if a > 0 else ''
665 a_v = a if a > 0 else ''
666 d_v = d if d > 0 else ''
666 d_v = d if d > 0 else ''
667
667
668
668
669 def cgen(l_type):
669 def cgen(l_type):
670 mapping = {'tr':'top-right-rounded-corner',
670 mapping = {'tr':'top-right-rounded-corner',
671 'tl':'top-left-rounded-corner',
671 'tl':'top-left-rounded-corner',
672 'br':'bottom-right-rounded-corner',
672 'br':'bottom-right-rounded-corner',
673 'bl':'bottom-left-rounded-corner'}
673 'bl':'bottom-left-rounded-corner'}
674 map_getter = lambda x:mapping[x]
674 map_getter = lambda x:mapping[x]
675
675
676 if l_type == 'a' and d_v:
676 if l_type == 'a' and d_v:
677 #case when added and deleted are present
677 #case when added and deleted are present
678 return ' '.join(map(map_getter, ['tl', 'bl']))
678 return ' '.join(map(map_getter, ['tl', 'bl']))
679
679
680 if l_type == 'a' and not d_v:
680 if l_type == 'a' and not d_v:
681 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
681 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
682
682
683 if l_type == 'd' and a_v:
683 if l_type == 'd' and a_v:
684 return ' '.join(map(map_getter, ['tr', 'br']))
684 return ' '.join(map(map_getter, ['tr', 'br']))
685
685
686 if l_type == 'd' and not a_v:
686 if l_type == 'd' and not a_v:
687 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
687 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
688
688
689
689
690
690
691 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
691 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
692 a_p, a_v)
692 a_p, a_v)
693 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
693 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
694 d_p, d_v)
694 d_p, d_v)
695 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
695 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
General Comments 0
You need to be logged in to leave comments. Login now