##// END OF EJS Templates
updated changelog, small fix for journal
marcink -
r1055:903aadbf beta
parent child Browse files
Show More
@@ -1,193 +1,200 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 Changelog
3 Changelog
4 =========
4 =========
5
5
6 1.2.0 (**2011-XX-XX**)
6 1.2.0 (**2011-XX-XX**)
7 ======================
7 ======================
8
8
9 :status: in-progress
9 :status: in-progress
10 :branch: beta
10 :branch: beta
11
11
12 news
12 news
13 ----
13 ----
14
14
15 - implemented #89 Can setup google analytics code from settings menu
15 - implemented #89 Can setup google analytics code from settings menu
16 - implemented #91 added nicer looking archive urls with more download options
16 - implemented #91 added nicer looking archive urls with more download options
17 like tags, branches
17 like tags, branches
18 - implemented #44 into file browsing, and added follow branch option
18 - implemented #44 into file browsing, and added follow branch option
19 - implemented #102 allowing '.' in username
19 - implemented #102 allowing '.' in username
20 - implemented #84 downloads can be enabled/disabled for each repository
20 - implemented #84 downloads can be enabled/disabled for each repository
21 - anonymous repository can be cloned without having to pass default:default
21 - anonymous repository can be cloned without having to pass default:default
22 into clone url
22 into clone url
23 - fixed #90 whoosh indexer can index chooses repositories passed in command
23 - fixed #90 whoosh indexer can index chooses repositories passed in command
24 line
24 line
25 - extended journal with day aggregates and paging
25 - extended journal with day aggregates and paging
26 - implemented #107 customizable code highlights on code sources
26 - implemented #107 customizable code highlights on code sources
27 - implemented #93 customizable changelog on combined revision ranges -
27 - implemented #93 customizable changelog on combined revision ranges -
28 equivalent of githubs compare view
28 equivalent of githubs compare view
29 - implemented #108 extended and more powerfull LDAP configuration
29 - implemented #108 extended and more powerfull LDAP configuration
30 - implemented #56 users groups
31 - major code rewrites optimized codes for speed and memory usage
32 - raw and diff downloads are now in git format
30
33
31 fixes
34 fixes
32 -----
35 -----
33
36
34 - fixed file browser bug, when switching into given form revision the url was
37 - fixed file browser bug, when switching into given form revision the url was
35 not changing
38 not changing
36 - fixed propagation to error controller on simplehg and simplegit middlewares
39 - fixed propagation to error controller on simplehg and simplegit middlewares
37 - fixed error when trying to make a download on empty repository
40 - fixed error when trying to make a download on empty repository
38 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
41 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
39 apollo13 and Johan Walles
42 apollo13 and Johan Walles
40 - fixed problem with '[' chars in commit messages in journal
43 - fixed problem with '[' chars in commit messages in journal
41 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
44 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
42 - fixed #106 relation issues on databases different than sqlite
45 - fixed #106 relation issues on databases different than sqlite
43 - registration,password reset and login boxes share the same title as main
46 - registration,password reset and login boxes share the same title as main
44 application now
47 application now
45 - fixed problems with getting setting for celery from .ini files
48 - fixed problems with getting setting for celery from .ini files
46
49 - journal fork fixes
50 - fixed #113: to high permissions to fork repository
51 - fixed static files paths links to use of url() method
52 - db transaction fixes when filesystem repositry creation failed
53 - removed issue with space inside renamed repository after deletion
47
54
48 1.1.2 (**2011-01-12**)
55 1.1.2 (**2011-01-12**)
49 ======================
56 ======================
50
57
51 news
58 news
52 ----
59 ----
53
60
54
61
55 fixes
62 fixes
56 -----
63 -----
57
64
58 - fixes #98 protection against float division of percentage stats
65 - fixes #98 protection against float division of percentage stats
59 - fixed graph bug
66 - fixed graph bug
60 - forced webhelpers version since it was making troubles during installation
67 - forced webhelpers version since it was making troubles during installation
61
68
62
69
63 1.1.1 (**2011-01-06**)
70 1.1.1 (**2011-01-06**)
64 ======================
71 ======================
65
72
66 news
73 news
67 ----
74 ----
68
75
69 - added force https option into ini files for easier https usage (no need to
76 - added force https option into ini files for easier https usage (no need to
70 set server headers with this options)
77 set server headers with this options)
71 - small css updates
78 - small css updates
72
79
73 fixes
80 fixes
74 -----
81 -----
75
82
76 - fixed #96 redirect loop on files view on repositories without changesets
83 - fixed #96 redirect loop on files view on repositories without changesets
77 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
84 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
78 and server crashed with errors
85 and server crashed with errors
79 - fixed large tooltips problems on main page
86 - fixed large tooltips problems on main page
80 - fixed #92 whoosh indexer is more error proof
87 - fixed #92 whoosh indexer is more error proof
81
88
82 1.1.0 (**2010-12-18**)
89 1.1.0 (**2010-12-18**)
83 ======================
90 ======================
84
91
85 news
92 news
86 ----
93 ----
87
94
88 - rewrite of internals for vcs >=0.1.10
95 - rewrite of internals for vcs >=0.1.10
89 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
96 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
90 with older clients
97 with older clients
91 - anonymous access, authentication via ldap
98 - anonymous access, authentication via ldap
92 - performance upgrade for cached repos list - each repository has it's own
99 - performance upgrade for cached repos list - each repository has it's own
93 cache that's invalidated when needed.
100 cache that's invalidated when needed.
94 - performance upgrades on repositories with large amount of commits (20K+)
101 - performance upgrades on repositories with large amount of commits (20K+)
95 - main page quick filter for filtering repositories
102 - main page quick filter for filtering repositories
96 - user dashboards with ability to follow chosen repositories actions
103 - user dashboards with ability to follow chosen repositories actions
97 - sends email to admin on new user registration
104 - sends email to admin on new user registration
98 - added cache/statistics reset options into repository settings
105 - added cache/statistics reset options into repository settings
99 - more detailed action logger (based on hooks) with pushed changesets lists
106 - more detailed action logger (based on hooks) with pushed changesets lists
100 and options to disable those hooks from admin panel
107 and options to disable those hooks from admin panel
101 - introduced new enhanced changelog for merges that shows more accurate results
108 - introduced new enhanced changelog for merges that shows more accurate results
102 - new improved and faster code stats (based on pygments lexers mapping tables,
109 - new improved and faster code stats (based on pygments lexers mapping tables,
103 showing up to 10 trending sources for each repository. Additionally stats
110 showing up to 10 trending sources for each repository. Additionally stats
104 can be disabled in repository settings.
111 can be disabled in repository settings.
105 - gui optimizations, fixed application width to 1024px
112 - gui optimizations, fixed application width to 1024px
106 - added cut off (for large files/changesets) limit into config files
113 - added cut off (for large files/changesets) limit into config files
107 - whoosh, celeryd, upgrade moved to paster command
114 - whoosh, celeryd, upgrade moved to paster command
108 - other than sqlite database backends can be used
115 - other than sqlite database backends can be used
109
116
110 fixes
117 fixes
111 -----
118 -----
112
119
113 - fixes #61 forked repo was showing only after cache expired
120 - fixes #61 forked repo was showing only after cache expired
114 - fixes #76 no confirmation on user deletes
121 - fixes #76 no confirmation on user deletes
115 - fixes #66 Name field misspelled
122 - fixes #66 Name field misspelled
116 - fixes #72 block user removal when he owns repositories
123 - fixes #72 block user removal when he owns repositories
117 - fixes #69 added password confirmation fields
124 - fixes #69 added password confirmation fields
118 - fixes #87 RhodeCode crashes occasionally on updating repository owner
125 - fixes #87 RhodeCode crashes occasionally on updating repository owner
119 - fixes #82 broken annotations on files with more than 1 blank line at the end
126 - fixes #82 broken annotations on files with more than 1 blank line at the end
120 - a lot of fixes and tweaks for file browser
127 - a lot of fixes and tweaks for file browser
121 - fixed detached session issues
128 - fixed detached session issues
122 - fixed when user had no repos he would see all repos listed in my account
129 - fixed when user had no repos he would see all repos listed in my account
123 - fixed ui() instance bug when global hgrc settings was loaded for server
130 - fixed ui() instance bug when global hgrc settings was loaded for server
124 instance and all hgrc options were merged with our db ui() object
131 instance and all hgrc options were merged with our db ui() object
125 - numerous small bugfixes
132 - numerous small bugfixes
126
133
127 (special thanks for TkSoh for detailed feedback)
134 (special thanks for TkSoh for detailed feedback)
128
135
129
136
130 1.0.2 (**2010-11-12**)
137 1.0.2 (**2010-11-12**)
131 ======================
138 ======================
132
139
133 news
140 news
134 ----
141 ----
135
142
136 - tested under python2.7
143 - tested under python2.7
137 - bumped sqlalchemy and celery versions
144 - bumped sqlalchemy and celery versions
138
145
139 fixes
146 fixes
140 -----
147 -----
141
148
142 - fixed #59 missing graph.js
149 - fixed #59 missing graph.js
143 - fixed repo_size crash when repository had broken symlinks
150 - fixed repo_size crash when repository had broken symlinks
144 - fixed python2.5 crashes.
151 - fixed python2.5 crashes.
145
152
146
153
147 1.0.1 (**2010-11-10**)
154 1.0.1 (**2010-11-10**)
148 ======================
155 ======================
149
156
150 news
157 news
151 ----
158 ----
152
159
153 - small css updated
160 - small css updated
154
161
155 fixes
162 fixes
156 -----
163 -----
157
164
158 - fixed #53 python2.5 incompatible enumerate calls
165 - fixed #53 python2.5 incompatible enumerate calls
159 - fixed #52 disable mercurial extension for web
166 - fixed #52 disable mercurial extension for web
160 - fixed #51 deleting repositories don't delete it's dependent objects
167 - fixed #51 deleting repositories don't delete it's dependent objects
161
168
162
169
163 1.0.0 (**2010-11-02**)
170 1.0.0 (**2010-11-02**)
164 ======================
171 ======================
165
172
166 - security bugfix simplehg wasn't checking for permissions on commands
173 - security bugfix simplehg wasn't checking for permissions on commands
167 other than pull or push.
174 other than pull or push.
168 - fixed doubled messages after push or pull in admin journal
175 - fixed doubled messages after push or pull in admin journal
169 - templating and css corrections, fixed repo switcher on chrome, updated titles
176 - templating and css corrections, fixed repo switcher on chrome, updated titles
170 - admin menu accessible from options menu on repository view
177 - admin menu accessible from options menu on repository view
171 - permissions cached queries
178 - permissions cached queries
172
179
173 1.0.0rc4 (**2010-10-12**)
180 1.0.0rc4 (**2010-10-12**)
174 ==========================
181 ==========================
175
182
176 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
183 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
177 - removed cache_manager settings from sqlalchemy meta
184 - removed cache_manager settings from sqlalchemy meta
178 - added sqlalchemy cache settings to ini files
185 - added sqlalchemy cache settings to ini files
179 - validated password length and added second try of failure on paster setup-app
186 - validated password length and added second try of failure on paster setup-app
180 - fixed setup database destroy prompt even when there was no db
187 - fixed setup database destroy prompt even when there was no db
181
188
182
189
183 1.0.0rc3 (**2010-10-11**)
190 1.0.0rc3 (**2010-10-11**)
184 =========================
191 =========================
185
192
186 - fixed i18n during installation.
193 - fixed i18n during installation.
187
194
188 1.0.0rc2 (**2010-10-11**)
195 1.0.0rc2 (**2010-10-11**)
189 =========================
196 =========================
190
197
191 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
198 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
192 occure. After vcs is fixed it'll be put back again.
199 occure. After vcs is fixed it'll be put back again.
193 - templating/css rewrites, optimized css. No newline at end of file
200 - templating/css rewrites, optimized css.
@@ -1,593 +1,593 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 from pygments.formatters import HtmlFormatter
9 from pygments.formatters import HtmlFormatter
10 from pygments import highlight as code_highlight
10 from pygments import highlight as code_highlight
11 from pylons import url
11 from pylons import url
12 from pylons.i18n.translation import _, ungettext
12 from pylons.i18n.translation import _, ungettext
13 from vcs.utils.annotate import annotate_highlight
13 from vcs.utils.annotate import annotate_highlight
14 from rhodecode.lib.utils import repo_name_slug
14 from rhodecode.lib.utils import repo_name_slug
15
15
16 from webhelpers.html import literal, HTML, escape
16 from webhelpers.html import literal, HTML, escape
17 from webhelpers.html.tools import *
17 from webhelpers.html.tools import *
18 from webhelpers.html.builder import make_tag
18 from webhelpers.html.builder import make_tag
19 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
19 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
20 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
20 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
21 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
21 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
22 password, textarea, title, ul, xml_declaration, radio
22 password, textarea, title, ul, xml_declaration, radio
23 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
23 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
24 mail_to, strip_links, strip_tags, tag_re
24 mail_to, strip_links, strip_tags, tag_re
25 from webhelpers.number import format_byte_size, format_bit_size
25 from webhelpers.number import format_byte_size, format_bit_size
26 from webhelpers.pylonslib import Flash as _Flash
26 from webhelpers.pylonslib import Flash as _Flash
27 from webhelpers.pylonslib.secure_form import secure_form
27 from webhelpers.pylonslib.secure_form import secure_form
28 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
28 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
29 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
29 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
30 replace_whitespace, urlify, truncate, wrap_paragraphs
30 replace_whitespace, urlify, truncate, wrap_paragraphs
31 from webhelpers.date import time_ago_in_words
31 from webhelpers.date import time_ago_in_words
32
32
33 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
33 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
34 convert_boolean_attrs, NotGiven
34 convert_boolean_attrs, NotGiven
35
35
36 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
36 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
37 """Reset button
37 """Reset button
38 """
38 """
39 _set_input_attrs(attrs, type, name, value)
39 _set_input_attrs(attrs, type, name, value)
40 _set_id_attr(attrs, id, name)
40 _set_id_attr(attrs, id, name)
41 convert_boolean_attrs(attrs, ["disabled"])
41 convert_boolean_attrs(attrs, ["disabled"])
42 return HTML.input(**attrs)
42 return HTML.input(**attrs)
43
43
44 reset = _reset
44 reset = _reset
45
45
46
46
47 def get_token():
47 def get_token():
48 """Return the current authentication token, creating one if one doesn't
48 """Return the current authentication token, creating one if one doesn't
49 already exist.
49 already exist.
50 """
50 """
51 token_key = "_authentication_token"
51 token_key = "_authentication_token"
52 from pylons import session
52 from pylons import session
53 if not token_key in session:
53 if not token_key in session:
54 try:
54 try:
55 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
55 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
56 except AttributeError: # Python < 2.4
56 except AttributeError: # Python < 2.4
57 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
57 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
58 session[token_key] = token
58 session[token_key] = token
59 if hasattr(session, 'save'):
59 if hasattr(session, 'save'):
60 session.save()
60 session.save()
61 return session[token_key]
61 return session[token_key]
62
62
63 class _GetError(object):
63 class _GetError(object):
64 """Get error from form_errors, and represent it as span wrapped error
64 """Get error from form_errors, and represent it as span wrapped error
65 message
65 message
66
66
67 :param field_name: field to fetch errors for
67 :param field_name: field to fetch errors for
68 :param form_errors: form errors dict
68 :param form_errors: form errors dict
69 """
69 """
70
70
71 def __call__(self, field_name, form_errors):
71 def __call__(self, field_name, form_errors):
72 tmpl = """<span class="error_msg">%s</span>"""
72 tmpl = """<span class="error_msg">%s</span>"""
73 if form_errors and form_errors.has_key(field_name):
73 if form_errors and form_errors.has_key(field_name):
74 return literal(tmpl % form_errors.get(field_name))
74 return literal(tmpl % form_errors.get(field_name))
75
75
76 get_error = _GetError()
76 get_error = _GetError()
77
77
78 class _ToolTip(object):
78 class _ToolTip(object):
79
79
80 def __call__(self, tooltip_title, trim_at=50):
80 def __call__(self, tooltip_title, trim_at=50):
81 """Special function just to wrap our text into nice formatted
81 """Special function just to wrap our text into nice formatted
82 autowrapped text
82 autowrapped text
83
83
84 :param tooltip_title:
84 :param tooltip_title:
85 """
85 """
86
86
87 return wrap_paragraphs(escape(tooltip_title), trim_at)\
87 return wrap_paragraphs(escape(tooltip_title), trim_at)\
88 .replace('\n', '<br/>')
88 .replace('\n', '<br/>')
89
89
90 def activate(self):
90 def activate(self):
91 """Adds tooltip mechanism to the given Html all tooltips have to have
91 """Adds tooltip mechanism to the given Html all tooltips have to have
92 set class `tooltip` and set attribute `tooltip_title`.
92 set class `tooltip` and set attribute `tooltip_title`.
93 Then a tooltip will be generated based on that. All with yui js tooltip
93 Then a tooltip will be generated based on that. All with yui js tooltip
94 """
94 """
95
95
96 js = '''
96 js = '''
97 YAHOO.util.Event.onDOMReady(function(){
97 YAHOO.util.Event.onDOMReady(function(){
98 function toolTipsId(){
98 function toolTipsId(){
99 var ids = [];
99 var ids = [];
100 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
100 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
101
101
102 for (var i = 0; i < tts.length; i++) {
102 for (var i = 0; i < tts.length; i++) {
103 //if element doesn't not have and id autogenerate one for tooltip
103 //if element doesn't not have and id autogenerate one for tooltip
104
104
105 if (!tts[i].id){
105 if (!tts[i].id){
106 tts[i].id='tt'+i*100;
106 tts[i].id='tt'+i*100;
107 }
107 }
108 ids.push(tts[i].id);
108 ids.push(tts[i].id);
109 }
109 }
110 return ids
110 return ids
111 };
111 };
112 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
112 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
113 context: toolTipsId(),
113 context: toolTipsId(),
114 monitorresize:false,
114 monitorresize:false,
115 xyoffset :[0,0],
115 xyoffset :[0,0],
116 autodismissdelay:300000,
116 autodismissdelay:300000,
117 hidedelay:5,
117 hidedelay:5,
118 showdelay:20,
118 showdelay:20,
119 });
119 });
120
120
121 // Set the text for the tooltip just before we display it. Lazy method
121 // Set the text for the tooltip just before we display it. Lazy method
122 myToolTips.contextTriggerEvent.subscribe(
122 myToolTips.contextTriggerEvent.subscribe(
123 function(type, args) {
123 function(type, args) {
124
124
125 var context = args[0];
125 var context = args[0];
126
126
127 //positioning of tooltip
127 //positioning of tooltip
128 var tt_w = this.element.clientWidth;//tooltip width
128 var tt_w = this.element.clientWidth;//tooltip width
129 var tt_h = this.element.clientHeight;//tooltip height
129 var tt_h = this.element.clientHeight;//tooltip height
130
130
131 var context_w = context.offsetWidth;
131 var context_w = context.offsetWidth;
132 var context_h = context.offsetHeight;
132 var context_h = context.offsetHeight;
133
133
134 var pos_x = YAHOO.util.Dom.getX(context);
134 var pos_x = YAHOO.util.Dom.getX(context);
135 var pos_y = YAHOO.util.Dom.getY(context);
135 var pos_y = YAHOO.util.Dom.getY(context);
136
136
137 var display_strategy = 'right';
137 var display_strategy = 'right';
138 var xy_pos = [0,0];
138 var xy_pos = [0,0];
139 switch (display_strategy){
139 switch (display_strategy){
140
140
141 case 'top':
141 case 'top':
142 var cur_x = (pos_x+context_w/2)-(tt_w/2);
142 var cur_x = (pos_x+context_w/2)-(tt_w/2);
143 var cur_y = (pos_y-tt_h-4);
143 var cur_y = (pos_y-tt_h-4);
144 xy_pos = [cur_x,cur_y];
144 xy_pos = [cur_x,cur_y];
145 break;
145 break;
146 case 'bottom':
146 case 'bottom':
147 var cur_x = (pos_x+context_w/2)-(tt_w/2);
147 var cur_x = (pos_x+context_w/2)-(tt_w/2);
148 var cur_y = pos_y+context_h+4;
148 var cur_y = pos_y+context_h+4;
149 xy_pos = [cur_x,cur_y];
149 xy_pos = [cur_x,cur_y];
150 break;
150 break;
151 case 'left':
151 case 'left':
152 var cur_x = (pos_x-tt_w-4);
152 var cur_x = (pos_x-tt_w-4);
153 var cur_y = pos_y-((tt_h/2)-context_h/2);
153 var cur_y = pos_y-((tt_h/2)-context_h/2);
154 xy_pos = [cur_x,cur_y];
154 xy_pos = [cur_x,cur_y];
155 break;
155 break;
156 case 'right':
156 case 'right':
157 var cur_x = (pos_x+context_w+4);
157 var cur_x = (pos_x+context_w+4);
158 var cur_y = pos_y-((tt_h/2)-context_h/2);
158 var cur_y = pos_y-((tt_h/2)-context_h/2);
159 xy_pos = [cur_x,cur_y];
159 xy_pos = [cur_x,cur_y];
160 break;
160 break;
161 default:
161 default:
162 var cur_x = (pos_x+context_w/2)-(tt_w/2);
162 var cur_x = (pos_x+context_w/2)-(tt_w/2);
163 var cur_y = pos_y-tt_h-4;
163 var cur_y = pos_y-tt_h-4;
164 xy_pos = [cur_x,cur_y];
164 xy_pos = [cur_x,cur_y];
165 break;
165 break;
166
166
167 }
167 }
168
168
169 this.cfg.setProperty("xy",xy_pos);
169 this.cfg.setProperty("xy",xy_pos);
170
170
171 });
171 });
172
172
173 //Mouse out
173 //Mouse out
174 myToolTips.contextMouseOutEvent.subscribe(
174 myToolTips.contextMouseOutEvent.subscribe(
175 function(type, args) {
175 function(type, args) {
176 var context = args[0];
176 var context = args[0];
177
177
178 });
178 });
179 });
179 });
180 '''
180 '''
181 return literal(js)
181 return literal(js)
182
182
183 tooltip = _ToolTip()
183 tooltip = _ToolTip()
184
184
185 class _FilesBreadCrumbs(object):
185 class _FilesBreadCrumbs(object):
186
186
187 def __call__(self, repo_name, rev, paths):
187 def __call__(self, repo_name, rev, paths):
188 if isinstance(paths, str):
188 if isinstance(paths, str):
189 paths = paths.decode('utf-8')
189 paths = paths.decode('utf-8')
190 url_l = [link_to(repo_name, url('files_home',
190 url_l = [link_to(repo_name, url('files_home',
191 repo_name=repo_name,
191 repo_name=repo_name,
192 revision=rev, f_path=''))]
192 revision=rev, f_path=''))]
193 paths_l = paths.split('/')
193 paths_l = paths.split('/')
194 for cnt, p in enumerate(paths_l):
194 for cnt, p in enumerate(paths_l):
195 if p != '':
195 if p != '':
196 url_l.append(link_to(p, url('files_home',
196 url_l.append(link_to(p, url('files_home',
197 repo_name=repo_name,
197 repo_name=repo_name,
198 revision=rev,
198 revision=rev,
199 f_path='/'.join(paths_l[:cnt + 1]))))
199 f_path='/'.join(paths_l[:cnt + 1]))))
200
200
201 return literal('/'.join(url_l))
201 return literal('/'.join(url_l))
202
202
203 files_breadcrumbs = _FilesBreadCrumbs()
203 files_breadcrumbs = _FilesBreadCrumbs()
204
204
205 class CodeHtmlFormatter(HtmlFormatter):
205 class CodeHtmlFormatter(HtmlFormatter):
206 """My code Html Formatter for source codes
206 """My code Html Formatter for source codes
207 """
207 """
208
208
209 def wrap(self, source, outfile):
209 def wrap(self, source, outfile):
210 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
210 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
211
211
212 def _wrap_code(self, source):
212 def _wrap_code(self, source):
213 for cnt, it in enumerate(source):
213 for cnt, it in enumerate(source):
214 i, t = it
214 i, t = it
215 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
215 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
216 yield i, t
216 yield i, t
217
217
218 def _wrap_tablelinenos(self, inner):
218 def _wrap_tablelinenos(self, inner):
219 dummyoutfile = StringIO.StringIO()
219 dummyoutfile = StringIO.StringIO()
220 lncount = 0
220 lncount = 0
221 for t, line in inner:
221 for t, line in inner:
222 if t:
222 if t:
223 lncount += 1
223 lncount += 1
224 dummyoutfile.write(line)
224 dummyoutfile.write(line)
225
225
226 fl = self.linenostart
226 fl = self.linenostart
227 mw = len(str(lncount + fl - 1))
227 mw = len(str(lncount + fl - 1))
228 sp = self.linenospecial
228 sp = self.linenospecial
229 st = self.linenostep
229 st = self.linenostep
230 la = self.lineanchors
230 la = self.lineanchors
231 aln = self.anchorlinenos
231 aln = self.anchorlinenos
232 nocls = self.noclasses
232 nocls = self.noclasses
233 if sp:
233 if sp:
234 lines = []
234 lines = []
235
235
236 for i in range(fl, fl + lncount):
236 for i in range(fl, fl + lncount):
237 if i % st == 0:
237 if i % st == 0:
238 if i % sp == 0:
238 if i % sp == 0:
239 if aln:
239 if aln:
240 lines.append('<a href="#%s%d" class="special">%*d</a>' %
240 lines.append('<a href="#%s%d" class="special">%*d</a>' %
241 (la, i, mw, i))
241 (la, i, mw, i))
242 else:
242 else:
243 lines.append('<span class="special">%*d</span>' % (mw, i))
243 lines.append('<span class="special">%*d</span>' % (mw, i))
244 else:
244 else:
245 if aln:
245 if aln:
246 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
246 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
247 else:
247 else:
248 lines.append('%*d' % (mw, i))
248 lines.append('%*d' % (mw, i))
249 else:
249 else:
250 lines.append('')
250 lines.append('')
251 ls = '\n'.join(lines)
251 ls = '\n'.join(lines)
252 else:
252 else:
253 lines = []
253 lines = []
254 for i in range(fl, fl + lncount):
254 for i in range(fl, fl + lncount):
255 if i % st == 0:
255 if i % st == 0:
256 if aln:
256 if aln:
257 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
257 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
258 else:
258 else:
259 lines.append('%*d' % (mw, i))
259 lines.append('%*d' % (mw, i))
260 else:
260 else:
261 lines.append('')
261 lines.append('')
262 ls = '\n'.join(lines)
262 ls = '\n'.join(lines)
263
263
264 # in case you wonder about the seemingly redundant <div> here: since the
264 # in case you wonder about the seemingly redundant <div> here: since the
265 # content in the other cell also is wrapped in a div, some browsers in
265 # content in the other cell also is wrapped in a div, some browsers in
266 # some configurations seem to mess up the formatting...
266 # some configurations seem to mess up the formatting...
267 if nocls:
267 if nocls:
268 yield 0, ('<table class="%stable">' % self.cssclass +
268 yield 0, ('<table class="%stable">' % self.cssclass +
269 '<tr><td><div class="linenodiv" '
269 '<tr><td><div class="linenodiv" '
270 'style="background-color: #f0f0f0; padding-right: 10px">'
270 'style="background-color: #f0f0f0; padding-right: 10px">'
271 '<pre style="line-height: 125%">' +
271 '<pre style="line-height: 125%">' +
272 ls + '</pre></div></td><td class="code">')
272 ls + '</pre></div></td><td class="code">')
273 else:
273 else:
274 yield 0, ('<table class="%stable">' % self.cssclass +
274 yield 0, ('<table class="%stable">' % self.cssclass +
275 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
275 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
276 ls + '</pre></div></td><td class="code">')
276 ls + '</pre></div></td><td class="code">')
277 yield 0, dummyoutfile.getvalue()
277 yield 0, dummyoutfile.getvalue()
278 yield 0, '</td></tr></table>'
278 yield 0, '</td></tr></table>'
279
279
280
280
281 def pygmentize(filenode, **kwargs):
281 def pygmentize(filenode, **kwargs):
282 """pygmentize function using pygments
282 """pygmentize function using pygments
283
283
284 :param filenode:
284 :param filenode:
285 """
285 """
286
286
287 return literal(code_highlight(filenode.content,
287 return literal(code_highlight(filenode.content,
288 filenode.lexer, CodeHtmlFormatter(**kwargs)))
288 filenode.lexer, CodeHtmlFormatter(**kwargs)))
289
289
290 def pygmentize_annotation(filenode, **kwargs):
290 def pygmentize_annotation(filenode, **kwargs):
291 """pygmentize function for annotation
291 """pygmentize function for annotation
292
292
293 :param filenode:
293 :param filenode:
294 """
294 """
295
295
296 color_dict = {}
296 color_dict = {}
297 def gen_color(n=10000):
297 def gen_color(n=10000):
298 """generator for getting n of evenly distributed colors using
298 """generator for getting n of evenly distributed colors using
299 hsv color and golden ratio. It always return same order of colors
299 hsv color and golden ratio. It always return same order of colors
300
300
301 :returns: RGB tuple
301 :returns: RGB tuple
302 """
302 """
303 import colorsys
303 import colorsys
304 golden_ratio = 0.618033988749895
304 golden_ratio = 0.618033988749895
305 h = 0.22717784590367374
305 h = 0.22717784590367374
306
306
307 for c in xrange(n):
307 for c in xrange(n):
308 h += golden_ratio
308 h += golden_ratio
309 h %= 1
309 h %= 1
310 HSV_tuple = [h, 0.95, 0.95]
310 HSV_tuple = [h, 0.95, 0.95]
311 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
311 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
312 yield map(lambda x:str(int(x * 256)), RGB_tuple)
312 yield map(lambda x:str(int(x * 256)), RGB_tuple)
313
313
314 cgenerator = gen_color()
314 cgenerator = gen_color()
315
315
316 def get_color_string(cs):
316 def get_color_string(cs):
317 if color_dict.has_key(cs):
317 if color_dict.has_key(cs):
318 col = color_dict[cs]
318 col = color_dict[cs]
319 else:
319 else:
320 col = color_dict[cs] = cgenerator.next()
320 col = color_dict[cs] = cgenerator.next()
321 return "color: rgb(%s)! important;" % (', '.join(col))
321 return "color: rgb(%s)! important;" % (', '.join(col))
322
322
323 def url_func(changeset):
323 def url_func(changeset):
324 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
324 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
325 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
325 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
326
326
327 tooltip_html = tooltip_html % (changeset.author,
327 tooltip_html = tooltip_html % (changeset.author,
328 changeset.date,
328 changeset.date,
329 tooltip(changeset.message))
329 tooltip(changeset.message))
330 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
330 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
331 short_id(changeset.raw_id))
331 short_id(changeset.raw_id))
332 uri = link_to(
332 uri = link_to(
333 lnk_format,
333 lnk_format,
334 url('changeset_home', repo_name=changeset.repository.name,
334 url('changeset_home', repo_name=changeset.repository.name,
335 revision=changeset.raw_id),
335 revision=changeset.raw_id),
336 style=get_color_string(changeset.raw_id),
336 style=get_color_string(changeset.raw_id),
337 class_='tooltip',
337 class_='tooltip',
338 title=tooltip_html
338 title=tooltip_html
339 )
339 )
340
340
341 uri += '\n'
341 uri += '\n'
342 return uri
342 return uri
343 return literal(annotate_highlight(filenode, url_func, **kwargs))
343 return literal(annotate_highlight(filenode, url_func, **kwargs))
344
344
345 def get_changeset_safe(repo, rev):
345 def get_changeset_safe(repo, rev):
346 from vcs.backends.base import BaseRepository
346 from vcs.backends.base import BaseRepository
347 from vcs.exceptions import RepositoryError
347 from vcs.exceptions import RepositoryError
348 if not isinstance(repo, BaseRepository):
348 if not isinstance(repo, BaseRepository):
349 raise Exception('You must pass an Repository '
349 raise Exception('You must pass an Repository '
350 'object as first argument got %s', type(repo))
350 'object as first argument got %s', type(repo))
351
351
352 try:
352 try:
353 cs = repo.get_changeset(rev)
353 cs = repo.get_changeset(rev)
354 except RepositoryError:
354 except RepositoryError:
355 from rhodecode.lib.utils import EmptyChangeset
355 from rhodecode.lib.utils import EmptyChangeset
356 cs = EmptyChangeset()
356 cs = EmptyChangeset()
357 return cs
357 return cs
358
358
359
359
360 def is_following_repo(repo_name, user_id):
360 def is_following_repo(repo_name, user_id):
361 from rhodecode.model.scm import ScmModel
361 from rhodecode.model.scm import ScmModel
362 return ScmModel().is_following_repo(repo_name, user_id)
362 return ScmModel().is_following_repo(repo_name, user_id)
363
363
364 flash = _Flash()
364 flash = _Flash()
365
365
366
366
367 #==============================================================================
367 #==============================================================================
368 # MERCURIAL FILTERS available via h.
368 # MERCURIAL FILTERS available via h.
369 #==============================================================================
369 #==============================================================================
370 from mercurial import util
370 from mercurial import util
371 from mercurial.templatefilters import person as _person
371 from mercurial.templatefilters import person as _person
372
372
373 def _age(curdate):
373 def _age(curdate):
374 """turns a datetime into an age string."""
374 """turns a datetime into an age string."""
375
375
376 if not curdate:
376 if not curdate:
377 return ''
377 return ''
378
378
379 from datetime import timedelta, datetime
379 from datetime import timedelta, datetime
380
380
381 agescales = [("year", 3600 * 24 * 365),
381 agescales = [("year", 3600 * 24 * 365),
382 ("month", 3600 * 24 * 30),
382 ("month", 3600 * 24 * 30),
383 ("day", 3600 * 24),
383 ("day", 3600 * 24),
384 ("hour", 3600),
384 ("hour", 3600),
385 ("minute", 60),
385 ("minute", 60),
386 ("second", 1), ]
386 ("second", 1), ]
387
387
388 age = datetime.now() - curdate
388 age = datetime.now() - curdate
389 age_seconds = (age.days * agescales[2][1]) + age.seconds
389 age_seconds = (age.days * agescales[2][1]) + age.seconds
390 pos = 1
390 pos = 1
391 for scale in agescales:
391 for scale in agescales:
392 if scale[1] <= age_seconds:
392 if scale[1] <= age_seconds:
393 if pos == 6:pos = 5
393 if pos == 6:pos = 5
394 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
394 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
395 pos += 1
395 pos += 1
396
396
397 return _('just now')
397 return _('just now')
398
398
399 age = lambda x:_age(x)
399 age = lambda x:_age(x)
400 capitalize = lambda x: x.capitalize()
400 capitalize = lambda x: x.capitalize()
401 email = util.email
401 email = util.email
402 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
402 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
403 person = lambda x: _person(x)
403 person = lambda x: _person(x)
404 short_id = lambda x: x[:12]
404 short_id = lambda x: x[:12]
405
405
406
406
407 def bool2icon(value):
407 def bool2icon(value):
408 """Returns True/False values represented as small html image of true/false
408 """Returns True/False values represented as small html image of true/false
409 icons
409 icons
410
410
411 :param value: bool value
411 :param value: bool value
412 """
412 """
413
413
414 if value is True:
414 if value is True:
415 return HTML.tag('img', src=url("/images/icons/accept.png"),
415 return HTML.tag('img', src=url("/images/icons/accept.png"),
416 alt=_('True'))
416 alt=_('True'))
417
417
418 if value is False:
418 if value is False:
419 return HTML.tag('img', src=url("/images/icons/cancel.png"),
419 return HTML.tag('img', src=url("/images/icons/cancel.png"),
420 alt=_('False'))
420 alt=_('False'))
421
421
422 return value
422 return value
423
423
424
424
425 def action_parser(user_log):
425 def action_parser(user_log):
426 """This helper will map the specified string action into translated
426 """This helper will map the specified string action into translated
427 fancy names with icons and links
427 fancy names with icons and links
428
428
429 :param user_log: user log instance
429 :param user_log: user log instance
430 """
430 """
431
431
432 action = user_log.action
432 action = user_log.action
433 action_params = ' '
433 action_params = ' '
434
434
435 x = action.split(':')
435 x = action.split(':')
436
436
437 if len(x) > 1:
437 if len(x) > 1:
438 action, action_params = x
438 action, action_params = x
439
439
440 def get_cs_links():
440 def get_cs_links():
441 revs_limit = 5 #display this amount always
441 revs_limit = 5 #display this amount always
442 revs_top_limit = 50 #show upto this amount of changesets hidden
442 revs_top_limit = 50 #show upto this amount of changesets hidden
443 revs = action_params.split(',')
443 revs = action_params.split(',')
444 repo_name = user_log.repository.repo_name
444 repo_name = user_log.repository.repo_name
445
445
446 from rhodecode.model.scm import ScmModel
446 from rhodecode.model.scm import ScmModel
447 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
447 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
448 invalidation_list=[])
448 invalidation_list=[])
449
449
450 message = lambda rev: get_changeset_safe(repo, rev).message
450 message = lambda rev: get_changeset_safe(repo, rev).message
451
451
452 cs_links = " " + ', '.join ([link_to(rev,
452 cs_links = " " + ', '.join ([link_to(rev,
453 url('changeset_home',
453 url('changeset_home',
454 repo_name=repo_name,
454 repo_name=repo_name,
455 revision=rev), title=tooltip(message(rev)),
455 revision=rev), title=tooltip(message(rev)),
456 class_='tooltip') for rev in revs[:revs_limit] ])
456 class_='tooltip') for rev in revs[:revs_limit] ])
457
457
458 compare_view = (' <div class="compare_view tooltip" title="%s">'
458 compare_view = (' <div class="compare_view tooltip" title="%s">'
459 '<a href="%s">%s</a> '
459 '<a href="%s">%s</a> '
460 '</div>' % (_('Show all combined changesets %s->%s' \
460 '</div>' % (_('Show all combined changesets %s->%s' \
461 % (revs[0], revs[-1])),
461 % (revs[0], revs[-1])),
462 url('changeset_home', repo_name=repo_name,
462 url('changeset_home', repo_name=repo_name,
463 revision='%s...%s' % (revs[0], revs[-1])
463 revision='%s...%s' % (revs[0], revs[-1])
464 ),
464 ),
465 _('compare view'))
465 _('compare view'))
466 )
466 )
467
467
468 if len(revs) > revs_limit:
468 if len(revs) > revs_limit:
469 uniq_id = revs[0]
469 uniq_id = revs[0]
470 html_tmpl = ('<span> %s '
470 html_tmpl = ('<span> %s '
471 '<a class="show_more" id="_%s" href="#more">%s</a> '
471 '<a class="show_more" id="_%s" href="#more">%s</a> '
472 '%s</span>')
472 '%s</span>')
473 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
473 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
474 % (len(revs) - revs_limit),
474 % (len(revs) - revs_limit),
475 _('revisions'))
475 _('revisions'))
476
476
477 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
477 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
478 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
478 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
479 url('changeset_home',
479 url('changeset_home',
480 repo_name=repo_name, revision=rev),
480 repo_name=repo_name, revision=rev),
481 title=message(rev), class_='tooltip')
481 title=message(rev), class_='tooltip')
482 for rev in revs[revs_limit:revs_top_limit]]))
482 for rev in revs[revs_limit:revs_top_limit]]))
483 if len(revs) > 1:
483 if len(revs) > 1:
484 cs_links += compare_view
484 cs_links += compare_view
485 return cs_links
485 return cs_links
486
486
487 def get_fork_name():
487 def get_fork_name():
488 repo_name = action_params
488 repo_name = action_params
489 return str(link_to(action_params, url('summary_home',
489 return _('fork name ') + str(link_to(action_params, url('summary_home',
490 repo_name=repo_name,)))
490 repo_name=repo_name,)))
491
491
492 map = {'user_deleted_repo':(_('[deleted] repository'), None),
492 map = {'user_deleted_repo':(_('[deleted] repository'), None),
493 'user_created_repo':(_('[created] repository'), None),
493 'user_created_repo':(_('[created] repository'), None),
494 'user_forked_repo':(_('[forked] repository as:'), get_fork_name),
494 'user_forked_repo':(_('[forked] repository'), get_fork_name),
495 'user_updated_repo':(_('[updated] repository'), None),
495 'user_updated_repo':(_('[updated] repository'), None),
496 'admin_deleted_repo':(_('[delete] repository'), None),
496 'admin_deleted_repo':(_('[delete] repository'), None),
497 'admin_created_repo':(_('[created] repository'), None),
497 'admin_created_repo':(_('[created] repository'), None),
498 'admin_forked_repo':(_('[forked] repository'), None),
498 'admin_forked_repo':(_('[forked] repository'), None),
499 'admin_updated_repo':(_('[updated] repository'), None),
499 'admin_updated_repo':(_('[updated] repository'), None),
500 'push':(_('[pushed] into'), get_cs_links),
500 'push':(_('[pushed] into'), get_cs_links),
501 'pull':(_('[pulled] from'), None),
501 'pull':(_('[pulled] from'), None),
502 'started_following_repo':(_('[started following] repository'), None),
502 'started_following_repo':(_('[started following] repository'), None),
503 'stopped_following_repo':(_('[stopped following] repository'), None),
503 'stopped_following_repo':(_('[stopped following] repository'), None),
504 }
504 }
505
505
506 action_str = map.get(action, action)
506 action_str = map.get(action, action)
507 action = action_str[0].replace('[', '<span class="journal_highlight">')\
507 action = action_str[0].replace('[', '<span class="journal_highlight">')\
508 .replace(']', '</span>')
508 .replace(']', '</span>')
509 action_params_func = lambda :""
509 action_params_func = lambda :""
510
510
511 if action_str[1] is not None:
511 if action_str[1] is not None:
512 action_params_func = action_str[1]
512 action_params_func = action_str[1]
513
513
514 return [literal(action), action_params_func]
514 return [literal(action), action_params_func]
515
515
516 def action_parser_icon(user_log):
516 def action_parser_icon(user_log):
517 action = user_log.action
517 action = user_log.action
518 action_params = None
518 action_params = None
519 x = action.split(':')
519 x = action.split(':')
520
520
521 if len(x) > 1:
521 if len(x) > 1:
522 action, action_params = x
522 action, action_params = x
523
523
524 tmpl = """<img src="%s/%s" alt="%s"/>"""
524 tmpl = """<img src="%s/%s" alt="%s"/>"""
525 map = {'user_deleted_repo':'database_delete.png',
525 map = {'user_deleted_repo':'database_delete.png',
526 'user_created_repo':'database_add.png',
526 'user_created_repo':'database_add.png',
527 'user_forked_repo':'arrow_divide.png',
527 'user_forked_repo':'arrow_divide.png',
528 'user_updated_repo':'database_edit.png',
528 'user_updated_repo':'database_edit.png',
529 'admin_deleted_repo':'database_delete.png',
529 'admin_deleted_repo':'database_delete.png',
530 'admin_created_repo':'database_add.png',
530 'admin_created_repo':'database_add.png',
531 'admin_forked_repo':'arrow_divide.png',
531 'admin_forked_repo':'arrow_divide.png',
532 'admin_updated_repo':'database_edit.png',
532 'admin_updated_repo':'database_edit.png',
533 'push':'script_add.png',
533 'push':'script_add.png',
534 'pull':'down_16.png',
534 'pull':'down_16.png',
535 'started_following_repo':'heart_add.png',
535 'started_following_repo':'heart_add.png',
536 'stopped_following_repo':'heart_delete.png',
536 'stopped_following_repo':'heart_delete.png',
537 }
537 }
538 return literal(tmpl % ((url('/images/icons/')),
538 return literal(tmpl % ((url('/images/icons/')),
539 map.get(action, action), action))
539 map.get(action, action), action))
540
540
541
541
542 #==============================================================================
542 #==============================================================================
543 # PERMS
543 # PERMS
544 #==============================================================================
544 #==============================================================================
545 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
545 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
546 HasRepoPermissionAny, HasRepoPermissionAll
546 HasRepoPermissionAny, HasRepoPermissionAll
547
547
548 #==============================================================================
548 #==============================================================================
549 # GRAVATAR URL
549 # GRAVATAR URL
550 #==============================================================================
550 #==============================================================================
551 import hashlib
551 import hashlib
552 import urllib
552 import urllib
553 from pylons import request
553 from pylons import request
554
554
555 def gravatar_url(email_address, size=30):
555 def gravatar_url(email_address, size=30):
556 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
556 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
557 default = 'identicon'
557 default = 'identicon'
558 baseurl_nossl = "http://www.gravatar.com/avatar/"
558 baseurl_nossl = "http://www.gravatar.com/avatar/"
559 baseurl_ssl = "https://secure.gravatar.com/avatar/"
559 baseurl_ssl = "https://secure.gravatar.com/avatar/"
560 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
560 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
561
561
562
562
563 # construct the url
563 # construct the url
564 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
564 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
565 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
565 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
566
566
567 return gravatar_url
567 return gravatar_url
568
568
569 def safe_unicode(str):
569 def safe_unicode(str):
570 """safe unicode function. In case of UnicodeDecode error we try to return
570 """safe unicode function. In case of UnicodeDecode error we try to return
571 unicode with errors replace, if this failes we return unicode with
571 unicode with errors replace, if this failes we return unicode with
572 string_escape decoding """
572 string_escape decoding """
573
573
574 try:
574 try:
575 u_str = unicode(str)
575 u_str = unicode(str)
576 except UnicodeDecodeError:
576 except UnicodeDecodeError:
577 try:
577 try:
578 u_str = unicode(str, 'utf-8', 'replace')
578 u_str = unicode(str, 'utf-8', 'replace')
579 except UnicodeDecodeError:
579 except UnicodeDecodeError:
580 #incase we have a decode error just represent as byte string
580 #incase we have a decode error just represent as byte string
581 u_str = unicode(str(str).encode('string_escape'))
581 u_str = unicode(str(str).encode('string_escape'))
582
582
583 return u_str
583 return u_str
584
584
585 def changed_tooltip(nodes):
585 def changed_tooltip(nodes):
586 if nodes:
586 if nodes:
587 pref = ': <br/> '
587 pref = ': <br/> '
588 suf = ''
588 suf = ''
589 if len(nodes) > 30:
589 if len(nodes) > 30:
590 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
590 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
591 return literal(pref + '<br/> '.join([x.path for x in nodes[:30]]) + suf)
591 return literal(pref + '<br/> '.join([x.path for x in nodes[:30]]) + suf)
592 else:
592 else:
593 return ': ' + _('No Files')
593 return ': ' + _('No Files')
General Comments 0
You need to be logged in to leave comments. Login now