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')
62
return value.strftime('%Y-%m-%d %T')
65
def _displaydate(date):
66
delta = abs(datetime.datetime.now() - date)
67
if delta > datetime.timedelta(1, 0, 0):
68
# far in the past or future, display the date
69
return 'on ' + date_day(date)
70
return _approximatedate(date)
73
def _approximatedate(date):
74
delta = datetime.datetime.now() - date
75
if abs(delta) > datetime.timedelta(1, 0, 0):
76
# far in the past or future, display the date
78
future = delta < datetime.timedelta(0, 0, 0)
81
hours = delta.seconds / 3600
82
minutes = (delta.seconds - (3600*hours)) / 60
83
seconds = delta.seconds % 60
101
result += '%s %s' % (amount, unit)
107
def _wrap_with_date_time_title(date, formatted_date):
108
elem = ET.Element("span")
109
elem.text = formatted_date
110
elem.set("title", date_time(date))
114
def approximatedate(date):
115
#FIXME: Returns an object instead of a string
116
return _wrap_with_date_time_title(date, _approximatedate(date))
119
def displaydate(date):
120
return _wrap_with_date_time_title(date, _displaydate(date))
123
81
class Container (object):
130
88
setattr(self, key, value)
131
89
for key, value in kw.iteritems():
132
90
setattr(self, key, value)
134
92
def __repr__(self):
136
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 ])
144
112
def trunc(text, limit=10):
145
113
if len(text) <= limit:
147
115
return text[:limit] + '...'
119
if isinstance(s, unicode):
120
return s.encode('utf-8')
150
124
STANDARD_PATTERN = re.compile(r'^(.*?)\s*<(.*?)>\s*$')
151
125
EMAIL_PATTERN = re.compile(r'[-\w\d\+_!%\.]+@[-\w\d\+_!%\.]+')
169
143
return '%s at %s' % (username, domains[-2])
170
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):
173
181
# only do this if unicode turns out to be a problem
174
182
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
184
192
s = s.replace(' ', ' ')
187
197
NONBREAKING_SPACE = u'\N{NO-BREAK SPACE}'
191
CSS is stupid. In some cases we need to replace an empty value with
192
a non breaking space ( ). There has to be a better way of doing this.
194
return: the same value recieved if not empty, and a ' ' if it is.
200
elif isinstance(s, int):
206
s = s.decode('utf-8')
207
except UnicodeDecodeError:
208
s = s.decode('iso-8859-15')
212
199
def fixed_width(s):
214
201
expand tabs and turn spaces into "non-breaking spaces", so browsers won't
236
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
240
236
s = base64.encodestring(s).replace('\n', '')
241
237
while (len(s) > 0) and (s[-1] == '='):
309
305
navigation.count = len(navigation.revid_list)
310
306
navigation.page_position = navigation.position // navigation.pagesize + 1
311
307
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
313
309
def get_offset(offset):
314
310
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
316
312
return navigation.revid_list[navigation.position + offset]
318
navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
319
314
navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
320
315
navigation.next_page_revid = get_offset(1 * navigation.pagesize)
321
prev_page_revno = navigation.history.get_revno(
322
navigation.prev_page_revid)
323
next_page_revno = navigation.history.get_revno(
324
navigation.next_page_revid)
325
start_revno = navigation.history.get_revno(navigation.start_revid)
327
params = { 'filter_file_id': navigation.filter_file_id }
317
params = { 'file_id': navigation.file_id }
328
318
if getattr(navigation, 'query', None) is not None:
329
319
params['q'] = navigation.query
331
if getattr(navigation, 'start_revid', None) is not None:
332
params['start_revid'] = start_revno
321
params['start_revid'] = navigation.start_revid
334
323
if navigation.prev_page_revid:
335
navigation.prev_page_url = navigation.branch.context_url(
336
[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))
337
325
if navigation.next_page_revid:
338
navigation.next_page_url = navigation.branch.context_url(
339
[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'):
342
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)))
373
381
def _f(*a, **kw):
374
382
from loggerhead.lsprof import profile
394
402
# current location along the navigation path (while browsing)
395
403
# - starting revid (start_revid)
396
404
# the current beginning of navigation (navigation continues back to
397
# the original revision) -- this defines an 'alternate mainline'
398
# 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
400
# the file being looked at
402
408
# if navigating the revisions that touched a file
404
410
# if navigating the revisions that matched a search query
415
421
# for re-ordering an existing page by different sort
417
423
t_context = threading.local()
418
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
419
'compare_revid', 'sort')
424
_valid = ('start_revid', 'file_id', 'q', 'remember', 'compare_revid', 'sort')
422
427
def set_context(map):
426
431
def get_context(**overrides):
428
Soon to be deprecated.
431
433
return a context map that may be overriden by specific values passed in,
432
434
but only contains keys from the list of valid context keys.
434
436
if 'clear' is set, only the 'remember' context value will be added, and
435
437
all other context will be omitted.