# -*- coding: utf-8 -*-
"""FIXME: many of these really should go in their own modules...
"""
import re, os, datetime
[docs]def identity(x): # XXX: replace any usages of this function with lambda x:x!
"""Return the argument unchanged.
This function is often called `identity` in programming language
and type theory (the type is t -> t, which turns out to be a
difficult type for most classical static type systems).
"""
return x
[docs]def srcpath(base, pth):
"""Return an absolute path based on the relative path `pth`.
Useful in tests, where we don't know what the absolute path is,
and we can't use relative paths since we don't know which folder
the tests are run from.
In a test file xxxxxxx/foo/test/test_foo.py::
path = 'foo/test'
fp = open(srcpath(path, 'data/testdata.txt'))
"""
# FIXME: __file__ is not srcroot when this file has been moved!
# XXX: this no longer works as intended!
srcroot = __file__.replace('\\', '/').rsplit('/', 1)[0]
if not base:
base = '/'
base = base.replace('\\', '/')
if base[0] != '/':
base = '/' + base
if base[-1] != '/':
base += '/'
return srcroot + base + pth.replace('\\', '/')
[docs]def root(): # FIXME: this is fubar (__file__ isn't near the root of the source tree when utils is here...)
"Return the root of the source tree."
return __file__.replace('\\', '/').rsplit('/', 1)[0]
# FIXME: this doesn't work since srcpath doesn't work!
[docs]def dkpath(pth=None):
"""Usage
::
dkpath() => (w:)/xxxxxxx/
=> (/home)/xxxxxxxx/
dkpath('app/') => ../xxxxxxxx/app/
"""
if not pth:
pth = ''
if pth.startswith('/'):
pth = pth[1:]
return os.path.normcase(os.path.normpath(srcpath('', pth)))
[docs]def hour_minute(v): # XXX: move to ttcal?
"Convert 7.5 (hours) to (7, 30) i.e. 7 hours and 30 minutes."
h = int(v)
m = int((v - h) * 60)
return h, m
[docs]def HourMinute(v): # XXX: move to ttcal?
"Format 7.10 as 7t 06m."
return '%dt %02dm' % hour_minute(v)
[docs]def hm_to_float(h, m): # XXX: move to ttcal?
"Convert 7, 30 to 7.5 hours."
return float(h) + (float(m) / 60.0)
[docs]def single_line(txt):
"Remove multiple spaces and newlines."
return u' '.join(txt.split())
[docs]def lower_case(s, encoding='u8'):
"""Return a lower cased (byte-)string version of ``s`` encoded
in ``encoding``.
"""
s = unicode_repr(s)
s = s.lower()
return s.encode(encoding)
[docs]def ulower_case(val):
"Call val.lower(). Return '' if val is None."
if val is None:
return u''
assert isinstance(val, unicode)
return val.lower()
[docs]def title_case(s, encoding='u8'):
"""Return a title cased (byte-)string version of ``s`` encoded
in ``encoding``.
"""
s = unicode_repr(s)
s = s.title()
return s.encode(encoding)
[docs]def utitle_case(val):
"(safer) val.title() implementation."
if val is None:
return u''
if not isinstance(val, unicode):
raise ValueError(repr(val) + ' of type ' + str(type(val)))
return val.title()
_mixedcase = re.compile(ur'[A-ZÆØÅ][a-zæøå]+[A-ZÆØÅ]')
_mcmac = re.compile(ur'(Mc)|(Mac)|(Van)|(Von)')
[docs]def title_case_lastname(s, encoding='u8'):
"""Return a title cased version of ``s`` encoded in ``encoding``.
If it looks like ``s`` is already title cased, then leave it alone
(in case of manual override and complex capitalization rules for
last names).
"""
if not s:
return ''
us = unicode_repr(s)
m = _mixedcase.match(us)
if m:
return us.encode(encoding)
else:
ts = us.title()
return ts.encode(encoding)
[docs]def utitle_case_lastname(s):
"""Return a title cased version of ``s``.
If ``s`` contains a recognized special case, then return it unchanged.
"""
if not s:
return u''
m = _mcmac.match(s)
if m:
return s
else:
return s.title()
[docs]def unicode_repr(obj):
"""Return obj as a unicode string. If obj is a (non-)unicode string, then
first try to decode it as utf-8, then as iso-8859-1.
"""
if isinstance(obj, unicode):
return obj
if isinstance(obj, str):
try:
return obj.decode('u8')
except:
return obj.decode('l1')
return unicode(obj)
u = unicode_repr
[docs]def utf8(obj):
"Return a utf-8 representation of ``obj``."
return unicode_repr(obj).encode('u8')
[docs]def latin1(obj):
"Return a latin-1 representation of ``obj``."
return unicode_repr(obj).encode('l1')
u8 = utf8
[docs]def unhtml(s, toencoding=None):
"Convert charrefs for Norwegian vowels to their unicode counterparts."
if type(s) not in (str, unicode):
return s
if type(s) is str:
s = unicode(s)
tr = {
u' ': u' ',
u'Å': u'Å',
u'Æ': u'Æ',
u'Ø': u'Ø',
u'å': u'å',
u'æ': u'æ',
u'ø': u'ø',
u'é': u'é',
}
for k, v in tr.items():
s = s.replace(k, v)
if toencoding is None:
return s
else:
return s.encode(toencoding)
[docs]def html2u8(s):
"Convert charrefs for Norwegian vowels to their utf-8 counterparts."
return unhtml(s, 'u8')
[docs]def normalize(v):
"""Return a string version of v such that
normalize(u) == normalize(v) iff **not** (u != v)
e.g.:
normalize(None) == normalize('') == normalize(u'')
"""
if type(v) == unicode:
return v.encode('u8')
if v in (None, ''):
return ''
return str(v)
[docs]def nlat(v):
"Normalize and recover from utf-8 stored in varchar columns."
return normalize(v).decode('u8').encode('l1')
[docs]def kronestring(kr):
"""Return a string version of the integer value ``kr``, with
space as the thousand-separator.
"""
if kr == 0:
return '0'
res = ''
if kr < 0:
sign = '-'
kr = -kr
else:
sign = ''
while kr:
kr, tusen = divmod(kr, 1000)
res = ' '.join(['%03d' % tusen, res])
return sign + res.rstrip(' ').lstrip('0')
[docs]def orestring(n):
u"""Return a string version of the integer ``øre`` value. Either a two-digit
string or a dash (as in 5,-).
"""
if n == 0:
return '-'
return '%02d' % n
[docs]def kr_ore(n):
u"Convert the øre-value ``n`` to a proper NOK string value."
kr, ore = divmod(n, 100)
return kronestring(kr) + ',' + orestring(ore)
[docs]def mk_post(model):
"""Encode ``model`` (a dict-like object) into a dict where:
- all values are strings
- None values are removed
- date values are expanded into year/month/day parts
Note:: this function is superceeded by maint.client._encode_date
which does this transparently for unit tests.
"""
res = {}
for key, val in model.items():
if isinstance(val, datetime.date):
res[key + '_year'] = str(val.year)
res[key + '_month'] = str(val.month)
res[key + '_day'] = str(val.day)
elif val is None:
pass # do nothing
else:
res[key] = str(val)
return res
[docs]class Ordered(dict): # FIXME: should be removed asap.
"""
Mapping that maintains insertion order.
(Should be removed and the adt versions should be used).
"""
def __init__(self):
super(Ordered, self).__init__()
self._ordered = []
def __setitem__(self, key, val):
super(Ordered, self).__setitem__(key, val)
self._ordered.append(key)
def __getitem__(self, key):
if key not in self._ordered:
return ''
return super(Ordered, self).__getitem__(key)
[docs] def keys(self):
return self._ordered
[docs] def values(self):
for key in self._ordered:
yield self[key]
[docs] def items(self):
for key in self._ordered:
yield (key, self[key])