~vorlon/ubuntu/saucy/gourmet/trunk

« back to all changes in this revision

Viewing changes to src/lib/convert.py

  • Committer: Steve Langasek
  • Author(s): Bernhard Reiter, Bernhard Reiter
  • Date: 2013-03-01 08:16:00 UTC
  • mfrom: (1.4.4)
  • Revision ID: steve.langasek@canonical.com-20130301081600-sfhpywjupqko07mk
Tags: 0.16.0-0ubuntu1
[ Bernhard Reiter ]
* New upstream release (Closes: #530403, LP: #1010659).
  This also fixes PIL imports as requested by
  http://lists.debian.org/debian-python/2013/02/msg00017.html
* debian/control:
  - Bump Standards-Version to 3.9.4.
  - Remove XS-Python-Version line.
  - Build-Depends: Remove quilt, add python-all, upgrade debhelper version to
    (>= 7.0.50~).
  - Build-Depends-Indep: Remove python-gtk2-dev, add python-distutils-extra.
  - Add Pre-Depends: dpkg (>= 1.15.6~) (for xz compression).
  - Depends: Drop python-glade2, increase python-gtk2 version (>= 2.22.0),
    version depend on python-sqlalchemy (>= 0.7), add python-elib.intl.
  - Recommends: Drop python-gnome2, add python-gst0.10, python-beautifulsoup.
  - Description: Update to reflect contents of gourmet/version.py.
* debian/copyright:
  - Switch to packaging-manuals/copyright-format/1.0 format.
  - Add contributors as of gourmet/version.py.
* debian/dirs: Remove, as obsolete.
* debian/docs: s/README/README.md
* debian/manpages: Add.
* debian/patches/01_fix_raise_str.diff:
  Drop (fixed upstream).
* debian/patches/lc-all-c, debian/patches/series:
  Fix a bug that caused gourmet to fail when LC_ALL=C.
* debian/patches/license-location, debian/patches/series:
  Add patch to change the location used to look for the license.
* debian/rules: Switch to tiny dh style, xz compression.
* debian/watch: Update to new location of releases on Launchpad.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
 
import re, locale, math
3
 
from defaults.defaults import lang as defaults
4
 
from gettext import gettext as _
5
 
from gettext import ngettext
6
 
from gdebug import *
7
 
 
8
 
FRACTIONS_ALL = 1
9
 
FRACTIONS_NORMAL = 0
10
 
FRACTIONS_ASCII = -1
11
 
FRACTIONS_OFF = -2
12
 
 
13
 
USE_FRACTIONS = FRACTIONS_NORMAL 
14
 
 
15
 
class PossiblyCaseInsensitiveDictionary (dict):
16
 
 
17
 
    transformations = ["lower","title","upper"]
18
 
 
19
 
    def has_key (self, k):
20
 
        if dict.has_key(self,k): return True
21
 
        else:
22
 
            for t in self.transformations:
23
 
                if hasattr(k,t):
24
 
                    if dict.has_key(self,getattr(k,t)()): return True
25
 
        return False
26
 
 
27
 
    def __getitem__ (self, k):
28
 
        if dict.has_key(self,k):
29
 
            return dict.__getitem__(self,k)
30
 
        else:
31
 
            for t in self.transformations:
32
 
                if hasattr(k,t):
33
 
                    nk = getattr(k,t)()
34
 
                    if dict.has_key(self,nk):
35
 
                        return dict.__getitem__(self,nk)
36
 
        # Raise plain old error
37
 
        dict.__getitem__(self,k)
38
 
        
39
 
 
40
 
class Converter:
41
 
 
42
 
    __single = None
43
 
 
44
 
    unit_to_seconds = {
45
 
    'seconds':1,
46
 
    'minutes':60,
47
 
    'hours':60*60,
48
 
    'days':24*60*60,
49
 
    'weeks':7*24*60*60,
50
 
    'months':31*24*60*60,
51
 
    'years':365.25*24*60*60,
52
 
    #'decades':10*365.25*24*60*60,
53
 
    #'centuries':100*365.25*24*60*60,
54
 
    #'millenia':1000*365.25*24*60*60,
55
 
    }
56
 
 
57
 
    # this is a bit of a hackish attempt to make a matcher for all
58
 
    # plural forms of time units. We use range(5) since as far as I
59
 
    # can see, enumerating the forms for 0 through 5 should give you
60
 
    # all possible forms in all languages.
61
 
    #
62
 
    # See http://www.sbin.org/doc/glibc/libc_8.html
63
 
    time_units = [('seconds',[ngettext('second','seconds',n) for n in range(5)] + ['s.', 'sec', 'secs','s' ]),
64
 
                  # min. = abbreviation for minutes
65
 
                  ('minutes',[_('min'),'min.','min','mins','m'] + [ngettext('minute','minutes',n) for n in range(5)]),
66
 
                  # hrs = abbreviation for hours
67
 
                  ('hours',[_('hrs.'),'hrs','hr','h'] + [ngettext('hour','hours',n) for n in range(5)]),
68
 
                  ('days',[ngettext('day','days',n) for n in range(5)]),
69
 
                  ('years',[ngettext('year','years',n) for n in range(5)]),
70
 
                  #('decades',[ngettext('decade','decades',n) for n in range(5)]),
71
 
                  #('centuries',[ngettext('century','centuries',n) for n in range(5)]),
72
 
                  #('millenia',[ngettext('millenium','millenia',n) for n in range(5)]),
73
 
                  ]
74
 
    
75
 
    def __init__(self):
76
 
        if Converter.__single: raise Converter.__single
77
 
        else: Converter.__single = self
78
 
        self.create_conv_table()
79
 
        self.create_density_table()
80
 
        self.create_cross_unit_table()
81
 
        self.create_vol_to_mass_table()
82
 
        # right now we only track densities, but we might convert
83
 
        # between other kinds of units eventually
84
 
        self.cross_unit_dicts={'density':self.density_table}
85
 
        ## This allows for varied spellings of units to be entered.
86
 
        self.create_unit_dict()
87
 
        self.add_time_table()
88
 
        self.build_converter_dictionary()
89
 
        self.build_converter_dictionary(self.v2m_table,density=True)
90
 
 
91
 
    def add_time_table (self):
92
 
        for u,conv in self.unit_to_seconds.items():
93
 
            self.conv_table[(u,'seconds')]=conv        
94
 
 
95
 
    def create_conv_table(self):
96
 
        self.conv_table = defaults.CONVERTER_TABLE.copy()
97
 
 
98
 
    def create_density_table (self):
99
 
        self.density_table = defaults.DENSITY_TABLE.copy()
100
 
 
101
 
    def create_vol_to_mass_table (self):
102
 
        self.v2m_table = defaults.VOL_TO_MASS_TABLE.copy()
103
 
 
104
 
    def create_cross_unit_table (self):
105
 
        self.cross_unit_table = defaults.CROSS_UNIT_TABLE.copy()
106
 
 
107
 
    def create_unit_dict(self):
108
 
        self.units = defaults.UNITS[0:]
109
 
        for u,alts in self.time_units:
110
 
            lst = []
111
 
            for a in alts:
112
 
                if not a in lst: lst.append(a)
113
 
                if not a.title() in lst: lst.append(a.title())
114
 
                if not a.capitalize() in lst: lst.append(a.capitalize())
115
 
                if not a.upper() in lst: lst.append(a.upper())
116
 
                if not a.lower() in lst: lst.append(a.lower())
117
 
            self.units.append((u,lst))
118
 
        self.unit_dict=PossiblyCaseInsensitiveDictionary()
119
 
        for itm in self.units:
120
 
            key = itm[0]
121
 
            variations = itm[1]
122
 
            self.unit_dict[key] = key
123
 
            for v in variations:
124
 
                self.unit_dict[v] = key
125
 
 
126
 
    def build_converter_dictionary (self, table=None, density=False):
127
 
        # first, make a list of all units in our dictionaries
128
 
        if not density:
129
 
            convert = self.convert_simple
130
 
        else:
131
 
            def convert (u1,u2):
132
 
                # Ignore anything that doesn't need density
133
 
                if self.convert_simple(u1,u2): return None
134
 
                return self.convert_w_density(u1,u2,density=1)
135
 
        units = []
136
 
        if not table:
137
 
            table=self.conv_table
138
 
        #else:
139
 
            #print "We were handed a table: ",table
140
 
        for u1,u2 in filter(lambda x: len(x)==2, table.keys()):
141
 
            if u1 not in units: units.append(u1)
142
 
            if u2 not in units: units.append(u2)
143
 
        #print 'done looping through list'
144
 
        for u in units:
145
 
            #print 'grabbing possible conversions for ',u
146
 
            debug('unit=%s'%u)
147
 
            d=self.possible_conversions(u,dict=table)
148
 
            to_expand = d.keys()
149
 
            # keep a list of what we've expanded
150
 
            expanded = []
151
 
            while len(to_expand) >= 1:
152
 
                itm = to_expand.pop()
153
 
                if itm not in expanded:
154
 
                    #debug('Expanding %s'%itm)
155
 
                    factor = convert(itm,u)
156
 
                    #debug('There are %s %s in a %s'%(factor,u,itm))
157
 
                    d2 = self.possible_conversions(itm)
158
 
                    if factor:
159
 
                        for k,v in d2.items():
160
 
                            if not convert(u,k):
161
 
                                #debug('and there are %s %s in a %s'%(v,itm,k))
162
 
                                conversion = float(v) * float(factor)
163
 
                                # If we're doing density, we want to
164
 
                                # make sure we always have our tuples
165
 
                                # (volume,density)
166
 
                                if density and itm not in [key[0] for key in table.keys()]:
167
 
                                    table[(u,k)]=float(1)/conversion
168
 
                                else: table[(k,u)]= conversion
169
 
                            if k not in expanded and k not in to_expand and k != itm and k != u:
170
 
                                to_expand.append(k)
171
 
                    expanded.append(itm)
172
 
 
173
 
    def convert_simple (self, u1, u2, item=None):
174
 
        if u1 == u2:
175
 
            return 1.0
176
 
        else:
177
 
            dict=self.conv_table
178
 
            if dict.has_key((u1,u2)):
179
 
                return dict[(u1,u2)]
180
 
            elif dict.has_key((u2,u1)):
181
 
                return float(1) / float(dict[(u2,u1)])
182
 
            else:
183
 
                return 0
184
 
 
185
 
    def convert_w_density (self, u1, u2, density=None, item=None):
186
 
        if u1 == u2:
187
 
            return 1.0
188
 
        if not density:
189
 
            if self.density_table.has_key(item):
190
 
                density=self.density_table[item]
191
 
            else:
192
 
                return None
193
 
        if self.v2m_table.has_key((u1,u2)):
194
 
            conv = self.v2m_table[(u1,u2)]                
195
 
            return conv * density
196
 
        elif self.v2m_table.has_key((u2,u1)):
197
 
            conv = float(1) / float(self.v2m_table[(u2,u1)])
198
 
            return conv / density
199
 
        else:
200
 
            return None
201
 
 
202
 
    def list_of_cu_tables (self, dictcu=None):
203
 
        if (not dictcu):
204
 
            dictcu = self.cross_unit_table
205
 
        values = dictcu.values()
206
 
        ret = []
207
 
        for v in values:
208
 
            if not v[0] in ret:
209
 
                ret.append(v[0])
210
 
        return ret
211
 
                
212
 
    def conv_dict_for_item (self, item=None, dictcu=None, mult=None):
213
 
        """item overrides mult"""
214
 
        if (not dictcu):
215
 
            dictcu = self.cross_unit_table
216
 
#        tbls = self.list_of_cu_tables(dictcu)
217
 
#        my_tbls = []
218
 
        ret = {}
219
 
        for itm in dictcu.items():
220
 
            k = itm[0]
221
 
            v = itm[1]
222
 
            dct = self.cross_unit_dicts[v[0]]
223
 
            conv = v[1]
224
 
            if item and dct.has_key(item):
225
 
                mult = dct[item]
226
 
            if mult: 
227
 
                ret[k] = conv * mult
228
 
        return ret
229
 
 
230
 
    def converter (self, u1, u2, item=None, density=None):
231
 
        ## Just a front end to convert_fancy that looks up units
232
 
        if self.unit_dict.has_key(u1):
233
 
            unit1 = self.unit_dict[u1]
234
 
        else:
235
 
            unit1 = u1
236
 
        if self.unit_dict.has_key(u2):
237
 
            unit2 = self.unit_dict[u2]
238
 
        else:
239
 
            unit2 = u2
240
 
        ## provide some kind of item lookup?
241
 
        return self.convert_fancy(unit1, unit2, item=item, density=density)
242
 
 
243
 
    def convert_fancy (self, u1, u2, item=None, density=None):
244
 
        simple = self.convert_simple(u1,u2,self.conv_table)
245
 
        if simple: return simple
246
 
        # otherwise, we need to grab use a density table
247
 
        debug('using density data')
248
 
        return self.convert_w_density(u1,u2,item=item,density=density)
249
 
 
250
 
    def get_conversions (self, u, item=None, density=None):
251
 
        dct = None
252
 
        if item or density:
253
 
            dct = self.conv_table.copy()
254
 
            if item:
255
 
                dct.update(self.conv_dict_for_item(item))
256
 
            elif density:
257
 
                dct.update(self.conv_dict_for_item(mult=density))
258
 
        return self.possible_conversions(u, dct)
259
 
 
260
 
    def get_all_conversions (self, u, item=None, density=None):
261
 
        dict = self.get_conversions(u, item, density)
262
 
        expanded = []
263
 
        conversions = dict.keys()
264
 
        lst = conversions[0:] # make another copy to chew up
265
 
        while len(lst) >= 1:
266
 
            itm = lst.pop()
267
 
            if not itm in conversions:
268
 
                conversions.append(itm)
269
 
            if not itm in expanded:
270
 
                d = self.get_conversions(u,itm,density)
271
 
                lst.extend(d.keys())
272
 
                expanded.append(itm)
273
 
        return conversions
274
 
    
275
 
    def possible_conversions(self, u, dict=0):
276
 
        """Return a dictionary of everything that unit u can convert to
277
 
        The keys are what it can convert to and the values are the conversion
278
 
        factor."""
279
 
        if (not dict):
280
 
            dict=self.conv_table
281
 
        ret = {}
282
 
        entries = dict.items()
283
 
        for item in entries:
284
 
            i1 = item[0][0]
285
 
            i2 = item[0][1]
286
 
            if u == i1:
287
 
                ret[i2] = float(1) / item[1]
288
 
            elif u == i2:
289
 
                ret[i1] = float(item[1])
290
 
        return ret            
291
 
 
292
 
    def readability_score (self,amt,unit=None):
293
 
        """We rate the readability of a number and unit
294
 
 
295
 
        This is rather advanced. We presume a few things -- such as that we
296
 
        like integers and simple fractions 1/2 1/3 1/4 better than other things.
297
 
 
298
 
        We also learn from the defaults module what the bounds are for readability
299
 
        for each amount given.
300
 
        """
301
 
        readability = 0
302
 
        ## We like our numbers whole or in normal fractions
303
 
        if integerp(amt):
304
 
            readability += 1
305
 
        elif integerp(amt * 2):
306
 
            readability += 1
307
 
        elif integerp(amt * 4):
308
 
            readability += 1
309
 
        elif integerp(amt * 3):
310
 
            readability += 1
311
 
        elif integerp (amt * 8):
312
 
            readability += 0.8
313
 
        elif integerp (amt * 6):
314
 
            readability += 0.3
315
 
        elif integerp (amt * 5):
316
 
            readability += 0.2
317
 
        elif integerp (amt * 10):
318
 
            readability += 0.05
319
 
        ## If it is not a normal fraction, readability takes a hit
320
 
        else:
321
 
            readability += -2
322
 
        ## If it is exactly 1 it gets a bump:
323
 
        if amt == 1: readability += 0.5
324
 
        if unit:
325
 
            # if we are beyond the min or max for our group, we take
326
 
            # a substantial readability hit (this is worse than anything
327
 
            # else)
328
 
            try:
329
 
                u = self.unit_dict[unit]
330
 
            except KeyError:
331
 
                debug("KeyError looking up unit",1)
332
 
                u = unit
333
 
            try:
334
 
                ugroup,n = defaults.unit_group_lookup[u]
335
 
            except KeyError:
336
 
                debug('Key Error for %s in \nunit_group_lookup: %s'%(unit,defaults.unit_group_lookup),
337
 
                      0)
338
 
                #raise
339
 
                return -10
340
 
            else:
341
 
                u,rng = defaults.UNIT_GROUPS[ugroup][n]
342
 
                mn,mx = rng
343
 
                debug('Readability range for %s = %s:%s'%(u,mn,mx),8)
344
 
                if mn and amt and  amt < mn:
345
 
                    readability += -2
346
 
                    # we add a penalty proportional to the undersizedness
347
 
                    if (mn-amt): readability += -(2 * (mn - amt))/amt
348
 
                if mx and amt > mx:
349
 
                    readability += -2
350
 
                    # now we get clever and add a penalty proportional to the oversizedness
351
 
                    if (amt-mx): readability += - ((amt - mx)/float(mx))*2
352
 
        else:
353
 
            # otherwise, we'll make some assumptions about numbers
354
 
            # we don't like things over a thousand and we really don't
355
 
            # or under a hundredth
356
 
            if amt > 1000: readability += -2
357
 
            elif amt < .1:
358
 
                readability += -1
359
 
                if amt < .01:
360
 
                    readability += -1
361
 
                    if amt < .001:
362
 
                        readability += -1
363
 
                        if amt < .0001:
364
 
                            readability += -1
365
 
        ## And we like numbers between 1/8 and 4
366
 
        #if 0.25 <= amt <= 4:
367
 
        #    readability += 1
368
 
        ### Less than 1/10th is getting too small
369
 
        #elif 0.1 >= amt:
370
 
        #    readability += -1
371
 
        ## And we rather dislike numbers over 10
372
 
        #elif 20 >= amt > 10:
373
 
        #    readability += -0.9
374
 
        ## And we really can't have numbers over 20
375
 
        #elif 100 >= amt > 20:
376
 
        #    readability += -5
377
 
        ## And we really, really, really can't have numbers over 100
378
 
        #elif 1000 >= amt > 100:
379
 
        #    readability += -10
380
 
        #elif amt >= 1000:
381
 
        #    readability += -20
382
 
        return readability
383
 
 
384
 
    def adjust_unit (self, amt, unit, item=None, favor_current_unit=True, preferred_unit_groups=[]):
385
 
 
386
 
        """Return the most readable equivalent of amount and unit for item ITM
387
 
 
388
 
        amt - our amount
389
 
        unit - our current unit
390
 
        item - the item (in case it makes a difference -- currently not implemented)
391
 
        favor_current_item - a flag; if True (default) we give our current unit a slight
392
 
                             preference, to avoid changing the unit if unnecessary.
393
 
        Here we do our best to provide readable units, so that the user is presented
394
 
        with 1/2 cup rather than 8 tablespoons, for example.
395
 
        """
396
 
        if not amt: return amt,unit
397
 
        try:
398
 
            u = self.unit_dict[unit]
399
 
            ugroup,n = defaults.unit_group_lookup[u]
400
 
        except KeyError:
401
 
            return amt,unit
402
 
        else:
403
 
            units=defaults.UNIT_GROUPS[ugroup]                
404
 
            if preferred_unit_groups:
405
 
                if ugroup not in preferred_unit_groups:
406
 
                    for ug in preferred_unit_groups:
407
 
                        conv = self.converter(u,defaults.UNIT_GROUPS[ug][0][0])
408
 
                        if conv:
409
 
                            units = defaults.UNIT_GROUPS[ug]
410
 
                            amt = conv * amt
411
 
                            u = unit = defaults.UNIT_GROUPS[ug][0][0]
412
 
                            break
413
 
                        else:
414
 
                            continue
415
 
            ret_readability = self.readability_score(amt,unit)
416
 
            if favor_current_unit: ret_readability += 1
417
 
            ret_amt = amt
418
 
            ret_unit = unit
419
 
            ret_distance = 0
420
 
            n1 = 0
421
 
            for u2,rng in units:
422
 
                conv = self.converter(u,u2)
423
 
                if not conv:
424
 
                    continue
425
 
                new_amt = conv * amt
426
 
                readability = self.readability_score(new_amt,u2)
427
 
                debug('%s %s, Readability = %s'%(new_amt,u2,readability),6)
428
 
                use_us = False
429
 
                if readability > ret_readability:
430
 
                    use_us = True
431
 
                elif readability == ret_readability and abs(n-n1) < ret_distance:
432
 
                    use_us = True
433
 
                if use_us:
434
 
                    ret_amt = new_amt
435
 
                    ret_distance = abs(n-n1)
436
 
                    ret_unit = u2
437
 
                    ret_readability = readability
438
 
                n1 += 1
439
 
            debug('adjust unit called with %s %s, returning %s %s (R:%s)'%(amt,unit,ret_amt,ret_unit,
440
 
                                                                           ret_readability),
441
 
                  3)
442
 
            return ret_amt,ret_unit
443
 
 
444
 
    def use_reasonable_unit (self, amt1, u1, amt2, u2, conv):
445
 
        """Given the conversion factor and the amounts,
446
 
        we're going to try to figure out which unit
447
 
        is the most human-readable.  conv is what converts
448
 
        from amt1 into amt2.  We return a list of
449
 
        amt unit, where the amount is our total, and the unit
450
 
        is our chosen unit."""
451
 
        u1amt = amt1 + amt2 * (1 / float(conv))
452
 
        u2amt = amt2 + amt1 * conv
453
 
        if self.readability_score(u1amt) >= self.readability_score(u2amt):
454
 
            return [u1amt, u1]
455
 
        else:
456
 
            return [u2amt, u2]
457
 
 
458
 
    def add_reasonably (self, a1, u1, a2, u2, item=None):
459
 
        """Return a list with amount and unit if conversion is possible.
460
 
        Else return None"""
461
 
        if not (a1 and a2):
462
 
            # we give up if these aren't numbers
463
 
            return None
464
 
        conv = self.converter(u1,u2, item)
465
 
        if conv:
466
 
            ## Don't bother with all the work if the conversion factor is 1
467
 
            if conv == 1:
468
 
                return [a1 + a2, u1]
469
 
            else:
470
 
                return self.use_reasonable_unit(a1, u1, a2, u2, conv)
471
 
        else:
472
 
            return None
473
 
 
474
 
    def amt_string(self, amt, approx=0.01):
475
 
        """Given list of [amount unit], hand back a string
476
 
        representing amount.  We'll do our best to turn numbers back
477
 
        into fractions here so that they will look easily readable.
478
 
 
479
 
        We can also handle amounts handed to us as tuples (as ranges)!"""
480
 
        num = amt[0]
481
 
        un = amt[1]
482
 
        if type(num)==tuple or type(num)==list:
483
 
            nstring=float_to_frac(num[0],approx=approx).strip()
484
 
            if len(num)>1 and num[1]:
485
 
                nstring += "-"
486
 
                nstring += float_to_frac(num[1],approx=approx).strip()
487
 
        else:
488
 
            nstring = float_to_frac(num,approx=approx)
489
 
        if un:
490
 
            return "%s %s" %(nstring, un)
491
 
        else:
492
 
            return "%s"%nstring
493
 
 
494
 
    def timestring_to_seconds (self, timestring):
495
 
        """Take a timestring and parse it into seconds.
496
 
 
497
 
        We assume numbers come before time units - surely this will
498
 
        break some languages(?). We'll build the parameter in when the
499
 
        time comes...
500
 
 
501
 
        Return 0 if timestring is unparsable
502
 
        """
503
 
        # Before we do our real work, parse times that look like this 0:30
504
 
        # Note the following will be true
505
 
        # 1:30 = 1 1/2 hours
506
 
        # 00:00:20 = 20 seconds
507
 
        if re.match('^\d\d?:\d\d(:\d\d)?$',timestring):
508
 
            times = [locale.atof(s) for s in timestring.split(':')]
509
 
            if len(times) == 3:
510
 
                h,m,s = times
511
 
            else:
512
 
                h,m = times; s = 0
513
 
            return h*60*60 + m*60 + s
514
 
        numbers = []
515
 
        for match in NUMBER_FINDER.finditer(timestring):
516
 
            if numbers: numbers[-1].append(match.start())
517
 
            numbers.append([match.start(),match.end()])
518
 
        if numbers: numbers[-1].append(None)
519
 
        secs = 0
520
 
        for num_start,num_end,section_end in numbers:
521
 
            num = frac_to_float(timestring[num_start:num_end])
522
 
            unit = timestring[num_end:section_end].strip()
523
 
            if self.unit_dict.has_key(unit):
524
 
                conv = self.converter(unit,'seconds')
525
 
                if conv and num:
526
 
                    secs += num * conv
527
 
        return secs
528
 
 
529
 
    def timestring_to_seconds_old (self, timestring):
530
 
        """Take a timestring and parse it into seconds.
531
 
 
532
 
        This logic may be a little fragile for non-English languages.
533
 
        """
534
 
        words = re.split('[ \s,;]+',str(timestring))
535
 
        seconds = 0
536
 
        num = []
537
 
        for n,w in enumerate(words):
538
 
            if NUMBER_MATCHER.match(w): num.append(w)
539
 
            elif num and self.unit_dict.has_key(w):                
540
 
                conv = self.converter(w,'seconds')
541
 
                if conv:
542
 
                    n = frac_to_float(" ".join(num))
543
 
                    if n: seconds += n * conv
544
 
                    num = []
545
 
        if seconds: return seconds
546
 
 
547
 
def get_converter ():
548
 
    try:
549
 
        return Converter()
550
 
    except Converter, c:
551
 
        return c
552
 
 
553
 
# Each of our time formatting functions takes two arguments, which
554
 
# allows us to handle fractions in the outside world
555
 
time_formatters = {
556
 
    #'millennia':lambda decades: ngettext("millenium","millenia",round(decades))
557
 
    #'centuries':lambda decades: ngettext("century","centuries",round(decades))
558
 
    #'decades':lambda decades: ngettext("decade","decades",round(decades))
559
 
    'years':lambda years: ngettext("year","years",years),
560
 
    'months':lambda months: ngettext("month","months",months),
561
 
    'weeks':lambda weeks: ngettext("week","weeks",weeks),
562
 
    'days':lambda days: ngettext("day","days",days),
563
 
    'hours':lambda hours: ngettext("hour","hours",hours),
564
 
    'minutes':lambda minutes: ngettext("minute","minutes",minutes),
565
 
    'seconds':lambda seconds: ngettext("second","seconds",seconds),
566
 
    }    
567
 
 
568
 
def seconds_to_timestring (time, round_at=None, fractions=FRACTIONS_NORMAL):
569
 
    time = int(time)
570
 
    time_strings = []
571
 
    units = Converter.unit_to_seconds.items()
572
 
    units.sort(lambda a,b: a[1]<b[1] and 1 or a[1]>b[1] and -1 or 0)
573
 
    for unit,divisor in units:  
574
 
        time_covered = time / int(divisor)
575
 
        # special case hours, which we English speakers anyway are
576
 
        # used to hearing in 1/2s -- i.e. 1/2 hour is better than 30
577
 
        # minutes.
578
 
        add_half = 0
579
 
        if time % divisor == .5*divisor and not time_strings:
580
 
            add_half = 0.5
581
 
            time_covered += 0.5
582
 
        if time_covered or add_half:
583
 
            #print time_covered,'(rounds to ',round(time_covered),')'
584
 
            if round_at and len(time_strings)+1>=round_at:
585
 
                if not add_half: time_covered = int(round(float(time)/divisor))
586
 
                time_strings.append(" ".join([
587
 
                    float_to_frac(time_covered,fractions=fractions),
588
 
                    # round because 1/2 = 1 as far as plural formas are concerned
589
 
                    time_formatters[unit](round(time_covered)) 
590
 
                    ])
591
 
                                    )
592
 
                break
593
 
            else:
594
 
                time_strings.append(" ".join([
595
 
                    float_to_frac(time_covered,fractions=fractions),
596
 
                    # round because 1/2 = 1 as far as plural forms are concerned
597
 
                    time_formatters[unit](round(time_covered))
598
 
                    ]))
599
 
                time = time - time_covered * divisor
600
 
                if time==0: break
601
 
    if len(time_strings)>2:
602
 
        # Translators... this is a messay way of concatenating
603
 
        # lists. In English we do lists this way: 1, 2, 3, 4, 5
604
 
        # and 6. This set-up allows for variations of this system only.
605
 
        # You can of course make your language only use commas or
606
 
        # ands or spaces or whatever you like by translating both
607
 
        # ", " and " and " with the same string.
608
 
        return _(" and ").join([_(", ").join(time_strings[0:-1]),time_strings[-1]])
609
 
    else:
610
 
        return _(" ").join(time_strings)    
611
 
 
612
 
def integerp (num, approx=0.01):
613
 
    """approx can be a decimal that is a guide to rounding.
614
 
    That is, if approx is 0.001, then we will consider
615
 
    0.9991 and 1.0009 both to be the integer 1.  This feature
616
 
    will only work for positive, non-zero numbers."""
617
 
    try:
618
 
        if int(num) == float(num):
619
 
            return int(num)
620
 
        elif approx:
621
 
            bigside = int(num+approx)
622
 
            smallside = int(num-approx)
623
 
            if bigside != smallside:
624
 
                return bigside
625
 
            else:
626
 
                return None
627
 
    except:
628
 
        return None
629
 
 
630
 
 
631
 
# Fractions used in most charsets -- for certain exports, etc., we
632
 
# want to limit special fractions to these and use straight-up ascii
633
 
# fractions otherwise. These fractions must also be keys in
634
 
# NUM_TO_FRACTIONS
635
 
 
636
 
NUMBER_WORDS = {}
637
 
if hasattr(defaults,'NUMBERS'):
638
 
    for n,words in defaults.NUMBERS.items():
639
 
        for w in words:
640
 
            NUMBER_WORDS[w] = n
641
 
all_number_words = NUMBER_WORDS.keys()
642
 
all_number_words.sort(
643
 
    lambda x,y: ((len(y)>len(x) and 1) or (len(x)>len(y) and -1) or 0)
644
 
    )
645
 
 
646
 
NUMBER_WORD_REGEXP = '|'.join(all_number_words).replace(' ','\s+')
647
 
FRACTION_WORD_REGEXP = '|'.join(filter(lambda n: NUMBER_WORDS[n]<1.0,
648
 
                                       all_number_words)
649
 
                                ).replace(' ','\s+')
650
 
 
651
 
NORMAL_FRACTIONS = [(1,2),(1,4),(3,4)] 
652
 
 
653
 
NUM_TO_FRACTIONS = {
654
 
    (1,2) : u'\u00BD',
655
 
    (1,4) : u'\u00BC',
656
 
    (3,4) : u'\u00BE',
657
 
    (1,3) : u'\u2153',
658
 
    (2,3) : u'\u2154',
659
 
    (1,5) : u'\u2155',
660
 
    (2,5) : u'\u2156',
661
 
    (3,5) : u'\u2157',
662
 
    (4,5) : u'\u2158',
663
 
    (1,6) : u'\u2159',
664
 
    (5,6) : u'\u215A',
665
 
    (1,8) : u'\u215B',
666
 
    (3,8) : u'\u215C',
667
 
    (5,8) : u'\u215D',
668
 
    (7,8) : u'\u215E',
669
 
    }
670
 
 
671
 
UNICODE_FRACTIONS = {
672
 
    # a dictionary of funky unicode numbers not recognized by standard
673
 
    # python float() and int() functions
674
 
    u'\u00BD':1.0/2,
675
 
    u'\u00BC':1.0/4,
676
 
    u'\u00BE':3.0/4,
677
 
    u'\u2153':1.0/3,
678
 
    u'\u2154':2.0/3,
679
 
    u'\u2155':1.0/5,
680
 
    u'\u2156':2.0/5,
681
 
    u'\u2157':3.0/5,
682
 
    u'\u2158':4.0/5,
683
 
    u'\u2159':1.0/6,
684
 
    u'\u215A':5.0/6,
685
 
    u'\u215B':1.0/8,
686
 
    u'\u215C':3.0/8,
687
 
    u'\u215D':5.0/8,
688
 
    u'\u215E':7.0/8,
689
 
    }
690
 
 
691
 
SUP_DICT = {1:u'\u00B9',
692
 
            2:u'\u00B2',
693
 
            3:u'\u00B3',
694
 
            }
695
 
 
696
 
SLASH = u'\u2044'
697
 
SUB_DICT = {1:u'\u2081',
698
 
            2:u'\u2082',
699
 
            3:u'\u2083',
700
 
            4:u'\u2084',
701
 
            5:u'\u2085',
702
 
            6:u'\u2086',
703
 
            7:u'\u2087',
704
 
            8:u'\u2088',
705
 
            9:u'\u2089',
706
 
            }
707
 
            
708
 
# nonstandard integers (sub or sup) that may be used in fractions
709
 
UNICODE_INTEGERS = {}
710
 
for d in SUB_DICT,SUP_DICT:
711
 
    for k,v in d.items():
712
 
        UNICODE_INTEGERS[v]=k
713
 
 
714
 
NUMBER_REGEXP = "[\d.,"
715
 
#for k in UNICODE_INTEGERS.keys(): NUMBER_REGEXP+=k # COVERED by re.UNICODE
716
 
for k in UNICODE_FRACTIONS.keys(): NUMBER_REGEXP+=k
717
 
NUMBER_START_REGEXP = NUMBER_REGEXP + ']'
718
 
NUMBER_END_REGEXP = NUMBER_REGEXP + SLASH
719
 
NUMBER_END_NO_RANGE_REGEXP = NUMBER_END_REGEXP + " /]"
720
 
NUMBER_END_REGEXP += " /-"
721
 
NUMBER_END_REGEXP += "]"
722
 
NUMBER_REGEXP = "("+NUMBER_START_REGEXP+NUMBER_END_REGEXP+"*"
723
 
if NUMBER_WORD_REGEXP:
724
 
     NUMBER_REGEXP = NUMBER_REGEXP + '|' + NUMBER_WORD_REGEXP + ')'
725
 
     NUMBER_NO_RANGE_REGEXP = '(' + NUMBER_START_REGEXP + '+|' + NUMBER_WORD_REGEXP + ')'
726
 
else:
727
 
    NUMBER_REGEXP = NUMBER_REGEXP + ")"
728
 
    NUMBER_NO_RANGE_REGEXP = NUMBER_START_REGEXP + '+'
729
 
NUMBER_MATCHER = re.compile("^%s$"%NUMBER_REGEXP,re.UNICODE)
730
 
 
731
 
UNICODE_FRACTION_REGEXP = "[" + "".join(UNICODE_FRACTIONS.keys()) + "]"
732
 
DIVIDEND_REGEXP = "[0-9" + "".join(SUP_DICT.values()) + "]+"
733
 
SLASH_REGEXP = "[/" + SLASH + "]"
734
 
SLASH_MATCHER = re.compile(SLASH_REGEXP)
735
 
DIVISOR_REGEXP = "[0-9" + "".join(SUB_DICT.values()) + "]+"
736
 
FRACTION_REGEXP = "(" + UNICODE_FRACTION_REGEXP + "|" + DIVIDEND_REGEXP + \
737
 
                          SLASH_REGEXP + DIVISOR_REGEXP + ")"
738
 
 
739
 
AND_REGEXP = "(\s+%s\s+|\s*[&+]\s*|\s+)"%_('and')
740
 
 
741
 
# Match a fraction
742
 
if NUMBER_WORD_REGEXP:
743
 
    NUM_AND_FRACTION_REGEXP = "((?P<int>%s+|%s)%s)?(?P<frac>(%s|%s))"%(NUMBER_START_REGEXP,
744
 
                                                                       NUMBER_WORD_REGEXP,
745
 
                                                                       AND_REGEXP,
746
 
                                                                       FRACTION_REGEXP,
747
 
                                                                       FRACTION_WORD_REGEXP
748
 
                                                                       )
749
 
    
750
 
else:
751
 
    NUM_AND_FRACTION_REGEXP = "((?P<int>%s)+\s+)?(?P<frac>%s)"%(NUMBER_START_REGEXP,FRACTION_REGEXP)
752
 
 
753
 
FRACTION_MATCHER = re.compile(NUM_AND_FRACTION_REGEXP,re.UNICODE)
754
 
 
755
 
NUMBER_FINDER_REGEXP = "(%(NUM_AND_FRACTION_REGEXP)s|%(NUMBER_NO_RANGE_REGEXP)s)(?=($| |[\W]))"%locals()
756
 
NUMBER_FINDER = re.compile(NUMBER_FINDER_REGEXP,re.UNICODE)
757
 
 
758
 
# Note: the order matters on this range regular expression in order
759
 
# for it to properly split things like 1 - to - 3, which really do
760
 
# show up sometimes.
761
 
RANGE_REGEXP = '([ -]*%s[ -]*|\s*-\s*)'%_('to') # for 'to' used in a range, as in 3-4
762
 
RANGE_MATCHER = re.compile(RANGE_REGEXP[1:-1]) # no parens for this one
763
 
 
764
 
 
765
 
# We need a special matcher to match known units when they are more
766
 
# than one word. The assumption remains that units should be one word
767
 
# -- but if we already know about two word units, then we should
768
 
# recognize them.
769
 
 
770
 
multi_word_units = []
771
 
for canonical_name,other_names in defaults.UNITS:
772
 
    if ' ' in canonical_name: multi_word_units.append(canonical_name)
773
 
    for n in other_names:
774
 
        if ' ' in n: multi_word_units.append(n)
775
 
MULTI_WORD_UNIT_REGEXP = '(' + \
776
 
                       '|'.join([re.escape(unicode(u)) for u in multi_word_units]) \
777
 
                       + ')'
778
 
 
779
 
 
780
 
# generic ingredient matcher. This is far from a good matcher -- it's
781
 
# used as a fallback to test for things that obviously look like
782
 
# ingredients (e.g. 1 cup milk) that get misparsed by other ingredient
783
 
# parsers. This is often necessary because formats like mealmaster and
784
 
# mastercook are rarely actually followed.
785
 
NUMBER_FINDER_REGEXP2 = NUMBER_FINDER_REGEXP.replace('int','int2').replace('frac','frac2')
786
 
 
787
 
try:
788
 
    ING_MATCHER_REGEXP = """
789
 
 \s* # opening whitespace
790
 
 (?P<amount>
791
 
 %(NUMBER_FINDER_REGEXP)s # a number
792
 
 \s* # Extra whitespace
793
 
 (%(RANGE_REGEXP)s # a possible range delimiter
794
 
 \s* #More extra whitespace
795
 
 %(NUMBER_FINDER_REGEXP2)s)? # and more numbers
796
 
 )? # and of course no number is possible
797
 
 \s* # Whitespace between number and unit
798
 
 (?P<unit>\s*(%(MULTI_WORD_UNIT_REGEXP)s|[\w.]+))?\s+ # a unit
799
 
 (?P<item>.*?)$ # and the rest of our stuff...
800
 
 """
801
 
    ING_MATCHER_REGEXP = ING_MATCHER_REGEXP%locals()
802
 
except:
803
 
    print 'Failure with local vars...'
804
 
    for s in ['NUMBER_FINDER_REGEXP',
805
 
              'NUMBER_FINDER_REGEXP2',
806
 
              'RANGE_REGEXP',
807
 
              'MULTI_WORD_UNIT_REGEXP',]:
808
 
        try: print 'DOUBLE CHECK',s,'%%(%s)s'%s%locals()
809
 
        except:
810
 
            print 'Failed with ',s,locals()[s]
811
 
    raise
812
 
 
813
 
ING_MATCHER = re.compile(ING_MATCHER_REGEXP,
814
 
                         re.VERBOSE|re.UNICODE)
815
 
 
816
 
ING_MATCHER_AMT_GROUP = 'amount'
817
 
ING_MATCHER_UNIT_GROUP = 'unit'
818
 
ING_MATCHER_ITEM_GROUP = 'item'
819
 
 
820
 
def convert_fractions_to_ascii (s):
821
 
    """Convert all unicode-like fractions in string S with their ASCII equivalents"""
822
 
    for nums,uni in NUM_TO_FRACTIONS.items():
823
 
        s=re.sub(uni,"%s/%s"%(nums[0],nums[1]),s)
824
 
    for d in SUB_DICT,SUP_DICT:
825
 
        for num,uni in d.items():
826
 
            s=re.sub(uni,str(num),s)
827
 
    s=re.sub(SLASH,'/',s)
828
 
    return s
829
 
 
830
 
def fractify (decimal, divisor, approx=0.01, fractions=FRACTIONS_NORMAL):
831
 
    """Return fraction equivalent of decimal using divisor
832
 
 
833
 
    If we don't have a fit within our approximation, return the
834
 
    fraction. Otherwise, return False.
835
 
    """
836
 
    dividend = integerp(decimal*divisor)
837
 
    if dividend:
838
 
        if fractions==FRACTIONS_ASCII:
839
 
            return "%s/%s"%(dividend,divisor)
840
 
        elif fractions==FRACTIONS_ALL:
841
 
            # otherwise, we have to do nice unicode magic
842
 
            if NUM_TO_FRACTIONS.has_key((dividend,divisor)):
843
 
                return NUM_TO_FRACTIONS[(dividend,divisor)]
844
 
            else:
845
 
                if SUP_DICT.has_key(dividend): dividend = SUP_DICT[dividend]
846
 
                if SUB_DICT.has_key(divisor): divisor = SUB_DICT[divisor]
847
 
                return '%s%s%s'%(dividend,SLASH,divisor)
848
 
        else: # fractions==FRACTIONS_NORMAL
849
 
            #fallback to "normal" fractions -- 1/4, 1/2, 3/4 are special
850
 
            if (dividend,divisor) in NORMAL_FRACTIONS:
851
 
                return NUM_TO_FRACTIONS[(dividend,divisor)]
852
 
            else:
853
 
                return "%s/%s"%(dividend,divisor)
854
 
            
855
 
def float_to_frac (n, d=[2,3,4,5,6,8,10,16],approx=0.01,fractions=FRACTIONS_NORMAL):
856
 
    """Take a number -- or anything that can become a float --
857
 
    and attempt to return a fraction with a denominator in the list `d'. We
858
 
    approximate fractions to within approx. i.e. if approx=0.01, then 0.331=1/3"""
859
 
    if USE_FRACTIONS == FRACTIONS_OFF:
860
 
        return float_to_metric(n,approx)
861
 
    else:
862
 
        if not n: return ""
863
 
        n=float(n)
864
 
        i = int(n)
865
 
        if i >= 1:
866
 
            i="%s"%int(n)
867
 
        else:
868
 
            i=""
869
 
        rem = n - int(n)
870
 
        if rem==0 or rem<approx:
871
 
            if i:
872
 
                return "%i"%round(n)
873
 
            else:
874
 
                return "0"
875
 
        else:
876
 
            flag = False
877
 
            for div in d:
878
 
                f = fractify(rem,div,approx=approx,fractions=fractions)
879
 
                if f:
880
 
                    return " ".join([i,f]).strip()
881
 
             # use locale-specific metric formatting if fractions don't work
882
 
            return float_to_metric(n,approx)
883
 
 
884
 
def float_to_metric(n, approx=0.01):
885
 
    """Returns a formatted string in metric format, using locale-specific formatting"""    
886
 
    decimals_to_preserve = int(round(math.log(float(1)/approx,10)))
887
 
    if decimals_to_preserve > 0:
888
 
        format_string = "%."+str(decimals_to_preserve)+"f"
889
 
    else:
890
 
        format_string = "%i"
891
 
    if int(n) != n:
892
 
        if (n - int(n) < approx) or ((n - int(n) + approx) > 1):
893
 
            rounded = round(n)
894
 
            if rounded == 0:
895
 
                return float_to_metric(n,approx*.01)
896
 
            return locale.format("%i",int(rounded),True)
897
 
        else:
898
 
            rounded = round(n,decimals_to_preserve)
899
 
            if rounded == 0:
900
 
                return float_to_metric(n,approx*.01)
901
 
            return locale.format("%."+str(decimals_to_preserve)+"f",rounded,True) # format(formatstring, number, use_thousands_separator)
902
 
    else:
903
 
        return locale.format("%i",n,True)
904
 
    
905
 
def float_string (s):
906
 
    """Convert string to a float, assuming it is some sort of decimal number
907
 
 
908
 
    locale.atof should handle this, but we wrote our own to be a bit more flexible.
909
 
    Specifically, we assume most numbers are decimals since recipes calling for
910
 
    thousands and thousands of things are rare.
911
 
    Also, we recognize items outside of our locale, since e.g. American might well be
912
 
    importing British recipes and viceversa.
913
 
    """
914
 
    if NUMBER_WORDS.has_key(s.lower()):
915
 
        print 'We have key',s.lower()
916
 
        return NUMBER_WORDS[s.lower()]
917
 
    THOUSEP = locale.localeconv()['thousands_sep']
918
 
    DECSEP = locale.localeconv()['decimal_point']
919
 
    if s.count(',') > 1 and s.count('.') <= 1:
920
 
        # if we have more than one comma and less than one .
921
 
        # then we assume ,s are thousand-separators
922
 
        s=s.replace(',','')
923
 
        return float(s)
924
 
    elif s.count(',') <= 1 and s.count('.') > 1:
925
 
        # if we have more than one . and less than one ,,
926
 
        # then we assume . is the thousand-separators
927
 
        s=s.replace('.','')
928
 
        s=s.replace(',','.')
929
 
        return float(s)
930
 
    # otherwise let's check if this actually looks like a thousands separator
931
 
    # before trusting our locale
932
 
    elif re.search('[0-9]+%s[0-9][0-9][0-9]'%re.escape(THOUSEP),s):
933
 
        return locale.atof(s)
934
 
    elif THOUSEP and s.find(THOUSEP)>-1:
935
 
        # otherwise, perhaps our thousand separator is really a
936
 
        # decimal separator (we're out of our locale...)
937
 
        print 'Warning: assuming %s is a decimal point in %s'%(THOUSEP,s)
938
 
        s = s.replace(DECSEP,'!!!')
939
 
        s = s.replace(THOUSEP,DECSEP)
940
 
        s = s.replace('!!!',THOUSEP) # and remove any commas for good measure
941
 
        return locale.atof(s)
942
 
    else:
943
 
        # otherwise just trust our locale float
944
 
        return locale.atof(s)
945
 
 
946
 
def frac_to_float (s):
947
 
    """We assume fractions look like this (I )?N/D"""
948
 
    if NUMBER_WORDS.has_key(s): return NUMBER_WORDS[s]
949
 
    if hasattr(s,'lower') and NUMBER_WORDS.has_key(s.lower()):
950
 
        return NUMBER_WORDS[s.lower()]    
951
 
    s = unicode(s)
952
 
    m=FRACTION_MATCHER.match(s)
953
 
    if m:
954
 
        i = m.group('int')
955
 
        frac = m.group('frac')
956
 
        if i: i=float_string(i)
957
 
        else: i = 0
958
 
        if UNICODE_FRACTIONS.has_key(frac):
959
 
            return i+UNICODE_FRACTIONS[frac]
960
 
        elif NUMBER_WORDS.has_key(frac):
961
 
            return i+NUMBER_WORDS[frac]
962
 
        else:
963
 
            n,d = SLASH_MATCHER.split(frac)
964
 
            n = SUP_DICT.get(n,n)
965
 
            d = SUB_DICT.get(d,d)
966
 
            return float(i)+(float(n)/float(d))
967
 
    # else...
968
 
    try:
969
 
        return float_string(s)
970
 
    except:
971
 
        None
972
 
    else:
973
 
        #If this isn't a fraction, we're just using float
974
 
        try:
975
 
            return float_string(s)
976
 
        except:
977
 
            None
978
 
 
979
 
#class ConverterSingleton:
980
 
#    
981
 
#    __impl == Converter
982
 
#
983
 
#    def __init__ (
984
 
 
985
 
 
986
 
if __name__ == '__main__' and False:
987
 
    class InteractiveConverter:
988
 
        def __init__ (self):
989
 
            self.c = get_converter()
990
 
            self.options = {'Convert':self.converter,
991
 
                            'Add':self.adder,
992
 
                            'Adjust':self.adjuster,
993
 
                            'Quit':self.quit}
994
 
            while 1:
995
 
                self.offer_options()
996
 
            
997
 
        def offer_options (self):
998
 
            print 'Choose one of the following actions:'
999
 
            for k in self.options.keys(): print k
1000
 
            choice = raw_input('Type your choice: ')
1001
 
            if self.options.has_key(choice):
1002
 
                self.options[choice]()
1003
 
            else:
1004
 
                print "I'm afraid I didn't understand your choice!"
1005
 
                self.return_to_continue()
1006
 
                self.offer_options()
1007
 
                          
1008
 
        def get_unit (self, prompt="Enter unit: "):
1009
 
            u = raw_input(prompt).strip()
1010
 
            if self.c.unit_dict.has_key(u):
1011
 
                return u
1012
 
            elif u=='list':
1013
 
                for u in self.c.unit_dict.keys():
1014
 
                    print u,", ",
1015
 
                print ""
1016
 
                return self.get_unit(prompt)
1017
 
            else:
1018
 
                print u, 'Is not a unit I know about! Please try again.'
1019
 
                print '(Type "list" for a list of valid units)'
1020
 
                return self.get_unit(prompt)
1021
 
 
1022
 
        def get_amount (self, prompt="Enter amount: "):
1023
 
            amt = frac_to_float(raw_input(prompt))
1024
 
            if not amt:
1025
 
                print "Please enter an amount!"
1026
 
                return self.get_amount(prompt)
1027
 
            else:
1028
 
                return amt
1029
 
                
1030
 
        def converter (self):
1031
 
            u1 = self.get_unit("Enter source unit: ")
1032
 
            amt = self.get_amount("Enter source amount: ")
1033
 
            u2 = self.get_unit("Enter target unit: ")
1034
 
            conv = self.c.converter(u1,u2)
1035
 
            print '%s %s = %s %s'%(amt,u1,conv*amt,u2)
1036
 
            self.return_to_continue()
1037
 
 
1038
 
        def adjuster (self):
1039
 
            u1 = self.get_unit('Original unit: ')
1040
 
            a1 = self.get_amount('Original amount: ')
1041
 
            a,u = self.c.adjust_unit(a1,u1)
1042
 
            print 'Most readable unit = %s %s'%(a,u)
1043
 
 
1044
 
        def return_to_continue (self):
1045
 
            print 'Enter return to continue: '
1046
 
            raw_input()
1047
 
 
1048
 
        def adder (self):
1049
 
            u1 = self.get_unit('Enter unit 1: ')
1050
 
            a1 = self.get_amount("Enter amount 1: ")
1051
 
            u2 = self.get_unit('Enter unit 2: ')
1052
 
            a2 = self.get_amount('Enter amount 2: ')
1053
 
            result = self.c.add_reasonably(a1,u1,a2,u2)
1054
 
            if result:
1055
 
                print "%s %s + %s %s = %s"%(u1,a1,u2,a2,result)
1056
 
            else:
1057
 
                print "I'm sorry, I couldn't add that together!"
1058
 
            self.return_to_continue()
1059
 
            
1060
 
        def quit (self):
1061
 
            import sys
1062
 
            sys.exit()
1063
 
        
1064
 
    
1065
 
    #i=InteractiveConverter()