19
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
from xml.etree import ElementTree as ET
25
from elementtree import ElementTree as ET
37
35
log = logging.getLogger("loggerhead.controllers")
41
return '%d years' % (int(delta.days // 365.25),)
43
return '%d days' % delta.days
49
seg.append('%d days' % delta.days)
50
hrs = delta.seconds // 3600
51
mins = (delta.seconds % 3600) // 60
56
seg.append('%d hours' % hrs)
60
seg.append('1 minute')
62
seg.append('%d minutes' % mins)
64
seg.append('less than a minute')
69
now = datetime.datetime.now()
70
return timespan(now - timestamp) + ' ago'
39
73
def fix_year(year):
48
# date_day -- just the day
49
# date_time -- full date with time
51
# displaydate -- for use in sentences
52
# approximatedate -- for use in tables
54
# displaydate and approximatedate return an elementtree <span> Element
55
# with the full date in a tooltip.
58
return value.strftime('%Y-%m-%d')
63
return value.strftime('%Y-%m-%d %T')
68
def _displaydate(date):
69
delta = abs(datetime.datetime.now() - date)
70
if delta > datetime.timedelta(1, 0, 0):
71
# far in the past or future, display the date
72
return 'on ' + date_day(date)
73
return _approximatedate(date)
76
def _approximatedate(date):
77
delta = datetime.datetime.now() - date
78
if abs(delta) > datetime.timedelta(1, 0, 0):
79
# far in the past or future, display the date
81
future = delta < datetime.timedelta(0, 0, 0)
84
hours = delta.seconds / 3600
85
minutes = (delta.seconds - (3600*hours)) / 60
86
seconds = delta.seconds % 60
104
result += '%s %s' % (amount, unit)
110
def _wrap_with_date_time_title(date, formatted_date):
111
elem = ET.Element("span")
112
elem.text = formatted_date
113
elem.set("title", date_time(date))
117
def approximatedate(date):
118
#FIXME: Returns an object instead of a string
119
return _wrap_with_date_time_title(date, _approximatedate(date))
122
def displaydate(date):
123
return _wrap_with_date_time_title(date, _displaydate(date))
126
81
class Container (object):
133
88
setattr(self, key, value)
134
89
for key, value in kw.iteritems():
135
90
setattr(self, key, value)
137
92
def __repr__(self):
139
94
for key, value in self.__dict__.iteritems():
102
def clean_revid(revid):
103
if revid == 'missing':
105
return sha.new(revid).hexdigest()
109
return ''.join([ '&#%d;' % ord(c) for c in text ])
147
112
def trunc(text, limit=10):
148
113
if len(text) <= limit:
150
115
return text[:limit] + '...'
119
if isinstance(s, unicode):
120
return s.encode('utf-8')
153
124
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
154
125
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
172
143
return '%s at %s' % (username, domains[-2])
173
144
return '%s at %s' % (username, domains[0])
147
def triple_factors(min_value=1):
153
yield n * factors[index]
155
if index >= len(factors):
160
def scan_range(pos, max, pagesize=1):
162
given a position in a maximum range, return a list of negative and positive
163
jump factors for an hgweb-style triple-factor geometric scan.
165
for example, with pos=20 and max=500, the range would be:
166
[ -10, -3, -1, 1, 3, 10, 30, 100, 300 ]
168
i admit this is a very strange way of jumping through revisions. i didn't
172
for n in triple_factors(pagesize + 1):
176
181
# only do this if unicode turns out to be a problem
177
182
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
187
192
s = s.replace(' ', ' ')
190
197
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
194
CSS is stupid. In some cases we need to replace an empty value with
195
a non breaking space ( ). There has to be a better way of doing this.
197
return: the same value recieved if not empty, and a ' ' if it is.
203
elif isinstance(s, int):
209
s = s.decode('utf-8')
210
except UnicodeDecodeError:
211
s = s.decode('iso-8859-15')
215
199
def fixed_width(s):
217
201
expand tabs and turn spaces into "non-breaking spaces", so browsers won't
239
223
return '-rw-r--r--'
226
def if_present(format, value):
228
format a value using a format string, if the value exists and is not None.
232
return format % value
243
236
s = base64.encodestring(s).replace('\n', '')
244
237
while (len(s) > 0) and (s[-1] == '='):
312
305
navigation.count = len(navigation.revid_list)
313
306
navigation.page_position = navigation.position // navigation.pagesize + 1
314
307
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
316
309
def get_offset(offset):
317
310
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
319
312
return navigation.revid_list[navigation.position + offset]
321
navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
322
314
navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
323
315
navigation.next_page_revid = get_offset(1 * navigation.pagesize)
324
prev_page_revno = navigation.history.get_revno(
325
navigation.prev_page_revid)
326
next_page_revno = navigation.history.get_revno(
327
navigation.next_page_revid)
328
start_revno = navigation.history.get_revno(navigation.start_revid)
330
params = { 'filter_file_id': navigation.filter_file_id }
317
params = { 'file_id': navigation.file_id }
331
318
if getattr(navigation, 'query', None) is not None:
332
319
params['q'] = navigation.query
334
if getattr(navigation, 'start_revid', None) is not None:
335
params['start_revid'] = start_revno
321
params['start_revid'] = navigation.start_revid
337
323
if navigation.prev_page_revid:
338
navigation.prev_page_url = navigation.branch.context_url(
339
[navigation.scan_url, prev_page_revno], **params)
324
navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
340
325
if navigation.next_page_revid:
341
navigation.next_page_url = navigation.branch.context_url(
342
[navigation.scan_url, next_page_revno], **params)
326
navigation.next_page_url = navigation.branch.url([ navigation.scan_url, navigation.next_page_revid ], **get_context(**params))
329
def log_exception(log):
330
for line in ''.join(traceback.format_exception(*sys.exc_info())).split('\n'):
345
334
def decorator(unbound):
364
def strip_whitespace(f):
368
out = re.sub(r'\n\s+', '\n', out)
369
out = re.sub(r'[ \t]+', ' ', out)
370
out = re.sub(r'\s+\n', '\n', out)
372
log.debug('Saved %sB (%d%%) by stripping whitespace.',
373
human_size(orig_len - new_len),
374
round(100.0 - float(new_len) * 100.0 / float(orig_len)))
376
381
def _f(*a, **kw):
377
382
from loggerhead.lsprof import profile
397
402
# current location along the navigation path (while browsing)
398
403
# - starting revid (start_revid)
399
404
# the current beginning of navigation (navigation continues back to
400
# the original revision) -- this defines an 'alternate mainline'
401
# when the user navigates into a branch.
405
# the original revision) -- this may not be along the primary revision
406
# path since the user may have navigated into a branch
403
# the file being looked at
405
408
# if navigating the revisions that touched a file
407
410
# if navigating the revisions that matched a search query
418
421
# for re-ordering an existing page by different sort
420
423
t_context = threading.local()
421
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
422
'compare_revid', 'sort')
424
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
425
427
def set_context(map):
429
431
def get_context(**overrides):
431
Soon to be deprecated.
434
433
return a context map that may be overriden by specific values passed in,
435
434
but only contains keys from the list of valid context keys.
437
436
if 'clear' is set, only the 'remember' context value will be added, and
438
437
all other context will be omitted.