Project

General

Profile

1
# Quantities with units
2
# This file's encoding must be UTF-8Y with BOM so Python will auto-detect UTF-8
3

    
4
import re
5

    
6
import format
7
import strings
8
import util
9

    
10
def parse_range(str_, range_sep='-'):
11
    default = (str_, None)
12
    start, sep, end = str_.partition(range_sep)
13
    if sep == '': return default # not a range
14
    if start == '' and range_sep == '-': return default # negative number
15
    return tuple(d.strip() for d in (start, end))
16

    
17
class MissingUnitsException(Exception):
18
    def __init__(self, quantity):
19
        Exception.__init__(self, 'Quantity has no units: '
20
            +strings.ustr(quantity))
21

    
22
def std_units(units):
23
    if units == None: return units
24
    else: return strings.remove_suffix('.', units) # for abbr ending in '.'
25
    # TODO: deal with '"° and abbrs
26

    
27
class Quantity:
28
    def __init__(self, value='', units=None):
29
        self.value = value
30
        self.units = std_units(util.none_if(units, u''))
31

    
32
def quantity2str(quantity):
33
    str_ = quantity.value
34
    if quantity.units != None: str_ += ' '+quantity.units
35
    return str_
36

    
37
Quantity.__str__ = quantity2str
38
Quantity.__repr__ = quantity2str
39

    
40
def str2quantity(str_):
41
    value = str_
42
    units = None
43
    if not str_.isdigit(): # optimization to avoid regexp for simple cases
44
        match = re.match(ur'(?i)^\s*(.*?\d\.?)\s*([a-z%\'"°][\w.*/^-]*?)?\s*$',
45
            str_)
46
        if match: value, units = match.groups() # has units
47
    return Quantity(value, units)
48

    
49
def set_default_units(quantity, units):
50
    if quantity.units == None: quantity.units = units
51

    
52
conversions = {
53
    ('%', None): 1./100,
54
    ('ft', 'm'): 12*2.54/100,
55
}
56

    
57
def convert(quantity, units):
58
    units = std_units(units)
59
    if quantity.units == units: return quantity # units already correct
60
    try: conversion = conversions[(quantity.units, units)]
61
    except KeyError:
62
        if units == None: return Quantity(quantity.value, units) # remove units
63
        elif quantity.units == None: raise MissingUnitsException(quantity)
64
            # can't convert quantity with unknown units
65
        else: raise NotImplementedError('Unit conversion not implemented yet')
66
    else:
67
        for try_num in xrange(2):
68
            try:
69
                value = format.str2float(quantity.value)
70
                break
71
            except ValueError: quantity.value = parse_range(quantity.value)[0]
72
        return Quantity(str(value*conversion), units)
(45-45/53)