diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py
--- a/rhodecode/lib/helpers.py
+++ b/rhodecode/lib/helpers.py
@@ -7,11 +7,12 @@ import random
import hashlib
import StringIO
import urllib
+import math
from datetime import datetime
from pygments.formatters import HtmlFormatter
from pygments import highlight as code_highlight
-from pylons import url, app_globals as g
+from pylons import url, request, config
from pylons.i18n.translation import _, ungettext
from webhelpers.html import literal, HTML, escape
@@ -36,7 +37,7 @@ from webhelpers.html.tags import _set_in
from vcs.utils.annotate import annotate_highlight
from rhodecode.lib.utils import repo_name_slug
-from rhodecode.lib import str2bool, safe_unicode
+from rhodecode.lib import str2bool, safe_unicode, safe_str,get_changeset_safe
def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
"""
@@ -89,45 +90,7 @@ class _ToolTip(object):
:param tooltip_title:
"""
-
- return wrap_paragraphs(escape(tooltip_title), trim_at)\
- .replace('\n', '
')
-
- def activate(self):
- """Adds tooltip mechanism to the given Html all tooltips have to have
- set class `tooltip` and set attribute `tooltip_title`.
- Then a tooltip will be generated based on that. All with yui js tooltip
- """
-
- js = '''
- YAHOO.util.Event.onDOMReady(function(){
- function toolTipsId(){
- var ids = [];
- var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
-
- for (var i = 0; i < tts.length; i++) {
- //if element doesn't not have and id autogenerate one for tooltip
-
- if (!tts[i].id){
- tts[i].id='tt'+i*100;
- }
- ids.push(tts[i].id);
- }
- return ids
- };
- var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
- context: [[toolTipsId()],"tl","bl",null,[0,5]],
- monitorresize:false,
- xyoffset :[0,0],
- autodismissdelay:300000,
- hidedelay:5,
- showdelay:20,
- });
-
- });
- '''
- return literal(js)
-
+ return escape(tooltip_title)
tooltip = _ToolTip()
class _FilesBreadCrumbs(object):
@@ -160,19 +123,84 @@ class CodeHtmlFormatter(HtmlFormatter):
def _wrap_code(self, source):
for cnt, it in enumerate(source):
i, t = it
- t = '
'
+ ' ' + + ls + ' | ')
+ else:
+ yield 0, ('
Author:" + \
- " %s "
+ def url_func(repo_name):
- tooltip_html = tooltip_html % (changeset.author,
- changeset.date,
- tooltip(changeset.message))
- lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
- short_id(changeset.raw_id))
- uri = link_to(
- lnk_format,
- url('changeset_home', repo_name=changeset.repository.name,
- revision=changeset.raw_id),
- style=get_color_string(changeset.raw_id),
- class_='tooltip',
- title=tooltip_html
- )
+ def _url_func(changeset):
+ author = changeset.author
+ date = changeset.date
+ message = tooltip(changeset.message)
+
+ tooltip_html = ("Date: %s Message: %s Author:"
+ " %s ")
- uri += '\n'
- return uri
- return literal(annotate_highlight(filenode, url_func, **kwargs))
+ tooltip_html = tooltip_html % (author, date, message)
+ lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
+ short_id(changeset.raw_id))
+ uri = link_to(
+ lnk_format,
+ url('changeset_home', repo_name=repo_name,
+ revision=changeset.raw_id),
+ style=get_color_string(changeset.raw_id),
+ class_='tooltip',
+ title=tooltip_html
+ )
-def get_changeset_safe(repo, rev):
- from vcs.backends.base import BaseRepository
- from vcs.exceptions import RepositoryError
- if not isinstance(repo, BaseRepository):
- raise Exception('You must pass an Repository '
- 'object as first argument got %s', type(repo))
+ uri += '\n'
+ return uri
+ return _url_func
- try:
- cs = repo.get_changeset(rev)
- except RepositoryError:
- from rhodecode.lib.utils import EmptyChangeset
- cs = EmptyChangeset()
- return cs
+ return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
+def is_following_repo(repo_name, user_id):
+ from rhodecode.model.scm import ScmModel
+ return ScmModel().is_following_repo(repo_name, user_id)
flash = _Flash()
-
#==============================================================================
-# MERCURIAL FILTERS available via h.
+# SCM FILTERS available via h.
#==============================================================================
-from mercurial import util
-from mercurial.templatefilters import person as _person
-
-def _age(curdate):
- """turns a datetime into an age string."""
-
- if not curdate:
- return ''
-
- agescales = [("year", 3600 * 24 * 365),
- ("month", 3600 * 24 * 30),
- ("day", 3600 * 24),
- ("hour", 3600),
- ("minute", 60),
- ("second", 1), ]
-
- age = datetime.now() - curdate
- age_seconds = (age.days * agescales[2][1]) + age.seconds
- pos = 1
- for scale in agescales:
- if scale[1] <= age_seconds:
- if pos == 6:pos = 5
- return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
- pos += 1
-
- return _('just now')
+from vcs.utils import author_name, author_email
+from rhodecode.lib import credentials_filter, age as _age
age = lambda x:_age(x)
capitalize = lambda x: x.capitalize()
-email = util.email
-email_or_none = lambda x: util.email(x) if util.email(x) != x else None
-person = lambda x: _person(x)
+email = author_email
+email_or_none = lambda x: email(x) if email(x) != x else None
+person = lambda x: author_name(x)
short_id = lambda x: x[:12]
-
+hide_credentials = lambda x: ''.join(credentials_filter(x))
def bool2icon(value):
"""Returns True/False values represented as small html image of true/false
@@ -299,13 +313,14 @@ def bool2icon(value):
return value
-def action_parser(user_log):
- """
- This helper will map the specified string action into translated
+def action_parser(user_log, feed=False):
+ """This helper will action_map the specified string action into translated
fancy names with icons and links
-
- @param action:
+
+ :param user_log: user log instance
+ :param feed: use output for feeds (no html and fancy icons)
"""
+
action = user_log.action
action_params = ' '
@@ -315,56 +330,90 @@ def action_parser(user_log):
action, action_params = x
def get_cs_links():
- revs_limit = 5
+ revs_limit = 3 #display this amount always
+ revs_top_limit = 50 #show upto this amount of changesets hidden
revs = action_params.split(',')
- cs_links = " " + ', '.join ([link_to(rev, url('changeset_home',
- repo_name=user_log.repository.repo_name,
- revision=rev)) for rev in revs[:revs_limit]])
+ repo_name = user_log.repository.repo_name
+
+ from rhodecode.model.scm import ScmModel
+ repo = user_log.repository.scm_instance
+
+ message = lambda rev: get_changeset_safe(repo, rev).message
+ cs_links = []
+ cs_links.append(" " + ', '.join ([link_to(rev,
+ url('changeset_home',
+ repo_name=repo_name,
+ revision=rev), title=tooltip(message(rev)),
+ class_='tooltip') for rev in revs[:revs_limit] ]))
+
+ compare_view = (' Date: %s Message:" + " %s '
+ '%s '
+ ' ' % (_('Show all combined changesets %s->%s' \
+ % (revs[0], revs[-1])),
+ url('changeset_home', repo_name=repo_name,
+ revision='%s...%s' % (revs[0], revs[-1])
+ ),
+ _('compare view'))
+ )
+
if len(revs) > revs_limit:
uniq_id = revs[0]
html_tmpl = (' %s '
- '%s '
+ '%s '
'%s')
- cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
+ if not feed:
+ cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
% (len(revs) - revs_limit),
- _('revisions'))
+ _('revisions')))
- html_tmpl = ''
- cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
+ if not feed:
+ html_tmpl = ''
+ else:
+ html_tmpl = ' %s '
+
+ cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
url('changeset_home',
- repo_name=user_log.repository.repo_name,
- revision=rev)) for rev in revs[revs_limit:] ]))
-
- return cs_links
+ repo_name=repo_name, revision=rev),
+ title=message(rev), class_='tooltip')
+ for rev in revs[revs_limit:revs_top_limit]])))
+ if len(revs) > 1:
+ cs_links.append(compare_view)
+ return ''.join(cs_links)
def get_fork_name():
repo_name = action_params
- return str(link_to(action_params, url('summary_home',
+ return _('fork name ') + str(link_to(action_params, url('summary_home',
repo_name=repo_name,)))
- map = {'user_deleted_repo':(_('[deleted] repository'), None),
+ action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
'user_created_repo':(_('[created] repository'), None),
- 'user_forked_repo':(_('[forked] repository as'), get_fork_name),
+ 'user_forked_repo':(_('[forked] repository'), get_fork_name),
'user_updated_repo':(_('[updated] repository'), None),
'admin_deleted_repo':(_('[delete] repository'), None),
'admin_created_repo':(_('[created] repository'), None),
'admin_forked_repo':(_('[forked] repository'), None),
'admin_updated_repo':(_('[updated] repository'), None),
- 'push':(_('[pushed] '), get_cs_links),
- 'pull':(_('[pulled] '), None),
+ 'push':(_('[pushed] into'), get_cs_links),
+ 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
+ 'push_remote':(_('[pulled from remote] into'), get_cs_links),
+ 'pull':(_('[pulled] from'), None),
'started_following_repo':(_('[started following] repository'), None),
'stopped_following_repo':(_('[stopped following] repository'), None),
}
- action_str = map.get(action, action)
- action = action_str[0].replace('[', '')\
+ action_str = action_map.get(action, action)
+ if feed:
+ action = action_str[0].replace('[', '').replace(']', '')
+ else:
+ action = action_str[0].replace('[', '')\
.replace(']', '')
+
action_params_func = lambda :""
- if action_str[1] is not None:
+ if callable(action_str[1]):
action_params_func = action_str[1]
- return literal(action + " " + action_params_func())
+ return [literal(action), action_params_func]
def action_parser_icon(user_log):
action = user_log.action
@@ -384,6 +433,7 @@ def action_parser_icon(user_log):
'admin_forked_repo':'arrow_divide.png',
'admin_updated_repo':'database_edit.png',
'push':'script_add.png',
+ 'push_local':'script_edit.png',
'push_remote':'connect.png',
'pull':'down_16.png',
'started_following_repo':'heart_add.png',
@@ -402,9 +452,12 @@ HasRepoPermissionAny, HasRepoPermissionA
#==============================================================================
# GRAVATAR URL
#==============================================================================
-from pylons import request
def gravatar_url(email_address, size=30):
+ if not str2bool(config['app_conf'].get('use_gravatar')) or \
+ email_address == 'anonymous@rhodecode.org':
+ return url("/images/user%s.png" % size)
+
ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
default = 'identicon'
baseurl_nossl = "http://www.gravatar.com/avatar/"
@@ -413,19 +466,208 @@ def gravatar_url(email_address, size=30)
if isinstance(email_address, unicode):
#hashlib crashes on unicode items
- email_address = email_address.encode('utf8', 'replace')
+ email_address = safe_str(email_address)
# construct the url
gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
return gravatar_url
+
+#==============================================================================
+# REPO PAGER, PAGER FOR REPOSITORY
+#==============================================================================
+class RepoPage(Page):
+
+ def __init__(self, collection, page=1, items_per_page=20,
+ item_count=None, url=None, branch_name=None, **kwargs):
+
+ """Create a "RepoPage" instance. special pager for paging
+ repository
+ """
+ self._url_generator = url
+
+ # Safe the kwargs class-wide so they can be used in the pager() method
+ self.kwargs = kwargs
+
+ # Save a reference to the collection
+ self.original_collection = collection
+
+ self.collection = collection
+
+ # The self.page is the number of the current page.
+ # The first page has the number 1!
+ try:
+ self.page = int(page) # make it int() if we get it as a string
+ except (ValueError, TypeError):
+ self.page = 1
+
+ self.items_per_page = items_per_page
+
+ # Unless the user tells us how many items the collections has
+ # we calculate that ourselves.
+ if item_count is not None:
+ self.item_count = item_count
+ else:
+ self.item_count = len(self.collection)
+
+ # Compute the number of the first and last available page
+ if self.item_count > 0:
+ self.first_page = 1
+ self.page_count = int(math.ceil(float(self.item_count) /
+ self.items_per_page))
+ self.last_page = self.first_page + self.page_count - 1
+
+ # Make sure that the requested page number is the range of valid pages
+ if self.page > self.last_page:
+ self.page = self.last_page
+ elif self.page < self.first_page:
+ self.page = self.first_page
+
+ # Note: the number of items on this page can be less than
+ # items_per_page if the last page is not full
+ self.first_item = max(0, (self.item_count) - (self.page *
+ items_per_page))
+ self.last_item = ((self.item_count - 1) - items_per_page *
+ (self.page - 1))
+
+ iterator = self.collection.get_changesets(start=self.first_item,
+ end=self.last_item,
+ reverse=True,
+ branch_name=branch_name)
+ self.items = list(iterator)
+
+ # Links to previous and next page
+ if self.page > self.first_page:
+ self.previous_page = self.page - 1
+ else:
+ self.previous_page = None
+
+ if self.page < self.last_page:
+ self.next_page = self.page + 1
+ else:
+ self.next_page = None
+
+ # No items available
+ else:
+ self.first_page = None
+ self.page_count = 0
+ self.last_page = None
+ self.first_item = None
+ self.last_item = None
+ self.previous_page = None
+ self.next_page = None
+ self.items = []
+
+ # This is a subclass of the 'list' type. Initialise the list now.
+ list.__init__(self, self.items)
+
+
def changed_tooltip(nodes):
+ """
+ Generates a html string for changed nodes in changeset page.
+ It limits the output to 30 entries
+
+ :param nodes: LazyNodesGenerator
+ """
if nodes:
pref = ': ' suf = '' if len(nodes) > 30: suf = ' ' + _(' and %s more') % (len(nodes) - 30) - return literal(pref + ' '.join([safe_unicode(x.path) for x in nodes[:30]]) + suf) + return literal(pref + ' '.join([safe_unicode(x.path) + for x in nodes[:30]]) + suf) else: return ': ' + _('No Files') + + + +def repo_link(groups_and_repos): + """ + Makes a breadcrumbs link to repo within a group + joins » on each group to create a fancy link + + ex:: + group >> subgroup >> repo + + :param groups_and_repos: + """ + groups, repo_name = groups_and_repos + + if not groups: + return repo_name + else: + def make_link(group): + return link_to(group.group_name, url('repos_group', + id=group.group_id)) + return literal(' » '.join(map(make_link, groups)) + \ + " » " + repo_name) + + +def fancy_file_stats(stats): + """ + Displays a fancy two colored bar for number of added/deleted + lines of code on file + + :param stats: two element list of added/deleted lines of code + """ + + a, d, t = stats[0], stats[1], stats[0] + stats[1] + width = 100 + unit = float(width) / (t or 1) + + # needs > 9% of width to be visible or 0 to be hidden + a_p = max(9, unit * a) if a > 0 else 0 + d_p = max(9, unit * d) if d > 0 else 0 + p_sum = a_p + d_p + + if p_sum > width: + #adjust the percentage to be == 100% since we adjusted to 9 + if a_p > d_p: + a_p = a_p - (p_sum - width) + else: + d_p = d_p - (p_sum - width) + + a_v = a if a > 0 else '' + d_v = d if d > 0 else '' + + + def cgen(l_type): + mapping = {'tr':'top-right-rounded-corner', + 'tl':'top-left-rounded-corner', + 'br':'bottom-right-rounded-corner', + 'bl':'bottom-left-rounded-corner'} + map_getter = lambda x:mapping[x] + + if l_type == 'a' and d_v: + #case when added and deleted are present + return ' '.join(map(map_getter, ['tl', 'bl'])) + + if l_type == 'a' and not d_v: + return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl'])) + + if l_type == 'd' and a_v: + return ' '.join(map(map_getter, ['tr', 'br'])) + + if l_type == 'd' and not a_v: + return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl'])) + + + + d_a = ' %s ' % (cgen('a'),
+ a_p, a_v)
+ d_d = '%s ' % (cgen('d'),
+ d_p, d_v)
+ return literal('%s%s ' % (width, d_a, d_d))
+
+
+def urlify_text(text):
+ import re
+
+ url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
+
+ def url_func(match_obj):
+ url_full = match_obj.groups()[0]
+ return '%(url)s' % ({'url':url_full})
+
+ return literal(url_pat.sub(url_func, text))
|