2 # This file is part of my.gpodder.org.
4 # my.gpodder.org is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # my.gpodder.org is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 # License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
21 from datetime
import datetime
, timedelta
, date
24 from django
.core
.cache
import cache
27 def daterange(from_date
, to_date
=None, leap
=timedelta(days
=1)):
29 >>> from_d = datetime(2010, 01, 01)
30 >>> to_d = datetime(2010, 01, 05)
31 >>> list(daterange(from_d, to_d))
32 [datetime.datetime(2010, 1, 1, 0, 0), datetime.datetime(2010, 1, 2, 0, 0), datetime.datetime(2010, 1, 3, 0, 0), datetime.datetime(2010, 1, 4, 0, 0), datetime.datetime(2010, 1, 5, 0, 0)]
36 if isinstance(from_date
, datetime
):
37 to_date
= datetime
.now()
39 to_date
= date
.today()
41 while from_date
<= to_date
:
43 from_date
= from_date
+ leap
46 def format_time(value
):
47 """Format an offset (in seconds) to a string
49 The offset should be an integer or float value.
57 >>> format_time(10921)
61 dt
= datetime
.utcfromtimestamp(value
)
66 return dt
.strftime('%M:%S')
68 return dt
.strftime('%H:%M:%S')
70 def parse_time(value
):
75 >>> parse_time('05:10') #5*60+10
78 >>> parse_time('1:05:10') #60*60+5*60+10
82 raise ValueError('None value in parse_time')
84 if isinstance(value
, int):
85 # Don't need to parse already-converted time value
89 raise ValueError('Empty valueing in parse_time')
91 for format
in ('%H:%M:%S', '%M:%S'):
93 t
= time
.strptime(value
, format
)
94 return t
.tm_hour
* 60*60 + t
.tm_min
* 60 + t
.tm_sec
103 >>> parse_bool('True')
106 >>> parse_bool('true')
112 if isinstance(val
, bool):
114 if val
.lower() == 'true':
119 def iterate_together(l1
, l2
, compare
=lambda x
, y
: cmp(x
, y
)):
121 takes two ordered, possible sparse, lists l1 and l2 with similar items
122 (some items have a corresponding item in the other list, some don't).
124 It then yield tuples of corresponding items, where one element is None is
125 there is no corresponding entry in one of the lists.
127 Tuples where both elements are None are skipped.
129 compare is a method for comparing items from both lists; it defaults
132 >>> list(iterate_together(range(1, 3), range(1, 4, 2)))
133 [(1, 1), (2, None), (None, 3)]
135 >>> list(iterate_together([], []))
138 >>> list(iterate_together(range(1, 3), range(3, 5)))
139 [(1, None), (2, None), (None, 3), (None, 4)]
141 >>> list(iterate_together(range(1, 3), []))
142 [(1, None), (2, None)]
144 >>> list(iterate_together([1, None, 3], [None, None, 3]))
157 except StopIteration:
160 i1
, more1
= _take(l1
)
161 i2
, more2
= _take(l2
)
163 while more1
or more2
:
164 if not more2
or (i1
is not None and compare(i1
, i2
) < 0):
166 i1
, more1
= _take(l1
)
168 elif not more1
or (i2
is not None and compare(i1
, i2
) > 0):
170 i2
, more2
= _take(l2
)
172 elif compare(i1
, i2
) == 0:
174 i1
, more1
= _take(l1
)
175 i2
, more2
= _take(l2
)
178 def progress(val
, max_val
, status_str
='', max_width
=50, stream
=sys
.stdout
):
179 print >> stream
, '\r',
180 print >> stream
, '[ %s ] %s / %s | %s' % (
181 '#'*int(float(val
)/max_val
*max_width
) +
182 ' ' * (max_width
-(int(float(val
)/max_val
*max_width
))),
189 def set_cmp(list, simplify
):
191 Builds a set out of a list but uses the results of simplify to determine equality between items
193 simpl
= lambda x
: (simplify(x
), x
)
194 lst
= dict(map(simpl
, list))
200 returns the first not-None object or None if the iterator is exhausted
209 return list(set(a
) & set(b
))
212 def multi_request_view(cls
, view
, wrap
=True, *args
, **kwargs
):
214 splits up a view request into several requests, which reduces
215 the server load of the number of returned objects is large.
217 NOTE: As such a split request is obviously not atomical anymore, results
218 might skip some elements of contain some twice
221 per_page
= kwargs
.get('limit', 1000)
222 kwargs
['limit'] = per_page
+ 1
228 resp
= db
.view(view
, *args
, **kwargs
)
231 for n
, obj
in enumerate(resp
.iterator()):
236 doc
= cls
.wrap(obj
['doc'])
243 kwargs
['startkey'] = key
244 kwargs
['startkey_docid'] = docid
248 # we reached the end of the page, load next one
255 def remove_control_chars(s
):
256 import unicodedata
, re
258 all_chars
= (unichr(i
) for i
in xrange(0x110000))
259 control_chars
= ''.join(map(unichr, range(0,32) + range(127,160)))
260 control_char_re
= re
.compile('[%s]' % re
.escape(control_chars
))
262 return control_char_re
.sub('', s
)
266 return tuple(map(list,zip(*a
)))
269 def parse_range(s
, min, max, default
=None):
271 Parses the string and returns its value. If the value is outside the given
272 range, its closest number within the range is returned
274 >>> parse_range('5', 0, 10)
277 >>> parse_range('0', 5, 10)
280 >>> parse_range('15',0, 10)
283 >>> parse_range('x', 0, 20)
286 >>> parse_range('x', 0, 20, 20)
297 except (ValueError, TypeError):
298 return default
if default
is not None else (max-min)/2
301 def get_to_dict(cls
, ids
, get_id
=lambda x
: x
._id
, use_cache
=False):
311 cache_objs
.append(obj
)
314 db_objs
= list(cls
.get_multi(ids
))
318 cache
.set(get_id(obj
), obj
)
320 return dict((get_id(obj
), obj
) for obj
in cache_objs
+ db_objs
)
324 return [item
for sublist
in l
for item
in sublist
]
327 def linearize(key
, iterators
, reverse
=False):
329 Linearizes a number of iterators, sorted by some comparison function
332 iters
= [iter(i
) for i
in iterators
]
337 vals
. append( (v
, i
) )
338 except StopIteration:
342 vals
= sorted(vals
, key
=lambda x
: key(x
[0]), reverse
=reverse
)
343 val
, it
= vals
.pop(0)
347 vals
.append( (next_val
, it
) )
348 except StopIteration:
352 def skip_pairs(iterator
, cmp=cmp):
353 """ Skips pairs of equal items
355 >>> list(skip_pairs([]))
358 >>> list(skip_pairs([1]))
361 >>> list(skip_pairs([1, 2, 3]))
364 >>> list(skip_pairs([1, 1]))
367 >>> list(skip_pairs([1, 2, 2]))
370 >>> list(skip_pairs([1, 2, 2, 3]))
373 >>> list(skip_pairs([1, 2, 2, 2]))
376 >>> list(skip_pairs([1, 2, 2, 2, 2, 3]))
380 iterator
= iter(iterator
)
381 next
= iterator
.next()
386 next
= iterator
.next()
387 except StopIteration as e
:
391 if cmp(item
, next
) == 0:
392 next
= iterator
.next()
397 def get_timestamp(datetime_obj
):
398 """ Returns the timestamp as an int for the given datetime object
400 >>> get_timestamp(datetime(2011, 4, 7, 9, 30, 6))
403 >>> get_timestamp(datetime(1970, 1, 1, 0, 0, 0))
406 return int(time
.mktime(datetime_obj
.timetuple()))
410 re_url
= re
.compile('^https?://')
413 """ Returns true if a string looks like an URL
415 >>> is_url('http://example.com/some-path/file.xml')
418 >>> is_url('something else')
422 return bool(re_url
.match(string
))