helpers.py
374 lines
| 13.8 KiB
| text/x-python
|
PythonLexer
|
r0 | """Helper functions | ||
Consists of functions to typically be used within templates, but also | ||||
available to Controllers. This module is available to both as 'h'. | ||||
""" | ||||
r250 | from pygments.formatters import HtmlFormatter | |||
from pygments import highlight as code_highlight | ||||
r165 | from pylons import url, app_globals as g | |||
r97 | from pylons.i18n.translation import _, ungettext | |||
r250 | from vcs.utils.annotate import annotate_highlight | |||
from webhelpers.html import literal, HTML, escape | ||||
r281 | from webhelpers.html.tools import * | |||
r98 | from webhelpers.html.builder import make_tag | |||
r250 | from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \ | |||
end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \ | ||||
link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \ | ||||
r281 | password, textarea, title, ul, xml_declaration, radio | |||
r250 | from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \ | |||
mail_to, strip_links, strip_tags, tag_re | ||||
from webhelpers.number import format_byte_size, format_bit_size | ||||
|
r0 | from webhelpers.pylonslib import Flash as _Flash | ||
from webhelpers.pylonslib.secure_form import secure_form | ||||
r250 | from webhelpers.text import chop_at, collapse, convert_accented_entities, \ | |||
convert_misc_entities, lchop, plural, rchop, remove_formatting, \ | ||||
r281 | replace_whitespace, urlify, truncate, wrap_paragraphs | |||
|
r0 | |||
r281 | #Custom helpers here :) | |||
|
r0 | class _Link(object): | ||
''' | ||||
Make a url based on label and url with help of url_for | ||||
@param label:name of link if not defined url is used | ||||
@param url: the url for link | ||||
''' | ||||
r43 | def __call__(self, label='', *url_, **urlargs): | |||
|
r0 | if label is None or '': | ||
label = url | ||||
r43 | link_fn = link_to(label, url(*url_, **urlargs)) | |||
|
r0 | return link_fn | ||
r281 | link = _Link() | |||
|
r0 | |||
class _GetError(object): | ||||
def __call__(self, field_name, form_errors): | ||||
tmpl = """<span class="error_msg">%s</span>""" | ||||
if form_errors and form_errors.has_key(field_name): | ||||
return literal(tmpl % form_errors.get(field_name)) | ||||
r281 | get_error = _GetError() | |||
def recursive_replace(str, replace=' '): | ||||
""" | ||||
Recursive replace of given sign to just one instance | ||||
@param str: given string | ||||
@param replace:char to find and replace multiple instances | ||||
Examples:: | ||||
>>> recursive_replace("Mighty---Mighty-Bo--sstones",'-') | ||||
'Mighty-Mighty-Bo-sstones' | ||||
""" | ||||
if str.find(replace * 2) == -1: | ||||
return str | ||||
else: | ||||
str = str.replace(replace * 2, replace) | ||||
return recursive_replace(str, replace) | ||||
class _ToolTip(object): | ||||
def __call__(self, tooltip_title, trim_at=50): | ||||
""" | ||||
Special function just to wrap our text into nice formatted autowrapped | ||||
text | ||||
@param tooltip_title: | ||||
""" | ||||
r444 | return wrap_paragraphs(escape(tooltip_title), trim_at)\ | |||
.replace('\n', '<br/>') | ||||
r281 | ||||
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 not have and id autgenerate 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(), | ||||
monitorresize:false, | ||||
xyoffset :[0,0], | ||||
autodismissdelay:300000, | ||||
hidedelay:5, | ||||
showdelay:20, | ||||
}); | ||||
r284 | //Mouse Over event disabled for new repositories since they dont | |||
//have last commit message | ||||
r281 | myToolTips.contextMouseOverEvent.subscribe( | |||
function(type, args) { | ||||
var context = args[0]; | ||||
r284 | var txt = context.getAttribute('tooltip_title'); | |||
r292 | if(txt){ | |||
r284 | return true; | |||
} | ||||
else{ | ||||
return false; | ||||
} | ||||
r281 | }); | |||
r286 | ||||
r281 | // Set the text for the tooltip just before we display it. Lazy method | |||
myToolTips.contextTriggerEvent.subscribe( | ||||
function(type, args) { | ||||
r292 | ||||
r281 | var context = args[0]; | |||
r292 | ||||
r281 | var txt = context.getAttribute('tooltip_title'); | |||
r286 | this.cfg.setProperty("text", txt); | |||
r292 | ||||
// positioning of tooltip | ||||
var tt_w = this.element.clientWidth; | ||||
var tt_h = this.element.clientHeight; | ||||
var context_w = context.offsetWidth; | ||||
var context_h = context.offsetHeight; | ||||
var pos_x = YAHOO.util.Dom.getX(context); | ||||
var pos_y = YAHOO.util.Dom.getY(context); | ||||
r286 | ||||
r292 | var display_strategy = 'top'; | |||
r293 | var xy_pos = [0,0]; | |||
r292 | switch (display_strategy){ | |||
case 'top': | ||||
var cur_x = (pos_x+context_w/2)-(tt_w/2); | ||||
var cur_y = pos_y-tt_h-4; | ||||
xy_pos = [cur_x,cur_y]; | ||||
break; | ||||
case 'bottom': | ||||
var cur_x = (pos_x+context_w/2)-(tt_w/2); | ||||
var cur_y = pos_y+context_h+4; | ||||
xy_pos = [cur_x,cur_y]; | ||||
break; | ||||
case 'left': | ||||
var cur_x = (pos_x-tt_w-4); | ||||
var cur_y = pos_y-((tt_h/2)-context_h/2); | ||||
xy_pos = [cur_x,cur_y]; | ||||
break; | ||||
case 'right': | ||||
var cur_x = (pos_x+context_w+4); | ||||
var cur_y = pos_y-((tt_h/2)-context_h/2); | ||||
xy_pos = [cur_x,cur_y]; | ||||
break; | ||||
r293 | default: | |||
var cur_x = (pos_x+context_w/2)-(tt_w/2); | ||||
var cur_y = pos_y-tt_h-4; | ||||
xy_pos = [cur_x,cur_y]; | ||||
break; | ||||
r292 | } | |||
this.cfg.setProperty("xy",xy_pos); | ||||
r286 | ||||
r281 | }); | |||
r292 | ||||
r286 | //Mouse out | |||
myToolTips.contextMouseOutEvent.subscribe( | ||||
function(type, args) { | ||||
var context = args[0]; | ||||
}); | ||||
r281 | }); | |||
''' | ||||
return literal(js) | ||||
tooltip = _ToolTip() | ||||
r102 | class _FilesBreadCrumbs(object): | |||
def __call__(self, repo_name, rev, paths): | ||||
r287 | url_l = [link_to(repo_name, url('files_home', | |||
repo_name=repo_name, | ||||
revision=rev, f_path=''))] | ||||
paths_l = paths.split('/') | ||||
r102 | ||||
for cnt, p in enumerate(paths_l, 1): | ||||
if p != '': | ||||
r287 | url_l.append(link_to(p, url('files_home', | |||
repo_name=repo_name, | ||||
revision=rev, | ||||
f_path='/'.join(paths_l[:cnt])))) | ||||
r97 | ||||
r448 | return literal('/'.join(url_l)) | |||
r98 | ||||
r281 | files_breadcrumbs = _FilesBreadCrumbs() | |||
r250 | def pygmentize(filenode, **kwargs): | |||
r165 | """ | |||
r250 | pygmentize function using pygments | |||
@param filenode: | ||||
r165 | """ | |||
r450 | return literal(code_highlight(filenode.content, | |||
|
r428 | filenode.lexer, HtmlFormatter(**kwargs))) | ||
r165 | ||||
r250 | def pygmentize_annotation(filenode, **kwargs): | |||
r165 | """ | |||
r250 | pygmentize function for annotation | |||
@param filenode: | ||||
r165 | """ | |||
r250 | ||||
r438 | color_dict = {} | |||
r165 | def gen_color(): | |||
r438 | """generator for getting 10k of evenly distibuted colors using hsv color | |||
and golden ratio. | ||||
""" | ||||
import colorsys | ||||
n = 10000 | ||||
golden_ratio = 0.618033988749895 | ||||
h = 0.22717784590367374 | ||||
#generate 10k nice web friendly colors in the same order | ||||
for c in xrange(n): | ||||
r450 | h += golden_ratio | |||
r438 | h %= 1 | |||
HSV_tuple = [h, 0.95, 0.95] | ||||
RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple) | ||||
r450 | yield map(lambda x:str(int(x * 256)), RGB_tuple) | |||
r438 | ||||
cgenerator = gen_color() | ||||
r165 | def get_color_string(cs): | |||
if color_dict.has_key(cs): | ||||
col = color_dict[cs] | ||||
else: | ||||
r438 | col = color_dict[cs] = cgenerator.next() | |||
return "color: rgb(%s)! important;" % (', '.join(col)) | ||||
r250 | ||||
def url_func(changeset): | ||||
r450 | tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \ | |||
|
r428 | " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>" | ||
r281 | ||||
tooltip_html = tooltip_html % (changeset.author, | ||||
changeset.date, | ||||
tooltip(changeset.message)) | ||||
r438 | lnk_format = 'r%-5s:%s' % (changeset.revision, | |||
r281 | changeset.raw_id) | |||
uri = link_to( | ||||
lnk_format, | ||||
|
r428 | url('changeset_home', repo_name=changeset.repository.name, | ||
r281 | revision=changeset.raw_id), | |||
style=get_color_string(changeset.raw_id), | ||||
class_='tooltip', | ||||
tooltip_title=tooltip_html | ||||
) | ||||
uri += '\n' | ||||
return uri | ||||
r250 | return literal(annotate_highlight(filenode, url_func, **kwargs)) | |||
r260 | ||||
def repo_name_slug(value): | ||||
r481 | """Return slug of name of repository | |||
This function is called on each creation/modification | ||||
of repository to prevent bad names in repo | ||||
r260 | """ | |||
r481 | slug = remove_formatting(value) | |||
slug = strip_tags(slug) | ||||
for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """: | ||||
r260 | slug = slug.replace(c, '-') | |||
slug = recursive_replace(slug, '-') | ||||
r481 | slug = collapse(slug, '-') | |||
r260 | return slug | |||
r281 | ||||
r450 | 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)) | ||||
try: | ||||
cs = repo.get_changeset(rev) | ||||
except RepositoryError: | ||||
from pylons_app.lib.utils import EmptyChangeset | ||||
cs = EmptyChangeset() | ||||
return cs | ||||
|
r0 | flash = _Flash() | ||
r282 | ||||
#=============================================================================== | ||||
# MERCURIAL FILTERS available via h. | ||||
#=============================================================================== | ||||
from mercurial import util | ||||
from mercurial.templatefilters import age as _age, person as _person | ||||
age = lambda x:_age(x) | ||||
capitalize = lambda x: x.capitalize() | ||||
date = lambda x: util.datestr(x) | ||||
email = util.email | ||||
r401 | email_or_none = lambda x: util.email(x) if util.email(x) != x else None | |||
r282 | person = lambda x: _person(x) | |||
hgdate = lambda x: "%d %d" % x | ||||
isodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2') | ||||
isodatesec = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2') | ||||
localdate = lambda x: (x[0], util.makedate()[1]) | ||||
rfc822date = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2") | ||||
r494 | rfc822date_notz = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S") | |||
r282 | rfc3339date = lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2") | |||
time_ago = lambda x: util.datestr(_age(x), "%a, %d %b %Y %H:%M:%S %1%2") | ||||
r307 | #=============================================================================== | |||
# PERMS | ||||
#=============================================================================== | ||||
from pylons_app.lib.auth import HasPermissionAny, HasPermissionAll, \ | ||||
HasRepoPermissionAny, HasRepoPermissionAll | ||||
r282 | ||||
r398 | #=============================================================================== | |||
# GRAVATAR URL | ||||
#=============================================================================== | ||||
import hashlib | ||||
import urllib | ||||
r400 | from pylons import request | |||
r282 | ||||
r401 | def gravatar_url(email_address, size=30): | |||
r398 | ssl_enabled = 'https' == request.environ.get('HTTP_X_URL_SCHEME') | |||
default = 'identicon' | ||||
baseurl_nossl = "http://www.gravatar.com/avatar/" | ||||
baseurl_ssl = "https://secure.gravatar.com/avatar/" | ||||
baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl | ||||
# construct the url | ||||
r401 | gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?" | |||
r398 | gravatar_url += urllib.urlencode({'d':default, 's':str(size)}) | |||
return gravatar_url | ||||
r443 | ||||
def safe_unicode(str): | ||||
"""safe unicode function. In case of UnicodeDecode error we try to return | ||||
unicode with errors replace, if this failes we return unicode with | ||||
string_escape decoding """ | ||||
try: | ||||
u_str = unicode(str) | ||||
except UnicodeDecodeError: | ||||
try: | ||||
u_str = unicode(str, 'utf-8', 'replace') | ||||
except UnicodeDecodeError: | ||||
#incase we have a decode error just represent as byte string | ||||
u_str = unicode(str(str).encode('string_escape')) | ||||
r450 | return u_str | |||