m misc + most of the work for issue 106, except quote support

Signed-off-by: Mattias Andrée <maandree@operamail.com>
This commit is contained in:
Mattias Andrée 2013-04-03 17:03:42 +02:00
parent c3d9e73d71
commit 63f47bd979
12 changed files with 178 additions and 128 deletions

View file

@ -39,11 +39,13 @@ if __name__ == '__main__':
usage_listhelp = '(-l | -L | -B | +l | +L | -A | + A | -v | -h)' usage_listhelp = '(-l | -L | -B | +l | +L | -A | + A | -v | -h)'
usage_file = '[-f\033[33mPONY\033[39m]* [[--] \033[33mmessage\033[39m]' usage_file = '[-f\033[33mPONY\033[39m]* [[--] \033[33mmessage\033[39m]'
usage_xfile = '(+f\033[33mPONY\033[39m)* [[--] \033[33mmessage\033[39m]' usage_xfile = '(+f\033[33mPONY\033[39m)* [[--] \033[33mmessage\033[39m]'
usage_afile = '(-F\033[33mPONY\033[39m)* [[--] \033[33mmessage\033[39m]'
usage_quote = '(-q\033[33mPONY\033[39m)*' usage_quote = '(-q\033[33mPONY\033[39m)*'
usage = '%s %s\n%s %s %s\n%s %s %s\n%s %s %s' % (usage_saythink, usage_listhelp, usage = '%s %s\n%s %s %s\n%s %s %s\n%s %s %s' % (usage_saythink, usage_listhelp,
usage_saythink, usage_common, usage_file, usage_saythink, usage_common, usage_file,
usage_saythink, usage_common, usage_xfile, usage_saythink, usage_common, usage_xfile,
usage_saythink, usage_common, usage_afile,
usage_saythink, usage_common, usage_quote) usage_saythink, usage_common, usage_quote)
usage = usage.replace('\033[', '\0') usage = usage.replace('\033[', '\0')

0
src/argparser.py Normal file → Executable file
View file

0
src/backend.py Normal file → Executable file
View file

0
src/balloon.py Normal file → Executable file
View file

0
src/colourstack.py Normal file → Executable file
View file

0
src/common.py Normal file → Executable file
View file

0
src/kms.py Normal file → Executable file
View file

0
src/list.py Normal file → Executable file
View file

50
src/metadata.py Normal file → Executable file
View file

@ -101,3 +101,53 @@ class Metadata():
passed.append(pony) passed.append(pony)
return passed return passed
'''
Get ponies that fit the terminal
@param fitting:add(str)void The set to fill
@param requirement:int The maximum allowed value
@param file:istream The file with all data
'''
@staticmethod
def getfitting(fitting, requirement, file):
data = file.read() # not too much data, can load everything at once
ptr = 0
while data[ptr] != 47: # 47 == ord('/')
ptr += 1
ptr += 1
size = 0
while data[ptr] != 47: # 47 == ord('/')
size = (size * 10) - (data[ptr] & 15)
ptr += 1
ptr += 1
jump = ptr - size
stop = 0
backjump = 0
while ptr < jump:
size = 0
while data[ptr] != 47: # 47 == ord('/')
size = (size * 10) - (data[ptr] & 15)
ptr += 1
ptr += 1
if -size > requirement:
if backjump > 0:
ptr = backjump
while data[ptr] != 47: # 47 == ord('/')
stop = (stop * 10) - (data[ptr] & 15)
ptr += 1
stop = -stop
break
backjump = ptr
while data[ptr] != 47: # 47 == ord('/')
ptr += 1
ptr += 1
if ptr == jump:
stop = len(data)
else:
ptr = jump
stop += ptr
passed = data[jump : stop].decode('utf8', 'replace').split('/')
for pony in passed:
fitting.add(pony)

201
src/ponysay.py Normal file → Executable file
View file

