diff --git a/src/__main__.py b/src/__main__.py index bdf685e3..49cb71de 100755 --- a/src/__main__.py +++ b/src/__main__.py @@ -39,11 +39,13 @@ if __name__ == '__main__': usage_listhelp = '(-l | -L | -B | +l | +L | -A | + A | -v | -h)' usage_file = '[-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 = '%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_xfile, + usage_saythink, usage_common, usage_afile, usage_saythink, usage_common, usage_quote) usage = usage.replace('\033[', '\0') diff --git a/src/argparser.py b/src/argparser.py old mode 100644 new mode 100755 diff --git a/src/backend.py b/src/backend.py old mode 100644 new mode 100755 diff --git a/src/balloon.py b/src/balloon.py old mode 100644 new mode 100755 diff --git a/src/colourstack.py b/src/colourstack.py old mode 100644 new mode 100755 diff --git a/src/common.py b/src/common.py old mode 100644 new mode 100755 diff --git a/src/kms.py b/src/kms.py old mode 100644 new mode 100755 diff --git a/src/list.py b/src/list.py old mode 100644 new mode 100755 diff --git a/src/metadata.py b/src/metadata.py old mode 100644 new mode 100755 index 10935f37..af742f61 --- a/src/metadata.py +++ b/src/metadata.py @@ -100,4 +100,54 @@ class Metadata(): if logic(meta): passed.append(pony) 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) diff --git a/src/ponysay.py b/src/ponysay.py old mode 100644 new mode 100755 index 8bcb438e..07506034 --- a/src/ponysay.py +++ b/src/ponysay.py @@ -315,14 +315,7 @@ class Ponysay(): self.restriction = args.opts['-r'] ## The stuff - if test('-q'): - 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: + if not self.unrecognised: self.print_pony(args) else: args.help() @@ -330,6 +323,7 @@ class Ponysay(): return + ############################################## ## 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? Ponies to choose from, may be `None` - @param alt:bool For method internal use... - @param ponydirs:itr? The pony directories to use - @return :str The file name of a pony + @param selection:(name:str, dirs:itr, quote:bool)? Parsed command line arguments as name–directories–quoting 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... + @return (path, quote):(str, str?) The file name of a pony, and the ponyquote that should be used if any ''' - def __getponypath(self, names = None, alt = False, ponydirs = None): - if ponydirs is None: ponydirs = self.ponydirs - ponies = {} - - ## List all pony files, without the .pony ending - for ponydir in ponydirs: - for ponyfile in os.listdir(ponydir): - if endswith(ponyfile, '.pony'): - pony = ponyfile[:-5] - if pony not in ponies: - ponies[pony] = ponydir + ponyfile - - ## Support for explicit pony file names - 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) + def __getpony(self, selection, args, alt = False): + ## If there is no selected ponies, choose all of them + 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 []); - - ## If there is not select ponies, choose all of them - if (names is None) or (len(names) == 0): - oldponies = ponies + ## Get all ponies + oldponies = {} + for ponydir in ponydirs: + for ponyfile in os.listdir(ponydir): + if endswith(ponyfile, '.pony'): + pony = ponyfile[:-5] + if pony not in ponies: + oldponies[pony] = ponydir + ponyfile + + ## Apply metadata restriction if self.restriction is not None: logic = Metadata.makeRestrictionLogic(self.restriction) ponies = {} @@ -554,7 +499,10 @@ class Ponysay(): ponyfile = ponydir + pony + '.pony' if oldponies[pony] == ponyfile: ponies[pony] = ponyfile - oldponies = ponies + if len(ponies) > 0: + oldponies = ponies + + ## Apply dimension restriction ponies = {} (termh, termw) = gettermsize() for ponydir in ponydirs: @@ -562,52 +510,57 @@ class Ponysay(): if os.path.exists(ponydir + 'widths'): fitw = set() 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')): fith = set() 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(): if ponyfile.startswith(ponydir): pony = ponyfile[len(ponydir) : -5] if (fitw is None) or (pony in fitw): if (fith is None) or (pony in fith): ponies[pony] = ponyfile - #for ponyfile in os.listdir(ponydir): - # if endswith(ponyfile, '.pony'): - # 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 + + ## Select one pony and set all information 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 - pony = names[random.randrange(0, len(names))] - if pony not in ponies: - if not alt: - 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) + pony = selection[random.randrange(0, len(selection))] + if os.path.exists(pony[0]): + return (pony[0], if pony[2] else None) # FIXME select quote 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 - @param ponydirs:itr The pony directories to use - @param quotedirs:itr The quote directories to use - @return :set All ponies that have quotes and are displayable + @param ponydirs:itr? The pony directories to use + @param quotedirs:itr? The quote directories to use + @return :set All ponies that have quotes and are displayable ''' def __quoters(self, ponydirs = None, quotedirs = None): if ponydirs is None: ponydirs = self.ponydirs @@ -642,8 +595,8 @@ class Ponysay(): ''' Returns a list with all (pony, quote file) pairs - @param ponydirs:itr The pony directories to use - @param quotedirs:itr The quote directories to use + @param ponydirs:itr? The pony directories to use + @param quotedirs:itr? The quote directories to use @return (pony, quote):(str, str) All ponies–quote file-pairs ''' def __quotes(self, ponydirs = None, quotedirs = None): @@ -675,11 +628,10 @@ class Ponysay(): ## Listing methods ## ##################### - ''' Lists the available ponies - @param ponydirs:itr The pony directories to use + @param ponydirs:itr? The pony directories to use ''' def list(self, ponydirs = None): List.simplelist(self.ponydirs if ponydirs is None else ponydirs, @@ -735,6 +687,7 @@ class Ponysay(): print(pony) + ##################### ## Balloon methods ## ##################### @@ -787,11 +740,11 @@ class Ponysay(): autocorrect = SpelloCorrecter(self.balloondirs, '.think' if self.isthink else '.say') (alternatives, dist) = autocorrect.correct(balloon) 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): return self.__getballoonpath(alternatives, True) - printerr('That balloon style %s does not exist' % (balloon)); - exit(1) + printerr('That balloon style %s does not exist' % balloon) + exit(251) else: return balloons[balloon] @@ -825,8 +778,29 @@ class Ponysay(): @param args:ArgParser Parsed command line arguments ''' 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) - 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() else: msg = args.message @@ -854,8 +828,7 @@ class Ponysay(): last = c msg = buf.replace('\n', '\n\n') - ## Get the pony - pony = self.__getponypath(args.opts['-f']) + ## Print info printinfo('pony file: ' + pony) ## Use PNG file as pony file @@ -979,8 +952,8 @@ class Ponysay(): args.message = qfile.read().decode('utf8', 'replace').strip() args.opts['-f'] = [pair[0]] elif len(args.opts['-q']) == 0: - sys.stderr.write('Princess Celestia! All the ponies are mute!\n') - exit(1) + printerr('Princess Celestia! All the ponies are mute!') + exit(250) else: args.opts['-f'] = [args.opts['-q'][random.randrange(0, len(args.opts['-q']))]] args.message = 'Zecora! Help me, I am mute!' diff --git a/src/spellocorrecter.py b/src/spellocorrecter.py old mode 100644 new mode 100755 index 02cfc3e7..029290b0 --- a/src/spellocorrecter.py +++ b/src/spellocorrecter.py @@ -27,8 +27,12 @@ class SpelloCorrecter(): # Naïvely and quickly ported and adapted from optimise @param directories:list 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 + + -- OR -- (emulated overloading [overloading is absent in Python]) + + @param directories:list 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}, 'c' : {'k' : 0.25, 'g' : 0.75, 's' : 0.5, 'z' : 0.5, 'q' : 0.125}, 's' : {'z' : 0.25, 'c' : 0.5}, @@ -60,11 +64,32 @@ class SpelloCorrecter(): # Naïvely and quickly ported and adapted from optimise previous = '' self.dictionary[-1] = previous; - for directory in directories: - for filename in os.listdir(directory): - if (not endswith(filename, ending)) or (len(filename) - len(ending) > 127): + if ending is not None: + for directory in directories: + for filename in os.listdir(directory): + if (not endswith(filename, ending)) or (len(filename) - len(ending) > 127): + continue + proper = filename[:-len(ending)] + + 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)) + 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 - proper = filename[:-len(ending)] if self.dictionaryEnd == 0: self.dictionaryEnd = len(self.dictionary) diff --git a/src/ucs.py b/src/ucs.py old mode 100644 new mode 100755