"""
Account is a program to generate a devcoin receiver file from a bitcoinshare, bounty, devcoinshare and peer file.
This is meant to be used by devcoin accountants and auditors to create and check the receiver files. The account file has a list of addresses and shares. Anything after a dash is a comment.
==Commands==
===Help===
The -h option, the -help option, will print the help, which is this document. The example follows:
python account.py -h
===Input===
Default is https://raw.github.com/Unthinkingbit/charity/master/account_3.csv
The -input option sets the input file name. The example follows:
python account.py -input https://raw.github.com/Unthinkingbit/charity/master/account_3.csv
An example of an account information input file is at:
https://raw.github.com/Unthinkingbit/charity/master/account_3.csv
===Output===
Default is test_receiver.csv
The -output option sets the output. If the output ends with stderr, the output will be sent to stderr If the output ends with stdout, the output will be sent to stdout. If the output does not end with stderr or stdout, the output will be written to a file by that name, with whatever suffix the input file has. The example follows:
python genereceiver.py -output test_receiver.csv
An example of an genereceiver output file is at:
https://raw.github.com/Unthinkingbit/charity/master/test_receiver_3.csv
==Install==
For genereceiver to run, you need Python 2.x, almoner will probably not run with python 3.x. To check if it is on your machine, in a terminal type:
python
If python 2.x is not on your machine, download the latest python 2.x, which is available from:
http://www.python.org/download/
"""
import almoner
import base58
import cStringIO
import hashlib
import math
import sys
__license__ = 'MIT'
globalGoldenRatio = math.sqrt(1.25) + 0.5
def addAdministratorBonus(accountLines):
'Add the administrator bonus, up to a maximum of 15%.'
originalReceiverLines = getReceiverLinesByAccountLines(accountLines)
originalNumberOfLinesFloat = float(len(originalReceiverLines))
administrators = []
for accountLine in accountLines:
if 'Administrator' in accountLine:
administrators.append(Administrator(accountLine))
administratorPay = 0.0
generalAdministrators = []
factotumCount = 0
for administrator in administrators:
administratorPay += administrator.pay
if administrator.isGeneralAdministrator:
generalAdministrators.append(administrator)
if administrator.isFactotum:
factotumCount += 1
for bonusMultiplier in xrange(7, 0, -1):
bonusPay = bonusMultiplier * float(len(generalAdministrators) + factotumCount)
totalAdministratorPay = bonusPay + administratorPay
totalShares = originalNumberOfLinesFloat + bonusPay
percentPay = 0.1 * round(1000.0 * totalAdministratorPay / totalShares)
if percentPay < 15.0:
accountLines.append('Administrator Bonus' + ': %s Shares' % int(round(bonusPay)))
for generalAdministrator in generalAdministrators:
accountLines.append(generalAdministrator.getAccountLine(bonusMultiplier))
accountLines.append('')
return
def addReceiverLines(coinAddresses, receiverLines):
'Get the receiver format line.'
if len(coinAddresses) == 0:
return
addressQuantityDictionary = getQuantityDictionary(coinAddresses)
firstQuantity = addressQuantityDictionary.values()[0]
for addressQuantity in addressQuantityDictionary.values():
if addressQuantity != firstQuantity:
receiverLines.append(','.join(coinAddresses))
return
receiverLines.append(','.join(addressQuantityDictionary.keys()))
def carryCoinAddresses(denominatorSequences):
"""
Sometimes there number of identical coin addresses in the denominator sequence is as high as the denominator. To condense the receiver file,
if the number of copies of the coin address is as high as the denominator, coin addresses are carried to the sequence with the smaller
denominator.
To do this, the sequences to be carried into, which are sorted in ascending value of the denominator, are iterated downward from
the length minus two, to zero. The quantity of each coin address in the sequence with the big denominator is entered into a
dictionary. For each coin address, the carry is calculated by dividing quantity of the coin address by the denominator ratio between the
sequence with the big denominator and the denominator of the sequence to be carried into. The carry is then added to the sequence to be
carried into, and the carry times the denominator ratio is subtracted from the sequence with the big denominator. For example, if the
sequence has six copies of a coin address, and the denominator is five, and the sequence to be carried into has a denominator of one, the
denominator ratio is five, and the carry integer is int(6 / 5) = 1. So one coin address is added to the list of coin addresses in the
sequence to be carried into, and 1 * 5 = 5 is subtracted from the quantity of the coin address in the sequence with the big denominator.
After the quantities of each coin address are modified, the coin address lists in the sequence with the big denominator are generated from
the quantity dictionary.
"""
for denominatorSequenceIndex in xrange(len(denominatorSequences) - 2, -1, -1):
denominatorSequence = denominatorSequences[denominatorSequenceIndex]
denominatorSequenceBigDenominator = denominatorSequences[denominatorSequenceIndex + 1]
denominatorRatio = denominatorSequenceBigDenominator.denominator / denominatorSequence.denominator
bigDenominatorAddressDictionary = getQuantityDictionary(denominatorSequenceBigDenominator.coinAddresses)
for bigDenominatorAddressKey in bigDenominatorAddressDictionary:
bigDenominatorAddressValue = bigDenominatorAddressDictionary[bigDenominatorAddressKey]
carry = bigDenominatorAddressValue / denominatorRatio
if carry > 0:
bigDenominatorAddressDictionary[bigDenominatorAddressKey] -= carry * denominatorRatio
denominatorSequence.coinAddresses += [bigDenominatorAddressKey] * carry
denominatorSequenceBigDenominator.coinAddresses = []
for bigDenominatorAddressKey in bigDenominatorAddressDictionary:
bigDenominatorAddressValue = bigDenominatorAddressDictionary[bigDenominatorAddressKey]
denominatorSequenceBigDenominator.coinAddresses += [bigDenominatorAddressKey] * bigDenominatorAddressValue
def getAccountLines(arguments, suffixNumberString):
'Get the lines according to the arguments.'
linkFileName = almoner.getParameter(arguments, 'account_location.csv', 'location')
linkLines = almoner.getTextLines(almoner.getLocationText(linkFileName))[1 :]
accountLines = ['']
nameSet = set([])
for linkLine in linkLines:
linkLineSplit = linkLine.split(',')
name = linkLineSplit[0]
location = linkLineSplit[1]
extraLines = []
if '_xx' in location:
location = location.replace('_xx', '_' + suffixNumberString)
locationText = almoner.getLocationText(location)
if '
404 Not Found' in locationText:
print('Warning, could not download page: %s' % location)
else:
extraLines = almoner.getTextLines(locationText.replace('coinzen.org/index.php/topic,', 'coinzen.org/index.php/topic='))
else:
extraLines = getNameAddressLines(location, nameSet)
for extraLineIndex in xrange(len(extraLines) - 1, -1, -1):
extraWords = extraLines[extraLineIndex].split(',')
if len(extraWords) < 3:
print('Warning, less than 3 words in:')
print(linkLine)
print(extraWords)
print('')
del extraLines[extraLineIndex]
else:
secondWord = extraWords[1]
if '-' in secondWord or '(' in secondWord or ':' in secondWord:
print('Coin address is invalid in:')
print(extraWords)
del extraLines[extraLineIndex]
numberOfShares = len(getReceiverLinesByAccountLines(extraLines))
accountLines.append(name + ': %s Shares' % numberOfShares)
accountLines += extraLines
accountLines.append('')
addAdministratorBonus(accountLines)
return accountLines
def getAddressDictionary(round):
'Get the address dictionary.'
addressDictionary = {}
for accountLine in getAccountLines([], str(round)):
accountLineSplit = accountLine.split(',')
if len(accountLineSplit) > 1:
name = accountLineSplit[0].strip()
if name != '':
addressDictionary[accountLineSplit[1]] = name
return addressDictionary
def getAddressFractions(lines):
'Get the AddressFractions by text.'
addressFractions = []
for line in lines:
addressFractions.append(AddressFraction(line))
return addressFractions
def getCutLines(cutLines, suffixNumber):
"""
The lines are cut at a different part of the list, so that a developer whose key starts with 1A does not get more on average over multiple
rounds than a developer whose key starts with 1Z. This is done by cutting the list at an index which is the golden ratio times the round
number, then modulo is used to keep it within the list bounds. It also reverses the list at every even round number, in case cutting is
not enough to average pay over multiple rounds.
"""
rotation = (float(suffixNumber) * globalGoldenRatio) % 1.0
rotationIndex = int(math.floor(rotation * float(len(cutLines))))
if suffixNumber % 2 == 0:
cutLines.reverse()
cutLines = cutLines[rotationIndex :] + cutLines[: rotationIndex]
return cutLines
def getDenominatorSequences(addressFractions):
'Get the DenominatorSequences from the addressFractions.'
denominators = []
for addressFraction in addressFractions:
for fraction in addressFraction.fractions:
if fraction.denominator not in denominators:
denominators.append(fraction.denominator)
denominators.sort()
denominatorSequences = []
for denominator in denominators:
denominatorSequence = DenominatorSequence(addressFractions, denominator)
denominatorSequence.coinAddresses = getShuffledElements(denominatorSequence.coinAddresses)
denominatorSequences.append(denominatorSequence)
return denominatorSequences
def getDenominatorSequencesByAccountLines(accountLines):
'Get the lines according to the arguments.'
addressFractions = getAddressFractions(accountLines)
denominatorSequences = getDenominatorSequences(addressFractions)
carryCoinAddresses(denominatorSequences)
return denominatorSequences
def getGroupedReceiverLines(denominatorMultiplier, denominatorSequences):
'Get grouped receiver lines.'
print('Receiver lines will be grouped by a factor of %s.' % denominatorMultiplier)
for denominatorSequence in denominatorSequences:
denominatorSequence.denominator *= denominatorMultiplier
return getReceiverLinesByDenominatorSequences(denominatorSequences)
def getNameAddressLines(fileName, nameSet):
'Get the name and address lines by the file name.'
if fileName == '':
return []
listName = 'Share List'
shareIndex = fileName.find('share')
if shareIndex > -1:
listName = fileName[: shareIndex].capitalize() + ' ' + listName
addressLines = []
for contributor in almoner.getContributors(fileName):
name = contributor.name
leftBracketIndex = name.find('[')
if leftBracketIndex >= 0:
name = name[leftBracketIndex + 1 :]
spaceIndex = name.find(' ')
if spaceIndex >= 0:
name = name[spaceIndex + 1 :]
rightBracketIndex = name.find(']')
if rightBracketIndex >= 0:
name = name[: rightBracketIndex]
dotIndex = name.find('.')
if dotIndex > 0:
name = name[: dotIndex]
lowerName = name.lower()
if lowerName not in nameSet:
linkName = 'https://raw.github.com/Unthinkingbit/charity/master/' + fileName
addressLine = '%s,%s,1-%s(%s)' % (name.replace(' ', '_'), contributor.bitcoinAddress, listName, linkName)
addressLines.append(addressLine)
nameSet.add(lowerName)
else:
print('Duplicate contributor, which will not be added a second time.')
print(name)
print('')
return addressLines
def getPackedReceiverLines(denominatorSequences, originalReceiverLines, suffixNumber):
"""
A devcoin round has 4,000 blocks, if there are more than 4,000 receiver lines, the lines after the four thousandth row will not get
generation devcoins. To get devcoins to every line, the receiver lines are packed by the denominatorMultiplier:
denominatorMultiplier = (len(originalReceiverLines) + maximumReceivers) / (maximumReceivers + 1 - len(denominatorSequences))
The denominator in each denominator sequence is multiplied by the denominatorMultiplier. When the receiver lines are then generated by the
denominator sequences, the column width of the line will be up to the denominator, in effect reducing the number of rows by the
denominatorMultiplier and increasing the number of columns by the denominatorMultiplier.
Because this changes the number of rows, the 'Average devcoins per share' is calculated from the original number of lines.
"""
maximumReceivers = 4000
originalReceiverLineLength = len(originalReceiverLines)
denominatorMultiplier = 1
if len(originalReceiverLines) > maximumReceivers:
denominatorMultiplier = (len(originalReceiverLines) + maximumReceivers) / (maximumReceivers + 1 - len(denominatorSequences))
originalReceiverLines = getGroupedReceiverLines(denominatorMultiplier, denominatorSequences)
if len(originalReceiverLines) > maximumReceivers:
print('Warning, denominatorMultiplier math is wrong, the receiver lines will be grouped by another factor of two.')
originalReceiverLines = getGroupedReceiverLines(2, denominatorSequences)
originalDevcoinBlocksPerShareFloat = float(maximumReceivers) / originalReceiverLineLength
averageDevcoinsPerShare = int(round(originalDevcoinBlocksPerShareFloat * 45000.0))
print('Average devcoins per share: %s' % almoner.getCommaNumberString(averageDevcoinsPerShare))
print('Number of original receiver lines lines: %s' % originalReceiverLineLength)
print('Number of receiver lines lines: %s' % len(originalReceiverLines))
print('')
return getCutLines(originalReceiverLines, suffixNumber)
def getPeerLines(arguments):
'Get the inner peer text according to the arguments.'
peerFileName = almoner.getParameter(arguments, 'peer.csv', 'inputpeer')
peerLines = almoner.getTextLines(almoner.getLocationText(peerFileName))
print('Number of peers: %s' % len(peerLines))
print('')
return peerLines
def getPluribusunumText(peerText, receiverLines):
'Get the pluribusunum text according to the arguments.'
return 'Format,pluribusunum\n%s_begincoins\n%s_endcoins\n' % (peerText, almoner.getTextByLines(receiverLines))
def getQuantityDictionary(elements):
'Get the quantity dictionary.'
quantityDictionary = {}
for element in elements:
if element in quantityDictionary:
quantityDictionary[element] += 1
else:
quantityDictionary[element] = 1
return quantityDictionary
def getReceiverLinesByAccountLines(accountLines):
'Write output.'
denominatorSequences = getDenominatorSequencesByAccountLines(accountLines)
return getReceiverLinesByDenominatorSequences(denominatorSequences)
def getReceiverLinesByDenominatorSequences(denominatorSequences):
'Concatenate the receiver lines from all the denominator sequences.'
receiverLines = []
for denominatorSequence in denominatorSequences:
receiverLines += denominatorSequence.getReceiverLines()
return receiverLines
def getRecipientDictionary(round):
'Get the recipient dictionary.'
recipientDictionary = {}
for accountLine in getAccountLines([], str(round)):
accountLineSplit = accountLine.split(',')
if len(accountLineSplit) > 1:
name = accountLineSplit[0].lower()
if name != '':
recipientDictionary[name] = accountLineSplit[1]
return recipientDictionary
def getShareListSet(round):
'Get the names of the developers on the share list set.'
isShareName = False
shareListSet = set([])
for accountLine in getAccountLines([], str(round)):
accountLineSplit = accountLine.split(',')
if len(accountLineSplit) == 0:
isShareName = False
elif len(accountLineSplit[0]) == 0:
isShareName = False
if isShareName:
shareListSet.add(accountLineSplit[0].lower())
if len(accountLineSplit) == 1:
if ' Share List: ' in accountLineSplit[0]:
isShareName = True
return shareListSet
def getShuffledElements(elements):
"""
Because the number of lines is usually not perfectly divisible into 4,000, the addresses of each developer are spread out, so that in each
round the amount that the developer receives is close to the average. This is done by inserting them at an index within the shuffledLines
list which is increased by the golden ratio, then modulo is used to keep it within the list bounds.
"""
shuffledElements = []
for element in elements:
shuffledLengthFloat = float(len(shuffledElements))
index = int(shuffledLengthFloat * ((shuffledLengthFloat * globalGoldenRatio) % 1.0))
shuffledElements.insert(min(index, len(shuffledElements) - 1), element)
return shuffledElements
def getSuffixNumber(fileName):
'Determine the round number, returning 0 if there is not one.'
underscoreIndex = fileName.rfind('_')
if underscoreIndex == -1:
return 0
afterUnderscore = fileName[underscoreIndex + 1 :]
dotIndex = afterUnderscore.rfind('.')
if dotIndex == -1:
return 0
afterUnderscore = afterUnderscore[: dotIndex]
if not afterUnderscore.isdigit():
return 0
return int(afterUnderscore)
def getSummaryText(accountLines, originalReceiverLines, peerLines, suffixNumber):
'Get the summary text.'
cString = cStringIO.StringIO()
suffixNumberPlusOne = suffixNumber + 1
numberOfLines = len(originalReceiverLines)
numberOfLinesFloat = float(numberOfLines)
administratorPay = 0.0
for accountLine in accountLines:
if 'Administrator' in accountLine:
administratorPay += Administrator(accountLine).pay
percentPay = 0.1 * round(1000.0 * administratorPay / numberOfLinesFloat)
cString.write('The round %s receiver files have been uploaded to:\n' % suffixNumber)
for peerLine in peerLines:
suffixedPeerLine = peerLine[: -len('.csv')] + ('_%s.csv' % suffixNumber)
cString.write('%s\n' % suffixedPeerLine)
cString.write('\nThe account file is at:\n')
cString.write('http://galaxies.mygamesonline.org/account_%s.csv\n' % suffixNumber)
devcoins = int(round(180000000.0 / numberOfLinesFloat))
linesCommaString = almoner.getCommaNumberString(numberOfLines)
cString.write('\nThere were %s original receiver lines, so the average number of devcoins per share is ' % linesCommaString)
cString.write('180,000,000 dvc / %s = %s dvc.' % (linesCommaString, almoner.getCommaNumberString(devcoins)))
cString.write(' Administrator pay is %s shares, %s percent of the total.\n' % (administratorPay, percentPay))
cString.write('\nPeople on that list will start getting those coins in round %s, starting at block %s,000.' % (suffixNumber, 4 * suffixNumber))
cString.write(' The procedure for generating the receiver files is at:\n')
cString.write('http://devtome.com/doku.php?id=devcoin#generating_the_files\n')
cString.write('\nThe next bounties will go into round %s:\n' % suffixNumberPlusOne)
cString.write('http://devticker.pw/business_bounty/business_bounty_%s.csv\n' % suffixNumberPlusOne)
cString.write('\nThe next ongoing payments will go into round %s:\n' % suffixNumberPlusOne)
cString.write('http://dvccountdown.blisteringdevelopers.com/ongoing_%s.csv\n' % suffixNumberPlusOne)
return cString.getvalue()
def writeOutput(arguments):
'Write output.'
if '-h' in arguments or '-help' in arguments:
print(__doc__)
return
suffixNumberString = almoner.getParameter(arguments, '24', 'round')
suffixNumber = int(suffixNumberString)
outputAccountTo = almoner.getSuffixedFileName(almoner.getParameter(arguments, 'account.csv', 'account'), suffixNumberString)
accountLines = getAccountLines(arguments, suffixNumberString)
peerLines = getPeerLines(arguments)
peerText = '_beginpeers\n%s_endpeers\n' % almoner.getTextByLines(peerLines)
accountText = getPluribusunumText(peerText, accountLines)
if almoner.sendOutputTo(outputAccountTo, accountText):
print('The account file has been written to:\n%s\n' % outputAccountTo)
outputReceiverTo = almoner.getSuffixedFileName(almoner.getParameter(arguments, 'receiver.csv', 'receiver'), suffixNumberString)
outputSummaryTo = almoner.getParameter(arguments, 'receiver_summary.txt', 'summary')
denominatorSequences = getDenominatorSequencesByAccountLines(accountLines)
originalReceiverLines = getReceiverLinesByDenominatorSequences(denominatorSequences)
receiverLines = getPackedReceiverLines(denominatorSequences, originalReceiverLines, suffixNumber)
receiverText = getPluribusunumText(peerText, receiverLines)
if almoner.sendOutputTo(outputReceiverTo, receiverText):
print('The receiver file has been written to:\n%s\n' % outputReceiverTo)
shaOutputPrefix = almoner.getParameter(arguments, '', 'sha')
if len(shaOutputPrefix) != 0:
sha256FileName = almoner.getSuffixedFileName(outputReceiverTo, shaOutputPrefix)
almoner.writeFileText(sha256FileName, hashlib.sha256(receiverText).hexdigest())
print('The sha256 receiver file has been written to:\n%s\n' % sha256FileName)
if almoner.sendOutputTo(outputSummaryTo, getSummaryText(accountLines, originalReceiverLines, peerLines, suffixNumber)):
print('The summary file has been written to:\n%s\n' % outputSummaryTo)
class AddressFraction:
'A class to handle an address and associated fractions.'
def __init__(self, line=''):
'Initialize.'
self.coinAddress = ''
self.fractions = []
words = line.split(',')
if len(words) < 2:
return
self.coinAddress = words[1].strip()
if len(words) < 3:
self.fractions.append(Fraction())
return
for word in words[2 :]:
dashIndex = word.find('-')
if dashIndex != -1:
word = word[: dashIndex]
wordStripped = word.replace('/', '').strip()
if wordStripped.isdigit() or len(wordStripped) == 0:
self.fractions.append(Fraction(word))
def __repr__(self):
"Get the string representation of this class."
return '%s, %s' % (self.coinAddress, self.fractions)
class Administrator:
'A class to handle an administrator.'
def __init__(self, line):
'Initialize.'
self.administratorDescription = ''
self.factotumDescription = ''
self.isFactotum = False
self.isFileAdministrator = False
self.isGeneralAdministrator = False
self.pay = 0.0
lineSplit = line.split(',')
if len(lineSplit) < 3:
return
self.name = lineSplit[0].strip()
self.coinAddress = lineSplit[1].strip()
for word in lineSplit:
wordUntilBracket = word
bracketIndex = word.find('(')
if bracketIndex > -1:
wordUntilBracket = word[: bracketIndex]
if wordUntilBracket.endswith('-File Custodian'):
self.isFileAdministrator = True
numberStrings = wordUntilBracket[: wordUntilBracket.find('-')].split('/')
firstFloat = float(numberStrings[0])
if len(numberStrings) == 2:
self.pay += firstFloat / float(numberStrings[1])
else:
self.pay += firstFloat
elif wordUntilBracket.endswith(' Administrator'):
dashIndex = wordUntilBracket.find('-')
if dashIndex != -1:
self.pay += float(wordUntilBracket[: dashIndex])
self.isGeneralAdministrator = True
self.administratorDescription = word[dashIndex + 1 :]
elif wordUntilBracket.endswith(' Factotum'):
dashIndex = wordUntilBracket.find('-')
if dashIndex != -1:
self.pay += float(wordUntilBracket[: dashIndex])
self.isFactotum = True
self.factotumDescription = word[dashIndex + 1 :]
def getAccountLine(self, bonusMultiplier):
'Get account line.'
accountLine = '%s,%s,%s-%s' % (self.name, self.coinAddress, bonusMultiplier, self.administratorDescription)
if self.isFactotum:
accountLine += ',%s-%s' % (bonusMultiplier, self.factotumDescription)
return accountLine
class DenominatorSequence:
'A class to handle a DenominatorSequence.'
def __init__(self, addressFractions, denominator):
'Initialize.'
self.coinAddresses = []
self.denominator = denominator
for addressFraction in addressFractions:
for fraction in addressFraction.fractions:
if fraction.denominator == denominator:
for addressIndex in xrange(fraction.numerator):
if base58.get_bcaddress_version(addressFraction.coinAddress) == 0:
self.coinAddresses.append(addressFraction.coinAddress)
else:
print('Warning, the address %s is invalid and will not get paid.' % addressFraction.coinAddress)
self.coinAddresses.sort()
def __repr__(self):
"Get the string representation of this class."
return '%s, %s\n' % (self.denominator, self.coinAddresses)
def getReceiverLines(self):
'Get the receiver lines.'
numberOfSlots = int(math.ceil(float(len(self.coinAddresses)) / float(self.denominator)))
floatWidth = float(len(self.coinAddresses)) / float(numberOfSlots)
maximumSlotWidth = int(math.ceil(floatWidth))
minimumSlotWidth = int(math.floor(floatWidth))
numberOfCells = numberOfSlots * maximumSlotWidth
remainingNumberOfNarrows = numberOfCells - len(self.coinAddresses)
remainingNumberOfWides = numberOfSlots - remainingNumberOfNarrows
receiverLines = []
coinAddressIndex = 0
for wideIndex in xrange(remainingNumberOfWides):
endIndex = coinAddressIndex + maximumSlotWidth
addReceiverLines(self.coinAddresses[coinAddressIndex : endIndex], receiverLines)
coinAddressIndex = endIndex
for narrowIndex in xrange(remainingNumberOfNarrows):
endIndex = coinAddressIndex + minimumSlotWidth
addReceiverLines(self.coinAddresses[coinAddressIndex : endIndex], receiverLines)
coinAddressIndex = endIndex
return receiverLines
class Fraction:
'A class to handle a fraction.'
def __init__(self, line=''):
'Initialize.'
self.denominator = 1
self.numerator = 1
lineStripped = line.strip()
if len(lineStripped) < 1:
return
if lineStripped[0] == '/':
lineStripped = '1' + lineStripped
words = lineStripped.replace('/', ' ').split()
if len(words) == 0:
return
self.numerator = int(words[0])
if len(words) == 2:
self.denominator = int(words[1])
def __repr__(self):
"Get the string representation of this class."
return '%s/%s' % (self.numerator, self.denominator)
def main():
'Write output.'
writeOutput(sys.argv)
if __name__ == '__main__':
main()