Package trac :: Package util :: Module presentation

Source Code for Module trac.util.presentation

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (C)2006-2009 Edgewall Software 
  4  # Copyright (C) 2006 Christopher Lenz <[email protected]> 
  5  # All rights reserved. 
  6  # 
  7  # This software is licensed as described in the file COPYING, which 
  8  # you should have received as part of this distribution. The terms 
  9  # are also available at http://trac.edgewall.org/wiki/TracLicense. 
 10  # 
 11  # This software consists of voluntary contributions made by many 
 12  # individuals. For the exact contribution history, see the revision 
 13  # history and logs, available at http://trac.edgewall.org/log/. 
 14   
 15  """Various utility functions and classes that support common presentation 
 16  tasks such as grouping or pagination. 
 17  """ 
 18   
 19  from math import ceil 
 20  import re 
 21   
 22  __all__ = ['classes', 'first_last', 'group', 'istext', 'prepared_paginate',  
 23             'paginate', 'Paginator'] 
 24   
 25   
26 -def classes(*args, **kwargs):
27 """Helper function for dynamically assembling a list of CSS class names 28 in templates. 29 30 Any positional arguments are added to the list of class names. All 31 positional arguments must be strings: 32 33 >>> classes('foo', 'bar') 34 u'foo bar' 35 36 In addition, the names of any supplied keyword arguments are added if they 37 have a truth value: 38 39 >>> classes('foo', bar=True) 40 u'foo bar' 41 >>> classes('foo', bar=False) 42 u'foo' 43 44 If none of the arguments are added to the list, this function returns 45 `None`: 46 47 >>> classes(bar=False) 48 """ 49 classes = list(filter(None, args)) + [k for k, v in kwargs.items() if v] 50 if not classes: 51 return None 52 return u' '.join(classes)
53
54 -def first_last(idx, seq):
55 return classes(first=idx == 0, last=idx == len(seq) - 1)
56 57
58 -def group(iterable, num, predicate=None):
59 """Combines the elements produced by the given iterable so that every `n` 60 items are returned as a tuple. 61 62 >>> items = [1, 2, 3, 4] 63 >>> for item in group(items, 2): 64 ... print item 65 (1, 2) 66 (3, 4) 67 68 The last tuple is padded with `None` values if its' length is smaller than 69 `num`. 70 71 >>> items = [1, 2, 3, 4, 5] 72 >>> for item in group(items, 2): 73 ... print item 74 (1, 2) 75 (3, 4) 76 (5, None) 77 78 The optional `predicate` parameter can be used to flag elements that should 79 not be packed together with other items. Only those elements where the 80 predicate function returns True are grouped with other elements, otherwise 81 they are returned as a tuple of length 1: 82 83 >>> items = [1, 2, 3, 4] 84 >>> for item in group(items, 2, lambda x: x != 3): 85 ... print item 86 (1, 2) 87 (3,) 88 (4, None) 89 """ 90 buf = [] 91 for item in iterable: 92 flush = predicate and not predicate(item) 93 if buf and flush: 94 buf += [None] * (num - len(buf)) 95 yield tuple(buf) 96 del buf[:] 97 buf.append(item) 98 if flush or len(buf) == num: 99 yield tuple(buf) 100 del buf[:] 101 if buf: 102 buf += [None] * (num - len(buf)) 103 yield tuple(buf)
104 105
106 -def istext(text):
107 from genshi.core import Markup 108 return isinstance(text, basestring) and not isinstance(text, Markup)
109
110 -def prepared_paginate(items, num_items, max_per_page):
111 if max_per_page == 0: 112 num_pages = 1 113 else: 114 num_pages = int(ceil(float(num_items) / max_per_page)) 115 return items, num_items, num_pages
116
117 -def paginate(items, page=0, max_per_page=10):
118 """Simple generic pagination. 119 120 Given an iterable, this function returns: 121 * the slice of objects on the requested page, 122 * the total number of items, and 123 * the total number of pages. 124 125 The `items` parameter can be a list, tuple, or iterator: 126 127 >>> items = range(12) 128 >>> items 129 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 130 >>> paginate(items) 131 ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 12, 2) 132 >>> paginate(items, page=1) 133 ([10, 11], 12, 2) 134 >>> paginate(iter(items)) 135 ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 12, 2) 136 >>> paginate(iter(items), page=1) 137 ([10, 11], 12, 2) 138 139 This function also works with generators: 140 141 >>> def generate(): 142 ... for idx in range(12): 143 ... yield idx 144 >>> paginate(generate()) 145 ([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 12, 2) 146 >>> paginate(generate(), page=1) 147 ([10, 11], 12, 2) 148 149 The `max_per_page` parameter can be used to set the number of items that 150 should be displayed per page: 151 152 >>> items = range(12) 153 >>> paginate(items, page=0, max_per_page=6) 154 ([0, 1, 2, 3, 4, 5], 12, 2) 155 >>> paginate(items, page=1, max_per_page=6) 156 ([6, 7, 8, 9, 10, 11], 12, 2) 157 """ 158 if not page: 159 page = 0 160 start = page * max_per_page 161 stop = start + max_per_page 162 163 count = None 164 if hasattr(items, '__len__'): 165 count = len(items) 166 if count: 167 assert start < count, 'Page %d out of range' % page 168 169 try: # Try slicing first for better performance 170 retval = items[start:stop] 171 except TypeError: # Slicing not supported, so iterate through the whole list 172 retval = [] 173 idx = -1 # Needed if items = [] 174 for idx, item in enumerate(items): 175 if start <= idx < stop: 176 retval.append(item) 177 # If we already obtained the total number of items via `len()`, 178 # we can break out of the loop as soon as we've got the last item 179 # for the requested page 180 if count is not None and idx >= stop: 181 break 182 if count is None: 183 count = idx + 1 184 185 return retval, count, int(ceil(float(count) / max_per_page))
186 187
188 -class Paginator(object):
189
190 - def __init__(self, items, page=0, max_per_page=10, num_items=None):
191 if not page: 192 page = 0 193 194 if num_items is None: 195 items, num_items, num_pages = paginate(items, page, max_per_page) 196 else: 197 items, num_items, num_pages = prepared_paginate(items, num_items, 198 max_per_page) 199 offset = page * max_per_page 200 self.page = page 201 self.max_per_page = max_per_page 202 self.items = items 203 self.num_items = num_items 204 self.num_pages = num_pages 205 self.span = offset, offset + len(items) 206 self.show_index = True
207
208 - def __iter__(self):
209 return iter(self.items)
210
211 - def __len__(self):
212 return len(self.items)
213
214 - def __nonzero__(self):
215 return len(self.items) > 0
216
217 - def __setitem__(self, idx, value):
218 self.items[idx] = value
219
220 - def has_more_pages(self):
221 return self.num_pages > 1
222 has_more_pages = property(has_more_pages) 223
224 - def has_next_page(self):
225 return self.page + 1 < self.num_pages
226 has_next_page = property(has_next_page) 227
228 - def has_previous_page(self):
229 return self.page > 0
230 has_previous_page = property(has_previous_page) 231
232 - def get_shown_pages(self, page_index_count = 11):
233 if self.has_more_pages == False: 234 return range(1, 2) 235 236 min_page = 1 237 max_page = int(ceil(float(self.num_items) / self.max_per_page)) 238 current_page = self.page + 1 239 start_page = current_page - page_index_count / 2 240 end_page = current_page + page_index_count / 2 + \ 241 (page_index_count % 2 - 1) 242 243 if start_page < min_page: 244 start_page = min_page 245 if end_page > max_page: 246 end_page = max_page 247 248 return range(start_page, end_page + 1)
249
250 - def displayed_items(self):
251 from trac.util.translation import _ 252 start, stop = self.span 253 total = self.num_items 254 if start+1 == stop: 255 return _("%(last)d of %(total)d", last=stop, total=total) 256 else: 257 return _("%(start)d - %(stop)d of %(total)d", 258 start=self.span[0]+1, stop=self.span[1], total=total)
259 260
261 -def separated(items, sep=','):
262 """Yield `(item, sep)` tuples, one for each element in `items`. 263 264 `sep` will be `None` for the last item. 265 266 >>> list(separated([1, 2])) 267 [(1, ','), (2, None)] 268 269 >>> list(separated([1])) 270 [(1, None)] 271 272 >>> list(separated("abc", ':')) 273 [('a', ':'), ('b', ':'), ('c', None)] 274 """ 275 items = iter(items) 276 last = items.next() 277 for i in items: 278 yield last, sep 279 last = i 280 yield last, None
281 282 283 try: 284 from json import dumps 285 286 _js_quote = dict((c, '\\u%04x' % ord(c)) for c in '&<>') 287 _js_quote_re = re.compile('[' + ''.join(_js_quote) + ']') 288
289 - def to_json(value):
290 """Encode `value` to JSON.""" 291 def replace(match): 292 return _js_quote[match.group(0)]
293 text = dumps(value, sort_keys=True, separators=(',', ':')) 294 return _js_quote_re.sub(replace, text) 295 296 except ImportError: 297 from trac.util.text import javascript_quote 298
299 - def to_json(value):
300 """Encode `value` to JSON.""" 301 if isinstance(value, basestring): 302 return '"%s"' % javascript_quote(value) 303 elif value is None: 304 return 'null' 305 elif value is False: 306 return 'false' 307 elif value is True: 308 return 'true' 309 elif isinstance(value, (int, long)): 310 return str(value) 311 elif isinstance(value, float): 312 return repr(value) 313 elif isinstance(value, (list, tuple)): 314 return '[%s]' % ','.join(to_json(each) for each in value) 315 elif isinstance(value, dict): 316 return '{%s}' % ','.join('%s:%s' % (to_json(k), to_json(v)) 317 for k, v in sorted(value.iteritems())) 318 else: 319 raise TypeError('Cannot encode type %s' % value.__class__.__name__)
320