17
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
from xml.etree import ElementTree as ET
23
from elementtree import ElementTree as ET
35
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'
73
37
def fix_year(year):
46
# date_day -- just the day
47
# date_time -- full date with time
49
# displaydate -- for use in sentences
50
# approximatedate -- for use in tables
52
# displaydate and approximatedate return an elementtree <span> Element
53
# with the full date in a tooltip.
56
return value.strftime('%Y-%m-%d')
60
return value.strftime('%Y-%m-%d %T')
63
def _displaydate(date):
64
delta = abs(datetime.datetime.now() - date)
65
if delta > datetime.timedelta(1, 0, 0):
66
# far in the past or future, display the date
67
return 'on ' + date_day(date)
68
return _approximatedate(date)
71
def _approximatedate(date):
72
delta = datetime.datetime.now() - date
73
if abs(delta) > datetime.timedelta(1, 0, 0):
74
# far in the past or future, display the date
76
future = delta < datetime.timedelta(0, 0, 0)
79
hours = delta.seconds / 3600
80
minutes = (delta.seconds - (3600*hours)) / 60
81
seconds = delta.seconds % 60
99
result += '%s %s' % (amount, unit)
105
def _wrap_with_date_time_title(date, formatted_date):
106
elem = ET.Element("span")
107
elem.text = formatted_date
108
elem.set("title", date_time(date))
112
def approximatedate(date):
113
#FIXME: Returns an object instead of a string
114
return _wrap_with_date_time_title(date, _approximatedate(date))
117
def displaydate(date):
118
return _wrap_with_date_time_title(date, _displaydate(date))
81
121
class Container (object):
88
128
setattr(self, key, value)
89
129
for key, value in kw.iteritems():
90
130
setattr(self, key, value)
92
132
def __repr__(self):
94
134
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 ])
112
142
def trunc(text, limit=10):
113
143
if len(text) <= limit:
115
145
return text[:limit] + '...'
119
if isinstance(s, unicode):
120
return s.encode('utf-8')
124
148
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
125
149
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
143
167
return '%s at %s' % (username, domains[-2])
144
168
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):
181
171
# only do this if unicode turns out to be a problem
182
172
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
223
213
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
236
217
s = base64.encodestring(s).replace('\n', '')
237
218
while (len(s) > 0) and (s[-1] == '='):
305
286
navigation.count = len(navigation.revid_list)
306
287
navigation.page_position = navigation.position // navigation.pagesize + 1
307
288
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
309
290
def get_offset(offset):
310
291
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
312
293
return navigation.revid_list[navigation.position + offset]
314
295
navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
315
296
navigation.next_page_revid = get_offset(1 * navigation.pagesize)
317
params = { 'file_id': navigation.file_id }
297
prev_page_revno = navigation.history.get_revno(
298
navigation.prev_page_revid)
299
next_page_revno = navigation.history.get_revno(
300
navigation.next_page_revid)
301
start_revno = navigation.history.get_revno(navigation.start_revid)
303
params = { 'filter_file_id': navigation.filter_file_id }
318
304
if getattr(navigation, 'query', None) is not None:
319
305
params['q'] = navigation.query
321
params['start_revid'] = navigation.start_revid
307
if getattr(navigation, 'start_revid', None) is not None:
308
params['start_revid'] = start_revno
323
310
if navigation.prev_page_revid:
324
navigation.prev_page_url = navigation.branch.url([ navigation.scan_url, navigation.prev_page_revid ], **get_context(**params))
311
navigation.prev_page_url = navigation.branch.context_url(
312
[navigation.scan_url, prev_page_revno], **params)
325
313
if navigation.next_page_revid:
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'):
314
navigation.next_page_url = navigation.branch.context_url(
315
[navigation.scan_url, next_page_revno], **params)
334
318
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)))
381
349
def _f(*a, **kw):
382
350
from loggerhead.lsprof import profile
402
370
# current location along the navigation path (while browsing)
403
371
# - starting revid (start_revid)
404
372
# the current beginning of navigation (navigation continues back to
405
# the original revision) -- this may not be along the primary revision
406
# path since the user may have navigated into a branch
373
# the original revision) -- this defines an 'alternate mainline'
374
# when the user navigates into a branch.
376
# the file being looked at
408
378
# if navigating the revisions that touched a file
410
380
# if navigating the revisions that matched a search query
421
391
# for re-ordering an existing page by different sort
423
393
t_context = threading.local()
424
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
394
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
395
'compare_revid', 'sort')
427
398
def set_context(map):
431
402
def get_context(**overrides):
404
Soon to be deprecated.
433
407
return a context map that may be overriden by specific values passed in,
434
408
but only contains keys from the list of valid context keys.
436
410
if 'clear' is set, only the 'remember' context value will be added, and
437
411
all other context will be omitted.