mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-27 05:09:23 +01:00
*** empty log message ***
This commit is contained in:
parent
03e3ad47a0
commit
114f9b3215
487
others/convertcore.py
Executable file
487
others/convertcore.py
Executable file
@ -0,0 +1,487 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
|
||||
#****************************************************************************
|
||||
# This file has been modified from its original version. It has been
|
||||
# formatted to fit your irc bot.
|
||||
#
|
||||
# Below is the original copyright. Doug Bell rocks.
|
||||
# The hijacker is Keith Jones, and he has no bomb in his shoe.
|
||||
#
|
||||
#****************************************************************************
|
||||
|
||||
#****************************************************************************
|
||||
# convertcore.py, provides non-GUI base classes for data
|
||||
#
|
||||
# ConvertAll, a units conversion program
|
||||
# Copyright (C) 2002, Douglas W. Bell
|
||||
#
|
||||
# This is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the GNU General Public License, Version 2. This program is
|
||||
# distributed in the hope that it will be useful, but WITTHOUT ANY WARRANTY.
|
||||
#*****************************************************************************
|
||||
|
||||
#from option import Option
|
||||
import re, copy, sys, os.path
|
||||
import registry
|
||||
import conf
|
||||
#from math import *
|
||||
|
||||
class UnitGroup:
|
||||
"Stores, updates and converts a group of units"
|
||||
maxDecPlcs = 12
|
||||
def __init__(self, unitData, option):
|
||||
self.unitData = unitData
|
||||
self.option = option
|
||||
self.unitList = []
|
||||
self.currentNum = 0
|
||||
self.factor = 1.0
|
||||
self.reducedList = []
|
||||
self.linear = 1
|
||||
|
||||
def update(self, text, cursorPos=None):
|
||||
"Decode user entered text into units"
|
||||
self.unitList = self.parseGroup(text)
|
||||
if cursorPos != None:
|
||||
self.updateCurrentUnit(text, cursorPos)
|
||||
else:
|
||||
self.currentNum = len(self.unitList) - 1
|
||||
|
||||
def updateCurrentUnit(self, text, cursorPos):
|
||||
"Set current unit number"
|
||||
self.currentNum = len(re.findall('[\*/]', text[:cursorPos]))
|
||||
|
||||
def currentUnit(self):
|
||||
"Return current unit if its a full match, o/w None"
|
||||
if self.unitList and self.unitList[self.currentNum].equiv:
|
||||
return self.unitList[self.currentNum]
|
||||
return None
|
||||
|
||||
def currentPartialUnit(self):
|
||||
"Return unit with at least a partial match, o/w None"
|
||||
if not self.unitList:
|
||||
return None
|
||||
return self.unitData.findPartialMatch(self.unitList[self.currentNum]\
|
||||
.name)
|
||||
|
||||
def currentSortPos(self):
|
||||
"Return unit near current unit for sorting"
|
||||
if not self.unitList:
|
||||
return self.unitData[self.unitData.sortedKeys[0]]
|
||||
return self.unitData.findSortPos(self.unitList[self.currentNum]\
|
||||
.name)
|
||||
|
||||
def replaceCurrent(self, unit):
|
||||
"Replace the current unit with unit"
|
||||
if self.unitList:
|
||||
exp = self.unitList[self.currentNum].exp
|
||||
self.unitList[self.currentNum] = copy.copy(unit)
|
||||
self.unitList[self.currentNum].exp = exp
|
||||
else:
|
||||
self.unitList.append(copy.copy(unit))
|
||||
|
||||
def completePartial(self):
|
||||
"Replace a partial unit with a full one"
|
||||
if self.unitList and not self.unitList[self.currentNum].equiv:
|
||||
text = self.unitList[self.currentNum].name
|
||||
unit = self.unitData.findPartialMatch(text)
|
||||
if unit:
|
||||
exp = self.unitList[self.currentNum].exp
|
||||
self.unitList[self.currentNum] = copy.copy(unit)
|
||||
self.unitList[self.currentNum].exp = exp
|
||||
|
||||
def moveToNext(self, upward):
|
||||
"Replace unit with adjacent one based on match or sort position"
|
||||
unit = self.currentSortPos()
|
||||
num = self.unitData.sortedKeys.index(unit.name.\
|
||||
replace(' ', '')) \
|
||||
+ (upward and -1 or 1)
|
||||
if 0 <= num < len(self.unitData.sortedKeys):
|
||||
self.replaceCurrent(self.unitData[self.unitData.sortedKeys[num]])
|
||||
|
||||
def addOper(self, mult):
|
||||
"Add new operator & blank unit after current, * if mult is true"
|
||||
if self.unitList:
|
||||
self.completePartial()
|
||||
prevExp = self.unitList[self.currentNum].exp
|
||||
self.currentNum += 1
|
||||
self.unitList.insert(self.currentNum, Unit(''))
|
||||
if (not mult and prevExp > 0) or (mult and prevExp < 0):
|
||||
self.unitList[self.currentNum].exp = -1
|
||||
|
||||
def changeExp(self, newExp):
|
||||
"Change the current unit's exponent"
|
||||
if self.unitList:
|
||||
self.completePartial()
|
||||
if self.unitList[self.currentNum].exp > 0:
|
||||
self.unitList[self.currentNum].exp = newExp
|
||||
else:
|
||||
self.unitList[self.currentNum].exp = -newExp
|
||||
|
||||
def clearUnit(self):
|
||||
"Remove units"
|
||||
self.unitList = []
|
||||
|
||||
def parseGroup(self, text):
|
||||
"Return list of units from text string"
|
||||
unitList = []
|
||||
parts = [part.strip() for part in re.split('([\*/])', text)]
|
||||
numerator = 1
|
||||
while parts:
|
||||
unit = self.parseUnit(parts.pop(0))
|
||||
if not numerator:
|
||||
unit.exp = -unit.exp
|
||||
if parts and parts.pop(0) == '/':
|
||||
numerator = not numerator
|
||||
unitList.append(unit)
|
||||
return unitList
|
||||
|
||||
def parseUnit(self, text):
|
||||
"Return a valid or invalid unit with exponent from a text string"
|
||||
parts = text.split('^', 1)
|
||||
exp = 1
|
||||
if len(parts) > 1: # has exponent
|
||||
try:
|
||||
exp = int(parts[1])
|
||||
except ValueError:
|
||||
if parts[1].lstrip().startswith('-'):
|
||||
exp = -Unit.partialExp # tmp invalid exp
|
||||
else:
|
||||
exp = Unit.partialExp
|
||||
unitText = parts[0].strip().replace(' ', '')
|
||||
unit = copy.copy(self.unitData.get(unitText, None))
|
||||
if not unit and unitText and unitText[-1] == 's' and not \
|
||||
self.unitData.findPartialMatch(unitText): # check for plural
|
||||
unit = copy.copy(self.unitData.get(unitText[:-1], None))
|
||||
if not unit:
|
||||
#unit = Unit(parts[0].strip()) # tmp invalid unit
|
||||
raise UnitDataError('%s is not a valid unit.' % (unitText))
|
||||
unit.exp = exp
|
||||
return unit
|
||||
|
||||
def unitString(self, unitList=None):
|
||||
"Return the full string for this group or a given group"
|
||||
if unitList == None:
|
||||
unitList = self.unitList[:]
|
||||
fullText = ''
|
||||
if unitList:
|
||||
fullText = unitList[0].unitText(0)
|
||||
numerator = 1
|
||||
for unit in unitList[1:]:
|
||||
if (numerator and unit.exp > 0) \
|
||||
or (not numerator and unit.exp < 0):
|
||||
fullText = '%s * %s' % (fullText, unit.unitText(1))
|
||||
else:
|
||||
fullText = '%s / %s' % (fullText, unit.unitText(1))
|
||||
numerator = not numerator
|
||||
return fullText
|
||||
|
||||
def groupValid(self):
|
||||
"Return 1 if all unitself.reducedLists are valid"
|
||||
if not self.unitList:
|
||||
return 0
|
||||
for unit in self.unitList:
|
||||
if not unit.unitValid():
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def reduceGroup(self):
|
||||
"Update reduced list of units and factor"
|
||||
self.linear = 1
|
||||
self.reducedList = []
|
||||
self.factor = 1.0
|
||||
if not self.groupValid():
|
||||
return
|
||||
count = 0
|
||||
tmpList = self.unitList[:]
|
||||
while tmpList:
|
||||
count += 1
|
||||
if count > 5000:
|
||||
raise UnitDataError, 'Circular unit definition'
|
||||
unit = tmpList.pop(0)
|
||||
if unit.equiv == '!':
|
||||
self.reducedList.append(copy.copy(unit))
|
||||
elif not unit.equiv:
|
||||
raise UnitDataError, 'Invalid conversion for "%s"' % unit.name
|
||||
else:
|
||||
if unit.fromEqn:
|
||||
self.linear = 0
|
||||
newList = self.parseGroup(unit.equiv)
|
||||
for newUnit in newList:
|
||||
newUnit.exp *= unit.exp
|
||||
tmpList.extend(newList)
|
||||
self.factor *= unit.factor**unit.exp
|
||||
self.reducedList.sort()
|
||||
tmpList = self.reducedList[:]
|
||||
self.reducedList = []
|
||||
for unit in tmpList:
|
||||
if self.reducedList and unit == self.reducedList[-1]:
|
||||
self.reducedList[-1].exp += unit.exp
|
||||
else:
|
||||
self.reducedList.append(unit)
|
||||
self.reducedList = [unit for unit in self.reducedList if \
|
||||
unit.name != 'unit' and unit.exp != 0]
|
||||
|
||||
def categoryMatch(self, otherGroup):
|
||||
"Return 1 if unit types are equivalent"
|
||||
if not self.checkLinear() or not otherGroup.checkLinear():
|
||||
return 0
|
||||
return self.reducedList == otherGroup.reducedList and \
|
||||
[unit.exp for unit in self.reducedList] \
|
||||
== [unit.exp for unit in otherGroup.reducedList]
|
||||
|
||||
def checkLinear(self):
|
||||
"Return 1 if linear or acceptable non-linear"
|
||||
if not self.linear:
|
||||
if len(self.unitList) > 1 or self.unitList[0].exp != 1:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
def compatStr(self):
|
||||
"Return string with reduced unit or linear compatability problem"
|
||||
if self.checkLinear():
|
||||
return self.unitString(self.reducedList)
|
||||
return 'Cannot combine non-linear units'
|
||||
|
||||
def convert(self, num, toGroup):
|
||||
"Return num of this group converted to toGroup"
|
||||
if self.linear:
|
||||
num *= self.factor
|
||||
else:
|
||||
num = self.nonLinearCalc(num, 1) * self.factor
|
||||
if toGroup.linear:
|
||||
return num / toGroup.factor
|
||||
return toGroup.nonLinearCalc(num / toGroup.factor, 0)
|
||||
|
||||
def nonLinearCalc(self, num, isFrom):
|
||||
"Return result of non-linear calculation"
|
||||
x = num
|
||||
try:
|
||||
if self.unitList[0].toEqn: # regular equations
|
||||
if isFrom:
|
||||
return float(eval(self.unitList[0].fromEqn))
|
||||
return float(eval(self.unitList[0].toEqn))
|
||||
data = list(eval(self.unitList[0].fromEqn)) # extrapolation list
|
||||
if isFrom:
|
||||
data = [(float(group[0]), float(group[1])) for group in data]
|
||||
else:
|
||||
data = [(float(group[1]), float(group[0])) for group in data]
|
||||
data.sort()
|
||||
pos = len(data) - 1
|
||||
for i in range(len(data)):
|
||||
if num <= data[i][0]:
|
||||
pos = i
|
||||
break
|
||||
if pos == 0:
|
||||
pos = 1
|
||||
return (num-data[pos-1][0]) / float(data[pos][0]-data[pos-1][0]) \
|
||||
* (data[pos][1]-data[pos-1][1]) + data[pos-1][1]
|
||||
except OverflowError:
|
||||
return 1e9999
|
||||
except:
|
||||
raise UnitDataError, 'Bad equation for %s' % self.unitList[0].name
|
||||
|
||||
def convertStr(self, num, toGroup):
|
||||
"Return formatted string of converted number"
|
||||
return self.formatNumStr(self.convert(num, toGroup))
|
||||
|
||||
def formatNumStr(self, num):
|
||||
"Return num string formatted per options"
|
||||
decPlcs = self.option.intData('DecimalPlaces', 0, UnitGroup.maxDecPlcs)
|
||||
if self.option.boolData('SciNotation'):
|
||||
return ('%%0.%dE' % decPlcs) % num
|
||||
if self.option.boolData('FixedDecimals'):
|
||||
return ('%%0.%df' % decPlcs) % num
|
||||
return ('%%0.%dG' % decPlcs) % num
|
||||
|
||||
|
||||
class UnitDataError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnitData(dict):
|
||||
def __init__(self):
|
||||
dict.__init__(self)
|
||||
self.sortedKeys = []
|
||||
|
||||
def readData(self):
|
||||
"Read all unit data from file"
|
||||
types = []
|
||||
typeUnits = {}
|
||||
try:
|
||||
f = file(os.path.join(conf.supybot.directories.data(), \
|
||||
'units.dat'), 'r')
|
||||
lines = f.readlines()
|
||||
f.close()
|
||||
except IOError:
|
||||
raise UnitDataError, 'Can not read "units.dat" file'
|
||||
for i in range(len(lines)): # join continuation lines
|
||||
delta = 1
|
||||
while lines[i].rstrip().endswith('\\'):
|
||||
lines[i] = ''.join([lines[i].rstrip()[:-1], lines[i+delta]])
|
||||
lines[i+delta] = ''
|
||||
delta += 1
|
||||
units = [Unit(line) for line in lines if \
|
||||
line.split('#', 1)[0].strip()] # remove comment lines
|
||||
typeText = ''
|
||||
for unit in units: # find & set headings
|
||||
if unit.name.startswith('['):
|
||||
typeText = unit.name[1:-1].strip()
|
||||
types.append(typeText)
|
||||
typeUnits[typeText] = []
|
||||
unit.typeName = typeText
|
||||
units = [unit for unit in units if unit.equiv] # keep valid units
|
||||
for unit in units:
|
||||
self[unit.name.replace(' ', '')] = unit
|
||||
typeUnits[unit.typeName].append(unit.name)
|
||||
self.sortedKeys = self.keys()
|
||||
self.sortedKeys.sort()
|
||||
print len(units), 'units loaded'
|
||||
if len(self.sortedKeys) < len(units):
|
||||
raise UnitDataError, 'Duplicate unit names found'
|
||||
|
||||
return (types, typeUnits)
|
||||
|
||||
def findPartialMatch(self, text):
|
||||
"Return first partially matching unit or None"
|
||||
text = text.replace(' ', '')
|
||||
if not text:
|
||||
return None
|
||||
for name in self.sortedKeys:
|
||||
if name.startswith(text):
|
||||
return self[name]
|
||||
return None
|
||||
|
||||
def findSortPos(self, text):
|
||||
"Return unit whose abbrev comes immediately after text"
|
||||
text = text.replace(' ', '')
|
||||
for name in self.sortedKeys:
|
||||
if text <= name:
|
||||
return self[name]
|
||||
return self[self.sortedKeys[-1]]
|
||||
|
||||
|
||||
class Unit:
|
||||
"Reads and stores a single unit conversion"
|
||||
partialExp = 1000
|
||||
def __init__(self, dataStr):
|
||||
dataList = dataStr.split('#')
|
||||
unitList = dataList.pop(0).split('=', 1)
|
||||
self.name = unitList.pop(0).strip()
|
||||
self.equiv = ''
|
||||
self.factor = 1.0
|
||||
self.fromEqn = '' # used only for non-linear units
|
||||
self.toEqn = '' # used only for non-linear units
|
||||
if unitList:
|
||||
self.equiv = unitList[0].strip()
|
||||
if self.equiv[0] == '[': # used only for non-linear units
|
||||
try:
|
||||
self.equiv, self.fromEqn = re.match('\[(.*?)\](.*)', \
|
||||
self.equiv).groups()
|
||||
if ';' in self.fromEqn:
|
||||
self.fromEqn, self.toEqn = self.fromEqn.split(';', 1)
|
||||
self.toEqn = self.toEqn.strip()
|
||||
self.fromEqn = self.fromEqn.strip()
|
||||
except AttributeError:
|
||||
raise UnitDataError, 'Bad equation for "%s"' % self.name
|
||||
else: # split factor and equiv unit for linear
|
||||
parts = self.equiv.split(None, 1)
|
||||
if len(parts) > 1 and re.search('[^\d\.eE\+\-\*/]', parts[0]) \
|
||||
== None: # only allowed digits and operators
|
||||
try:
|
||||
self.factor = float(eval(parts[0]))
|
||||
self.equiv = parts[1]
|
||||
except:
|
||||
pass
|
||||
self.comments = [comm.strip() for comm in dataList]
|
||||
self.comments.extend([''] * (2 - len(self.comments)))
|
||||
self.exp = 1
|
||||
self.viewLink = [None, None]
|
||||
self.typeName = ''
|
||||
|
||||
def description(self):
|
||||
"Return name and 1st comment (usu. full name) if applicable"
|
||||
if self.comments[0]:
|
||||
return '%s (%s)' % (self.name, self.comments[0])
|
||||
return self.name
|
||||
|
||||
def unitValid(self):
|
||||
"Return 1 if unit and exponent are valid"
|
||||
if self.equiv and -Unit.partialExp < self.exp < Unit.partialExp:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def unitText(self, absExp=0):
|
||||
"Return text for unit name with exponent or absolute value of exp"
|
||||
exp = self.exp
|
||||
if absExp:
|
||||
exp = abs(self.exp)
|
||||
if exp == 1:
|
||||
return self.name
|
||||
if -Unit.partialExp < exp < Unit.partialExp:
|
||||
return '%s^%d' % (self.name, exp)
|
||||
if exp > 1:
|
||||
return '%s^' % self.name
|
||||
else:
|
||||
return '%s^-' % self.name
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.name, other.name)
|
||||
|
||||
############################################################################
|
||||
# Wrapper functionality
|
||||
#
|
||||
############################################################################
|
||||
|
||||
|
||||
# Parse the data file, and set everything up for conversion
|
||||
data = UnitData()
|
||||
(types, unitsByType) = data.readData()
|
||||
|
||||
# At the moment, we're not handling options
|
||||
option = None
|
||||
|
||||
# set up the objects for unit conversion
|
||||
fromUnit = UnitGroup(data, option)
|
||||
toUnit = UnitGroup(data, option)
|
||||
|
||||
def convert(num, unit1, unit2):
|
||||
""" Convert from one unit to another
|
||||
|
||||
num is the factor for the first unit. Raises UnitDataError for
|
||||
various errors.
|
||||
"""
|
||||
|
||||
global fromUnit
|
||||
global toUnit
|
||||
global types
|
||||
global unitsByType
|
||||
|
||||
fromUnit.update(unit1)
|
||||
toUnit.update(unit2)
|
||||
|
||||
fromUnit.reduceGroup()
|
||||
toUnit.reduceGroup()
|
||||
|
||||
# Match up unit categories
|
||||
if not fromUnit.categoryMatch(toUnit):
|
||||
raise UnitDataError('unit categories did not match')
|
||||
|
||||
return fromUnit.convert(num, toUnit)
|
||||
|
||||
|
||||
|
||||
def units(type):
|
||||
""" Return comma separated string list of units of given type, or
|
||||
a list of types if the argument is not valid.
|
||||
"""
|
||||
|
||||
global types
|
||||
global unitsByType
|
||||
|
||||
if type in types:
|
||||
return '%s units: %s' % (type, ', '.join(unitsByType[type]))
|
||||
else:
|
||||
return 'valid types: ' + ', '.join(types)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user