@ -315,14 +315,7 @@ class Ponysay():
self.restriction = args.opts['-r'] self.restriction = args.opts['-r']
## The stuff ## The stuff
if test('-q'): if not self.unrecognised:
if (len(args.opts['-q']) == 1) and ((args.opts['-q'][0] == '-f') or (args.opts['-q'][0] == '+f')):
if args.opts['-q'][0] == '-f':
args.opts['-q'] = args.files
if test('-f'):
args.opts['-q'] += args.opts['-f']
self.quote(args)
elif not self.unrecognised:
self.print_pony(args) self.print_pony(args)
else: else:
args.help() args.help()
@ -330,6 +323,7 @@ class Ponysay():
return return
############################################## ##############################################
## Methods that run before the mane methods ## ## Methods that run before the mane methods ##
############################################## ##############################################
@ -468,83 +462,34 @@ class Ponysay():
''' '''
Returns one file with full path, names is filter for names, also accepts filepaths Returns one file with full path and ponyquote that should be used, names is filter for names, also accepts filepaths
@param names:list<str>? Ponies to choose from, may be `None` @param selection:(name:str, dirs:itr<str>, quote:bool)? Parsed command line arguments as namedirectoriesquoting tubles:
name: The pony name
dirfiles: Files, with the directory, in the pony directories
quote: Whether to use ponyquotes
@param args:ArgParser Parsed command line arguments
@param alt:bool For method internal use... @param alt:bool For method internal use...
@param ponydirs:itr<str>? The pony directories to use @return (path, quote):(str, str?) The file name of a pony, and the ponyquote that should be used if any
@return :str The file name of a pony
''' '''
def __getponypath(self, names = None, alt = False, ponydirs = None): def __getpony(self, selection, args, alt = False):
if ponydirs is None: ponydirs = self.ponydirs ## If there is no selected ponies, choose all of them
ponies = {} if (selection is None) or (len(selection) == 0):
quote = args.opts['-q'] is not None ## TODO +q -Q
standard = (args.opts['-f'] is not None) or (args.opts['-F'] is not None) ## TODO -Q
extra = (args.opts['+f'] is not None) or (args.opts['-F'] is not None) ## TODO +q -Q
ponydirs = (self.ponydirs if standard else []) + (self.extraponydirs if extra else []);
## List all pony files, without the .pony ending ## Get all ponies
oldponies = {}
for ponydir in ponydirs: for ponydir in ponydirs:
for ponyfile in os.listdir(ponydir): for ponyfile in os.listdir(ponydir):
if endswith(ponyfile, '.pony'): if endswith(ponyfile, '.pony'):
pony = ponyfile[:-5] pony = ponyfile[:-5]
if pony not in ponies: if pony not in ponies:
ponies[pony] = ponydir + ponyfile oldponies[pony] = ponydir + ponyfile
## Support for explicit pony file names ## Apply metadata restriction
if names is not None:
for name in names:
if os.path.exists(name):
ponies[name] = name
'''
Get ponies that fit the terminal
@param fitting:add(str)void The set to fill
@param requirement:int The maximum allowed value
@param file:istream The file with all data
'''
def getfitting(fitting, requirement, file):
data = file.read() # not too much data, can load everything at once
ptr = 0
while data[ptr] != 47: # 47 == ord('/')
ptr += 1
ptr += 1
size = 0
while data[ptr] != 47: # 47 == ord('/')
size = (size * 10) - (data[ptr] & 15)
ptr += 1
ptr += 1
jump = ptr - size
stop = 0
backjump = 0
while ptr < jump:
size = 0
while data[ptr] != 47: # 47 == ord('/')
size = (size * 10) - (data[ptr] & 15)
ptr += 1
ptr += 1
if -size > requirement:
if backjump > 0:
ptr = backjump
while data[ptr] != 47: # 47 == ord('/')
stop = (stop * 10) - (data[ptr] & 15)
ptr += 1
stop = -stop
break
backjump = ptr
while data[ptr] != 47: # 47 == ord('/')
ptr += 1
ptr += 1
if ptr == jump:
stop = len(data)
else:
ptr = jump
stop += ptr
passed = data[jump : stop].decode('utf8', 'replace').split('/')
for pony in passed:
fitting.add(pony)
## If there is not select ponies, choose all of them
if (names is None) or (len(names) == 0):
oldponies = ponies
if self.restriction is not None: if self.restriction is not None:
logic = Metadata.makeRestrictionLogic(self.restriction) logic = Metadata.makeRestrictionLogic(self.restriction)
ponies = {} ponies = {}
@ -554,7 +499,10 @@ class Ponysay():
ponyfile = ponydir + pony + '.pony' ponyfile = ponydir + pony + '.pony'
if oldponies[pony] == ponyfile: if oldponies[pony] == ponyfile:
ponies[pony] = ponyfile ponies[pony] = ponyfile
if len(ponies) > 0:
oldponies = ponies oldponies = ponies
## Apply dimension restriction
ponies = {} ponies = {}
(termh, termw) = gettermsize() (termh, termw) = gettermsize()
for ponydir in ponydirs: for ponydir in ponydirs:
@ -562,51 +510,56 @@ class Ponysay():
if os.path.exists(ponydir + 'widths'): if os.path.exists(ponydir + 'widths'):
fitw = set() fitw = set()
with open(ponydir + 'widths', 'rb') as file: with open(ponydir + 'widths', 'rb') as file:
getfitting(fitw, termw, file) Metadata.getfitting(fitw, termw, file)
if os.path.exists(ponydir + ('onlyheights' if self.ponyonly else 'heights')): if os.path.exists(ponydir + ('onlyheights' if self.ponyonly else 'heights')):
fith = set() fith = set()
with open(ponydir + ('onlyheights' if self.ponyonly else 'heights'), 'rb') as file: with open(ponydir + ('onlyheights' if self.ponyonly else 'heights'), 'rb') as file:
getfitting(fith, termh, file) Metadata.getfitting(fith, termh, file)
for ponyfile in oldponies.values(): for ponyfile in oldponies.values():
if ponyfile.startswith(ponydir): if ponyfile.startswith(ponydir):
pony = ponyfile[len(ponydir) : -5] pony = ponyfile[len(ponydir) : -5]
if (fitw is None) or (pony in fitw): if (fitw is None) or (pony in fitw):
if (fith is None) or (pony in fith): if (fith is None) or (pony in fith):
ponies[pony] = ponyfile ponies[pony] = ponyfile
#for ponyfile in os.listdir(ponydir):
# if endswith(ponyfile, '.pony'): ## Select one pony and set all information
# pony = ponyfile[:-5]
# if pony not in ponies:
# if (fitw is None) or (pony in fitw):
# if (fith is None) or (pony in fith):
# ponies[pony] = ponydir + ponyfile
names = list((oldponies if len(ponies) == 0 else ponies).keys()) names = list((oldponies if len(ponies) == 0 else ponies).keys())
if len(names) == 0:
printerr('All the ponies are missing, call the Princess!')
exit(249)
pony = names[random.randrange(0, len(names))]
selection = (pony, ponies[pony], quote)
## Select a random pony of the choosen ones ## Select a random pony of the choosen ones
pony = names[random.randrange(0, len(names))] pony = selection[random.randrange(0, len(selection))]
if pony not in ponies: if os.path.exists(pony[0]):
if not alt: return (pony[0], if pony[2] else None) # FIXME select quote
autocorrect = SpelloCorrecter(ponydirs, '.pony')
(alternatives, dist) = autocorrect.correct(pony)
limit = os.environ['PONYSAY_TYPO_LIMIT'] if 'PONYSAY_TYPO_LIMIT' in os.environ else ''
limit = 5 if len(limit) == 0 else int(dist)
if (len(alternatives) > 0) and (dist <= limit):
return self.__getponypath(alternatives, True)
sys.stderr.write('I have never heard of anypony named %s\n' % (pony));
if not self.usingstandard:
sys.stderr.write('Use -f/-q or -F if it a MLP:FiM pony');
if not self.usingexta:
sys.stderr.write('Have you tested +f or -F?');
exit(1)
else: else:
return ponies[pony] possibilities = [f.split(os.sep)[-1][:-5] for f in pony[1]]
if pony[0] not in possibilities:
if not alt:
autocorrect = SpelloCorrecter(possibilities)
(alternatives, dist) = autocorrect.correct(pony[0])
limit = os.environ['PONYSAY_TYPO_LIMIT'] if 'PONYSAY_TYPO_LIMIT' in os.environ else ''
limit = 5 if len(limit) == 0 else int(limit)
if (len(alternatives) > 0) and (dist <= limit):
(_, files, quote) = pony
return self.__getpony([(a, files, quote) for a in alternatives], True)
printerr('I have never heard of anypony named %s' % pony[0]);
if not self.usingstandard:
printerr('Use -f/-q or -F if it a MLP:FiM pony');
if not self.usingextra:
printerr('Have you tested +f or -F?');
exit(252)
else:
return (pony[1][possibilities.find(pony[0])], if pony[2] else None) # FIXME select quote
''' '''
Returns a set with all ponies that have quotes and are displayable Returns a set with all ponies that have quotes and are displayable
@param ponydirs:itr<str> The pony directories to use @param ponydirs:itr<str>? The pony directories to use
@param quotedirs:itr<str> The quote directories to use @param quotedirs:itr<str>? The quote directories to use
@return :set<str> All ponies that have quotes and are displayable @return :set<str> All ponies that have quotes and are displayable
''' '''
def __quoters(self, ponydirs = None, quotedirs = None): def __quoters(self, ponydirs = None, quotedirs = None):
@ -642,8 +595,8 @@ class Ponysay():
''' '''
Returns a list with all (pony, quote file) pairs Returns a list with all (pony, quote file) pairs
@param ponydirs:itr<str> The pony directories to use @param ponydirs:itr<str>? The pony directories to use
@param quotedirs:itr<str> The quote directories to use @param quotedirs:itr<str>? The quote directories to use
@return (pony, quote):(str, str) All poniesquote file-pairs @return (pony, quote):(str, str) All poniesquote file-pairs
''' '''
def __quotes(self, ponydirs = None, quotedirs = None): def __quotes(self, ponydirs = None, quotedirs = None):
@ -675,11 +628,10 @@ class Ponysay():
## Listing methods ## ## Listing methods ##
##################### #####################
''' '''
Lists the available ponies Lists the available ponies
@param ponydirs:itr<str> The pony directories to use @param ponydirs:itr<str>? The pony directories to use
''' '''
def list(self, ponydirs = None): def list(self, ponydirs = None):
List.simplelist(self.ponydirs if ponydirs is None else ponydirs, List.simplelist(self.ponydirs if ponydirs is None else ponydirs,
@ -735,6 +687,7 @@ class Ponysay():
print(pony) print(pony)
##################### #####################
## Balloon methods ## ## Balloon methods ##
##################### #####################
@ -787,11 +740,11 @@ class Ponysay():
autocorrect = SpelloCorrecter(self.balloondirs, '.think' if self.isthink else '.say') autocorrect = SpelloCorrecter(self.balloondirs, '.think' if self.isthink else '.say')
(alternatives, dist) = autocorrect.correct(balloon) (alternatives, dist) = autocorrect.correct(balloon)
limit = os.environ['PONYSAY_TYPO_LIMIT'] if 'PONYSAY_TYPO_LIMIT' in os.environ else '' limit = os.environ['PONYSAY_TYPO_LIMIT'] if 'PONYSAY_TYPO_LIMIT' in os.environ else ''
limit = 5 if len(limit) == 0 else int(dist) limit = 5 if len(limit) == 0 else int(limit)
if (len(alternatives) > 0) and (dist <= limit): if (len(alternatives) > 0) and (dist <= limit):
return self.__getballoonpath(alternatives, True) return self.__getballoonpath(alternatives, True)
printerr('That balloon style %s does not exist' % (balloon)); printerr('That balloon style %s does not exist' % balloon)
exit(1) exit(251)
else: else:
return balloons[balloon] return balloons[balloon]
@ -825,8 +778,29 @@ class Ponysay():
@param args:ArgParser Parsed command line arguments @param args:ArgParser Parsed command line arguments
''' '''
def print_pony(self, args): def print_pony(self, args):
## Get the pony
(selection, standard, extra) = ([], [], [])
for ponydir in self.ponydirs:
for pony in os.listdir(ponydir):
standard.append(ponydir + pony)
for ponydir in self.extraponydirs:
for pony in os.listdir(ponydir):
extra.append(ponydir + pony)
both = standard + extra
if args.opts['-f'] is not None: for pony in args.opts['-f']: selection.append(pony, standard, False)
if args.opts['+f'] is not None: for pony in args.opts['+f']: selection.append(pony, extra, False)
if args.opts['-F'] is not None: for pony in args.opts['-F']: selection.append(pony, both, False)
if args.opts['-q'] is not None: for pony in args.opts['-q']: selection.append(pony, standard, True)
## TODO +q -Q
(pony, quote) = self.__getpony(selection, args)
## Get message and remove tailing whitespace from stdin (but not for each line) ## Get message and remove tailing whitespace from stdin (but not for each line)
if args.message == None: msg = None
if quote is not None:
printinfo('quote file: ' + quote)
with open(quote, 'rb') as qfile:
msg = qfile.read().decode('utf8', 'replace').strip()
elif args.message == None:
msg = ''.join(sys.stdin.readlines()).rstrip() msg = ''.join(sys.stdin.readlines()).rstrip()
else: else:
msg = args.message msg = args.message
@ -854,8 +828,7 @@ class Ponysay():
last = c last = c
msg = buf.replace('\n', '\n\n') msg = buf.replace('\n', '\n\n')
## Get the pony ## Print info
pony = self.__getponypath(args.opts['-f'])
printinfo('pony file: ' + pony) printinfo('pony file: ' + pony)
## Use PNG file as pony file ## Use PNG file as pony file
@ -979,8 +952,8 @@ class Ponysay():
args.message = qfile.read().decode('utf8', 'replace').strip() args.message = qfile.read().decode('utf8', 'replace').strip()
args.opts['-f'] = [pair[0]] args.opts['-f'] = [pair[0]]
elif len(args.opts['-q']) == 0: elif len(args.opts['-q']) == 0:
sys.stderr.write('Princess Celestia! All the ponies are mute!\n') printerr('Princess Celestia! All the ponies are mute!')
exit(1) exit(250)
else: else:
args.opts['-f'] = [args.opts['-q'][random.randrange(0, len(args.opts['-q']))]] args.opts['-f'] = [args.opts['-q'][random.randrange(0, len(args.opts['-q']))]]
args.message = 'Zecora! Help me, I am mute!' args.message = 'Zecora! Help me, I am mute!'

27
src/spellocorrecter.py Normal file → Executable file
View file

@ -27,8 +27,12 @@ class SpelloCorrecter(): # Naïvely and quickly ported and adapted from optimise
@param directories:list<str> List of directories that contains the file names with the correct spelling @param directories:list<str> List of directories that contains the file names with the correct spelling
@param ending:str The file name ending of the correctly spelled file names, this is removed for the name @param ending:str The file name ending of the correctly spelled file names, this is removed for the name
-- OR -- (emulated overloading [overloading is absent in Python])
@param directories:list<str> The file names with the correct spelling
''' '''
def __init__(self, directories, ending): def __init__(self, directories, ending = None):
self.weights = {'k' : {'c' : 0.25, 'g' : 0.75, 'q' : 0.125}, self.weights = {'k' : {'c' : 0.25, 'g' : 0.75, 'q' : 0.125},
'c' : {'k' : 0.25, 'g' : 0.75, 's' : 0.5, 'z' : 0.5, 'q' : 0.125}, 'c' : {'k' : 0.25, 'g' : 0.75, 's' : 0.5, 'z' : 0.5, 'q' : 0.125},
's' : {'z' : 0.25, 'c' : 0.5}, 's' : {'z' : 0.25, 'c' : 0.5},
@ -60,6 +64,7 @@ class SpelloCorrecter(): # Naïvely and quickly ported and adapted from optimise
previous = '' previous = ''
self.dictionary[-1] = previous; self.dictionary[-1] = previous;
if ending is not None:
for directory in directories: for directory in directories:
for filename in os.listdir(directory): for filename in os.listdir(directory):
if (not endswith(filename, ending)) or (len(filename) - len(ending) > 127): if (not endswith(filename, ending)) or (len(filename) - len(ending) > 127):
@ -74,6 +79,26 @@ class SpelloCorrecter(): # Naïvely and quickly ported and adapted from optimise
self.dictionaryEnd -= 1 self.dictionaryEnd -= 1
self.dictionary[self.dictionaryEnd] = proper self.dictionary[self.dictionaryEnd] = proper
prevCommon = min(len(previous), len(proper))
for i in range(0, prevCommon):
if previous[i] != proper[i]:
prevCommon = i
break
previous = proper
self.reusable[self.dictionaryEnd] = prevCommon
else:
for proper in directories:
if len(proper) > 127:
continue
if self.dictionaryEnd == 0:
self.dictionaryEnd = len(self.dictionary)
self.reusable = [0] * self.dictionaryEnd + self.reusable
self.dictionary = [None] * self.dictionaryEnd + self.dictionary
self.dictionaryEnd -= 1
self.dictionary[self.dictionaryEnd] = proper
prevCommon = min(len(previous), len(proper)) prevCommon = min(len(previous), len(proper))
for i in range(0, prevCommon): for i in range(0, prevCommon):
if previous[i] != proper[i]: if previous[i] != proper[i]:

0
src/ucs.py Normal file → Executable file
View file