35
35
log = logging.getLogger("loggerhead.controllers")
39
# date_day -- just the day
40
# date_time -- full date with time
42
# displaydate -- for use in sentences
43
# approximatedate -- for use in tables
45
# displaydate and approximatedate return an elementtree <span> Element
46
# with the full date in a tooltip.
49
return value.strftime('%Y-%m-%d')
53
return value.strftime('%Y-%m-%d %T')
56
def _displaydate(date):
57
delta = abs(datetime.datetime.now() - date)
58
if delta > datetime.timedelta(1, 0, 0):
59
# far in the past or future, display the date
60
return 'on ' + date_day(date)
61
return _approximatedate(date)
64
def _approximatedate(date):
65
delta = datetime.datetime.now() - date
66
if abs(delta) > datetime.timedelta(1, 0, 0):
67
# far in the past or future, display the date
69
future = delta < datetime.timedelta(0, 0, 0)
72
hours = delta.seconds / 3600
73
minutes = (delta.seconds - (3600*hours)) / 60
74
seconds = delta.seconds % 60
92
result += '%s %s' % (amount, unit)
98
def _wrap_with_date_time_title(date, formatted_date):
99
elem = ET.Element("span")
100
elem.text = formatted_date
101
elem.set("title", date_time(date))
105
def approximatedate(date):
106
return _wrap_with_date_time_title(date, _approximatedate(date))
109
def displaydate(date):
110
return _wrap_with_date_time_title(date, _displaydate(date))
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'
113
81
class Container (object):
120
88
setattr(self, key, value)
121
89
for key, value in kw.iteritems():
122
90
setattr(self, key, value)
124
92
def __repr__(self):
126
94
for key, value in self.__dict__.iteritems():
194
162
given a position in a maximum range, return a list of negative and positive
195
163
jump factors for an hgweb-style triple-factor geometric scan.
197
165
for example, with pos=20 and max=500, the range would be:
198
166
[ -10, -3, -1, 1, 3, 10, 30, 100, 300 ]
200
168
i admit this is a very strange way of jumping through revisions. i didn't
221
188
in displaying monospace text.
223
190
s = cgi.escape(s.expandtabs())
191
# s = _BADCHARS_RE.sub(lambda x: '&#%d;' % (ord(x.group(0)),), s)
224
192
s = s.replace(' ', ' ')
229
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
233
expand tabs and turn spaces into "non-breaking spaces", so browsers won't
236
if not isinstance(s, unicode):
237
# this kinda sucks. file contents are just binary data, and no
238
# encoding metadata is stored, so we need to guess. this is probably
239
# okay for most code, but for people using things like KOI-8, this
240
# will display gibberish. we have no way of detecting the correct
243
s = s.decode('utf-8')
244
except UnicodeDecodeError:
245
s = s.decode('iso-8859-15')
246
return s.expandtabs().replace(' ', NONBREAKING_SPACE)
249
196
def fake_permissions(kind, executable):
250
197
# fake up unix-style permissions given only a "kind" and executable bit
251
198
if kind == 'directory':
323
270
elif divisor == GIG:
328
def fill_in_navigation(navigation):
275
def fill_in_navigation(history, navigation):
330
277
given a navigation block (used by the template for the page header), fill
331
278
in useful calculated values.
333
if navigation.revid in navigation.revid_list: # XXX is this always true?
334
navigation.position = navigation.revid_list.index(navigation.revid)
280
navigation.position = history.get_revid_sequence(navigation.revid_list, navigation.revid)
281
if navigation.position is None:
336
282
navigation.position = 0
337
283
navigation.count = len(navigation.revid_list)
338
284
navigation.page_position = navigation.position // navigation.pagesize + 1
339
285
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
341
287
def get_offset(offset):
342
288
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
344
290
return navigation.revid_list[navigation.position + offset]
346
292
navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
347
293
navigation.next_page_revid = get_offset(1 * navigation.pagesize)
349
params = { 'filter_file_id': navigation.filter_file_id }
295
params = { 'file_id': navigation.file_id }
350
296
if getattr(navigation, 'query', None) is not None:
351
297
params['q'] = navigation.query
353
299
params['start_revid'] = navigation.start_revid
355
301
if navigation.prev_page_revid:
356
302
navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
357
303
if navigation.next_page_revid:
434
380
# current location along the navigation path (while browsing)
435
381
# - starting revid (start_revid)
436
382
# the current beginning of navigation (navigation continues back to
437
# the original revision) -- this defines an 'alternate mainline'
438
# when the user navigates into a branch.
383
# the original revision) -- this may not be along the primary revision
384
# path since the user may have navigated into a branch
440
# the file being looked at
442
386
# if navigating the revisions that touched a file
444
388
# if navigating the revisions that matched a search query
455
399
# for re-ordering an existing page by different sort
457
401
t_context = threading.local()
458
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
459
'compare_revid', 'sort')
402
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
462
405
def set_context(map):