19
16
# 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
29
log = logging.getLogger("loggerhead.controllers")
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))
35
return '%d years' % (int(delta.days // 365.25),)
37
return '%d days' % delta.days
43
seg.append('%d days' % delta.days)
44
hrs = delta.seconds // 3600
45
mins = (delta.seconds % 3600) // 60
50
seg.append('%d hours' % hrs)
54
seg.append('1 minute')
56
seg.append('%d minutes' % mins)
58
seg.append('less than a minute')
63
now = datetime.datetime.now()
64
return timespan(now - timestamp) + ' ago'
123
67
class Container (object):
169
117
return '%s at %s' % (username, domains[-2])
170
118
return '%s at %s' % (username, domains[0])
173
# only do this if unicode turns out to be a problem
174
#_BADCHARS_RE = re.compile(ur'[\u007f-\uffff]')
176
# FIXME: get rid of this method; use fixed_width() and avoid XML().
121
def triple_factors():
127
yield n * factors[index]
129
if index >= len(factors):
134
def scan_range(pos, max):
136
given a position in a maximum range, return a list of negative and positive
137
jump factors for an hgweb-style triple-factor geometric scan.
139
for example, with pos=20 and max=500, the range would be:
140
[ -10, -3, -1, 1, 3, 10, 30, 100, 300 ]
142
i admit this is a very strange way of jumping through revisions. i didn't
146
for n in triple_factors():
177
155
def html_clean(s):
179
157
clean up a string for html display. expand any tabs, encode any html
180
158
entities, and replace spaces with ' '. this is primarily for use
181
159
in displaying monospace text.
183
s = cgi.escape(s.expandtabs())
184
s = s.replace(' ', ' ')
161
s = cgi.escape(s.expandtabs()).replace(' ', ' ')
187
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')
214
expand tabs and turn spaces into "non-breaking spaces", so browsers won't
217
if not isinstance(s, unicode):
218
# this kinda sucks. file contents are just binary data, and no
219
# encoding metadata is stored, so we need to guess. this is probably
220
# okay for most code, but for people using things like KOI-8, this
221
# will display gibberish. we have no way of detecting the correct
224
s = s.decode('utf-8')
225
except UnicodeDecodeError:
226
s = s.decode('iso-8859-15')
227
return s.expandtabs().replace(' ', NONBREAKING_SPACE)
230
165
def fake_permissions(kind, executable):
231
166
# fake up unix-style permissions given only a "kind" and executable bit
236
171
return '-rw-r--r--'
240
s = base64.encodestring(s).replace('\n', '')
241
while (len(s) > 0) and (s[-1] == '='):
248
turn a potentially long string into a unique smaller string.
252
uniqs[type(None)] = next = uniqs.get(type(None), 0) + 1
253
x = struct.pack('>I', next)
254
while (len(x) > 1) and (x[0] == '\x00'):
263
P95_MEG = int(0.9 * MEG)
264
P95_GIG = int(0.9 * GIG)
266
def human_size(size, min_divisor=0):
268
if (size == 0) and (min_divisor == 0):
270
if (size < 512) and (min_divisor == 0):
273
if (size >= P95_GIG) or (min_divisor >= GIG):
275
elif (size >= P95_MEG) or (min_divisor >= MEG):
282
dot = dot * 10 // divisor
289
if (base < 100) and (dot != 0):
290
out += '.%d' % (dot,)
300
def fill_in_navigation(navigation):
302
given a navigation block (used by the template for the page header), fill
303
in useful calculated values.
305
if navigation.revid in navigation.revid_list: # XXX is this always true?
306
navigation.position = navigation.revid_list.index(navigation.revid)
308
navigation.position = 0
309
navigation.count = len(navigation.revid_list)
310
navigation.page_position = navigation.position // navigation.pagesize + 1
311
navigation.page_count = (len(navigation.revid_list) + (navigation.pagesize - 1)) // navigation.pagesize
313
def get_offset(offset):
314
if (navigation.position + offset < 0) or (navigation.position + offset > navigation.count - 1):
316
return navigation.revid_list[navigation.position + offset]
318
navigation.last_in_page_revid = get_offset(navigation.pagesize - 1)
319
navigation.prev_page_revid = get_offset(-1 * navigation.pagesize)
320
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 }
328
if getattr(navigation, 'query', None) is not None:
329
params['q'] = navigation.query
331
if getattr(navigation, 'start_revid', None) is not None:
332
params['start_revid'] = start_revno
334
if navigation.prev_page_revid:
335
navigation.prev_page_url = navigation.branch.context_url(
336
[navigation.scan_url, prev_page_revno], **params)
337
if navigation.next_page_revid:
338
navigation.next_page_url = navigation.branch.context_url(
339
[navigation.scan_url, next_page_revno], **params)
342
def decorator(unbound):
343
def new_decorator(f):
345
g.__name__ = f.__name__
346
g.__doc__ = f.__doc__
347
g.__dict__.update(f.__dict__)
349
new_decorator.__name__ = unbound.__name__
350
new_decorator.__doc__ = unbound.__doc__
351
new_decorator.__dict__.update(unbound.__dict__)
355
# common threading-lock decorator
356
def with_lock(lockname, debug_name=None):
357
if debug_name is None:
358
debug_name = lockname
360
def _decorator(unbound):
361
def locked(self, *args, **kw):
362
getattr(self, lockname).acquire()
364
return unbound(self, *args, **kw)
366
getattr(self, lockname).release()
374
from loggerhead.lsprof import profile
377
ret, stats = profile(f, *a, **kw)
378
log.debug('Finished profiled %s in %d msec.' % (f.__name__, int((time.time() - z) * 1000)))
382
msec = int(now * 1000) % 1000
383
timestr = time.strftime('%Y%m%d%H%M%S', time.localtime(now)) + ('%03d' % msec)
384
filename = f.__name__ + '-' + timestr + '.lsprof'
385
cPickle.dump(stats, open(filename, 'w'), 2)
390
# just thinking out loud here...
392
# so, when browsing around, there are 5 pieces of context, most optional:
394
# current location along the navigation path (while browsing)
395
# - starting revid (start_revid)
396
# 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.
400
# the file being looked at
402
# if navigating the revisions that touched a file
404
# if navigating the revisions that matched a search query
406
# a previous revision to remember for future comparisons
408
# current revid is given on the url path. the rest are optional components
411
# other transient things can be set:
413
# to compare one revision to another, on /revision only
415
# for re-ordering an existing page by different sort
417
t_context = threading.local()
418
_valid = ('start_revid', 'file_id', 'filter_file_id', 'q', 'remember',
419
'compare_revid', 'sort')
422
def set_context(map):
423
t_context.map = dict((k, v) for (k, v) in map.iteritems() if k in _valid)
426
def get_context(**overrides):
428
Soon to be deprecated.
431
return a context map that may be overriden by specific values passed in,
432
but only contains keys from the list of valid context keys.
434
if 'clear' is set, only the 'remember' context value will be added, and
435
all other context will be omitted.
438
if overrides.get('clear', False):
439
map['remember'] = t_context.map.get('remember', None)
441
map.update(t_context.map)
442
overrides = dict((k, v) for (k, v) in overrides.iteritems() if k in _valid)
443
map.update(overrides)
174
def if_present(format, value):
176
format a value using a format string, if the value exists and is not None.
180
return format % value
183
# global branch history & cache
186
_history_lock = threading.Lock()
190
from loggerhead.history import History
192
_history_lock.acquire()
194
if (_history is None) or _history.out_of_date():
195
log.debug('Reload branch history...')
196
if _history is not None:
197
_history.dont_use_cache()
198
_history = History.from_folder(turbogears.config.get('loggerhead.folder'))
199
_history.use_cache(turbogears.config.get('loggerhead.cachepath'))
202
_history_lock.release()