From e23628daf99f3ef1f6031284844bfb5474159b00 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Thu, 25 Oct 2012 00:06:30 +0200 Subject: fix balloon cowsay + fix mini balloons + fix wrapping bugs + inform user of exception in wrapping and do skip wrapping --- balloons/cowsay.say | 2 +- ponysay.py | 289 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 169 insertions(+), 122 deletions(-) diff --git a/balloons/cowsay.say b/balloons/cowsay.say index bb486db..51942fa 100644 --- a/balloons/cowsay.say +++ b/balloons/cowsay.say @@ -6,7 +6,7 @@ s:- w:| e: | -ww:< +ww:< ee: > nw: _ diff --git a/ponysay.py b/ponysay.py index e9a5b61..de6997a 100755 --- a/ponysay.py +++ b/ponysay.py @@ -79,7 +79,7 @@ Checks whether a text ends with a specific text, but has more @return :bool The result of the test ''' def endswith(text, ending): - return text.endswith(ending) and not (text == ending); + return text.endswith(ending) and not (text == ending) @@ -407,10 +407,13 @@ class Ponysay(): ''' def __gettermsize(self): ## Call `stty` to determine the size of the terminal, this way is better then using python's ncurses - termsize = Popen(['stty', 'size'], stdout=PIPE, stdin=sys.stderr).communicate()[0] - termsize = termsize.decode('utf8', 'replace')[:-1].split(' ') # [:-1] removes a \n - termsize = [int(item) for item in termsize] - return termsize + for channel in (sys.stderr, sys.stdout, sys.stdin): + termsize = Popen(['stty', 'size'], stdout=PIPE, stdin=channel, stderr=PIPE).communicate()[0] + if len(termsize) > 0: + termsize = termsize.decode('utf8', 'replace')[:-1].split(' ') # [:-1] removes a \n + termsize = [int(item) for item in termsize] + return termsize + return (24, 80) # fall back to minimal sane size @@ -706,8 +709,8 @@ class Ponysay(): ## Use default balloon if none is specified if balloonfile is None: if isthink: - return Balloon('o', 'o', '( ', ' )', [' _'], ['_'], ['_'], ['_'], ['_ '], ' )', ' )', ' )', ['- '], ['-'], ['-'], ['-'], [' -'], '( ', '( ', '( ') - return Balloon('\\', '/', '< ', ' >', [' _'], ['_'], ['_'], ['_'], ['_ '], ' \\', ' |', ' /', ['- '], ['-'], ['-'], ['-'], [' -'], '\\ ', '| ', '/ ') + return Balloon('o', 'o', '( ', ' )', [' _'], ['_'], ['_'], ['_'], ['_ '], ' )', ' )', ' )', ['- '], ['-'], ['-'], ['-'], [' -'], '( ', '( ', '( ') + return Balloon('\\', '/', '< ', ' >', [' _'], ['_'], ['_'], ['_'], ['_ '], ' \\', ' |', ' /', ['- '], ['-'], ['-'], ['-'], [' -'], '\\ ', '| ', '/ ') ## Initialise map for balloon parts map = {} @@ -716,7 +719,8 @@ class Ponysay(): ## Read all lines in the balloon file with open(balloonfile, 'rb') as balloonstream: - data = [line.replace('\n', '') for line in balloonstream.read().decode('utf8', 'replace').split('\n')] + data = balloonstream.read().decode('utf8', 'replace') + data = [line.replace('\n', '') for line in data.split('\n')] ## Parse the balloon file, and fill the map last = None @@ -1380,7 +1384,7 @@ class Balloon(): for j in range(0, len(self.n)): outer = UCS.dispLen(self.nw[j]) + UCS.dispLen(self.ne[j]) inner = UCS.dispLen(self.nnw[j]) + UCS.dispLen(self.nne[j]) - if outer + inner >= w: + if outer + inner <= w: rc.append(self.nw[j] + self.nnw[j] + self.n[j] * (w - outer - inner) + self.nne[j] + self.ne[j]) else: rc.append(self.nw[j] + self.n[j] * (w - outer) + self.ne[j]) @@ -1391,7 +1395,7 @@ class Balloon(): for j in range(0, len(self.s)): outer = UCS.dispLen(self.sw[j]) + UCS.dispLen(self.se[j]) inner = UCS.dispLen(self.ssw[j]) + UCS.dispLen(self.sse[j]) - if outer + inner >= w: + if outer + inner <= w: rc.append(self.sw[j] + self.ssw[j] + self.s[j] * (w - outer - inner) + self.sse[j] + self.se[j]) else: rc.append(self.sw[j] + self.s[j] * (w - outer) + self.se[j]) @@ -1440,17 +1444,41 @@ class Backend(): ''' def parse(self): self.__expandMessage() + self.__unpadMessage() self.__loadFile() + if self.pony.startswith('$$$\n'): self.pony = self.pony[4:] infoend = self.pony.index('\n$$$\n') printinfo(self.pony[:infoend]) self.pony = self.pony[infoend + 5:] self.pony = mode + self.pony + self.__processPony() self.__truncate() + ''' + Remove padding spaces fortune cookies are padded with whitespace (damn featherbrains) + ''' + def __unpadMessage(self): + lines = self.message.split('\n') + for spaces in (8, 4, 2, 1): + padded = True + for line in lines: + if not line.startswith(' ' * spaces): + padded = False + break + if padded: + for i in range(0, len(lines)): + line = lines[i] + while line.startswith(' ' * spaces): + line = line[spaces:] + lines[i] = line + lines = [line.rstrip(' ') for line in lines] + self.message = '\n'.join(lines) + + ''' Converts all tabs in the message to spaces by expanding ''' @@ -1728,131 +1756,149 @@ class Backend(): @return :str The message wrapped ''' def __wrapMessage(self, message, wrap): - AUTO_PUSH = '\033[0101y0~' - AUTO_POP = '\033[10101~' - msg = message.replace('\n', AUTO_PUSH + '\n' + AUTO_POP); - cstack = ColourStack(AUTO_PUSH, AUTO_POP) buf = '' - for c in msg: - buf += c + cstack.feed(c) - lines = buf.replace(AUTO_PUSH, '').replace(AUTO_POP, '').split('\n') - buf = '' - for line in lines: - b = [None] * len(line) - map = {} - (bi, cols, w) = (0, 0, wrap) - (indent, indentc) = (-1, 0) + try: + AUTO_PUSH = '\033[01010~' + AUTO_POP = '\033[10101~' + msg = message.replace('\n', AUTO_PUSH + '\n' + AUTO_POP); + cstack = ColourStack(AUTO_PUSH, AUTO_POP) + for c in msg: + buf += c + cstack.feed(c) + lines = buf.replace(AUTO_PUSH, '').replace(AUTO_POP, '').split('\n') + buf = '' - (i, n) = (0, len(line)) - while i <= n: - d = None - if i < n: - d = line[i] - i += 1 - if d == '\033': # TODO this should use self.__getcolour() - ## Invisible stuff - b[bi] = d - bi += 1 - b[bi] = line[i] - d = line[i] - bi += 1 + for line in lines: + b = [None] * len(line) + map = {0 : 0} + (bi, cols, w) = (0, 0, wrap) + (indent, indentc) = (-1, 0) + + (i, n) = (0, len(line)) + while i <= n: + d = None + if i < n: + d = line[i] i += 1 - if d == '[': - while True: - b[bi] = line[i] - d = line[i] - bi += 1 - i += 1 - if (('a' <= d) and (d <= 'z')) or (('A' <= d) and (d <= 'Z')) or (d == '~'): - break - elif d == ']': + if d == '\033': # TODO this should use self.__getcolour() + ## Invisible stuff + b[bi] = d + bi += 1 b[bi] = line[i] d = line[i] bi += 1 i += 1 - if d == 'P': - for j in range(0, 7): + if d == '[': + while True: b[bi] = line[i] + d = line[i] bi += 1 i += 1 - elif (d is not None) and (d != ' '): - ## Fetch word - if indent == -1: - indent = i - 1 - for j in range(0, indent): - if line[j] == ' ': - indentc += 1 - b[bi] = d - bi += 1 - if (not UCS.isCombining(d)) and (d != '­'): - cols += 1 - map[cols] = bi - else: - ## Wrap? - mm = 0 - bisub = 0 - iwrap = wrap - (0 if indent == 1 else indentc) - - while ((w > 8) and (cols > w + 5)) or (cols > iwrap): # TODO make configurable - ## wrap - x = w; - nbsp = b[map[mm + x]] == ' ' - m = map[mm + x] - - if ('­' in b[bisub : m]) and not nbsp: - hyphen = m - 1 - while b[hyphen] != '­': - hyphen -= 1 - while map[mm + x] > hyphen: ## Only looking backward, if foreward is required the word is probabily not hyphenated correctly - x -= 1 - x += 1 + if (('a' <= d) and (d <= 'z')) or (('A' <= d) and (d <= 'Z')) or (d == '~'): + break + elif d == ']': + b[bi] = line[i] + d = line[i] + bi += 1 + i += 1 + if d == 'P': + for j in range(0, 7): + b[bi] = line[i] + bi += 1 + i += 1 + elif (d is not None) and (d != ' '): + ## Fetch word + if indent == -1: + indent = i - 1 + for j in range(0, indent): + if line[j] == ' ': + indentc += 1 + b[bi] = d + bi += 1 + if (not UCS.isCombining(d)) and (d != '­'): + cols += 1 + map[cols] = bi + else: + ## Wrap? + mm = 0 + bisub = 0 + iwrap = wrap - (0 if indent == 1 else indentc) + + while ((w > 8) and (cols > w + 5)) or (cols > iwrap): # TODO make configurable + ## wrap + x = w; + if mm + x not in map: # Too much whitespace ? + cols = 0 + break + nbsp = b[map[mm + x]] == ' ' m = map[mm + x] + + if ('­' in b[bisub : m]) and not nbsp: + hyphen = m - 1 + while b[hyphen] != '­': + hyphen -= 1 + while map[mm + x] > hyphen: ## Only looking backward, if foreward is required the word is probabily not hyphenated correctly + x -= 1 + x += 1 + m = map[mm + x] + + mm += x - (0 if nbsp else 1) ## − 1 so we have space for a hythen + + for bb in b[bisub : m]: + buf += bb + buf += '\n' if nbsp else '\0\n' + cols -= x - (0 if nbsp else 1) + bisub = m + + w = iwrap + if indent != -1: + buf += line[:indent] - mm += x - (0 if nbsp else 1) ## − 1 so we have space for a hythen - - for bb in b[bisub : m]: - buf += bb - buf += '\n' if nbsp else '\0\n' - cols -= x - (0 if nbsp else 1) - bisub = m + for j in range(bisub, bi): + b[j - bisub] = b[j] + bi -= bisub - w = iwrap - if indent != -1: - buf += line[:indent] - - for j in range(bisub, bi): - b[j - bisub] = b[j] - bi -= bisub - - if cols > w: - buf += '\n' - w = wrap - if indent != -1: - buf += line[:indent] - w -= indentc - for bb in b[:bi]: - buf += bb - w -= cols - cols = 0 - bi = 0 - if d is None: - i += 1 - else: - if w > 0: - buf += ' ' - w -= 1 - else: + if cols > w: buf += '\n' w = wrap if indent != -1: - buf + line[:indent] + buf += line[:indent] w -= indentc - buf += '\n' - - rc = '\n'.join(line.rstrip() for line in buf[:-1].split('\n')); - rc = rc.replace('­', ''); # remove soft hyphens - rc = rc.replace('\0', '%s%s%s' % (AUTO_PUSH, self.hyphen, AUTO_POP)) - return rc + for bb in b[:bi]: + buf += bb + w -= cols + cols = 0 + bi = 0 + if d is None: + i += 1 + else: + if w > 0: + buf += ' ' + w -= 1 + else: + buf += '\n' + w = wrap + if indent != -1: + buf + line[:indent] + w -= indentc + buf += '\n' + + rc = '\n'.join(line.rstrip() for line in buf[:-1].split('\n')); + rc = rc.replace('­', ''); # remove soft hyphens + rc = rc.replace('\0', '%s%s%s' % (AUTO_PUSH, self.hyphen, AUTO_POP)) + return rc + except Exception as err: + import traceback + errormessage = ''.join(traceback.format_exception(type(err), err, None)) + rc = '\n'.join(line.rstrip() for line in buf.split('\n')); + rc = rc.replace('\0', '%s%s%s' % (AUTO_PUSH, self.hyphen, AUTO_POP)) + errormessage += '\n---- WRAPPING BUFFER ----\n\n' + rc + try: + if os.readlink('/proc/self/fd/2') != os.readlink('/proc/self/fd/1'): + printerr(errormessage, end='') + return message + except: + pass + return message + '\n\n\033[0;1;31m---- EXCEPTION IN PONYSAY WHILE WRAPPING ----\033[0m\n\n' + errormessage ''' @@ -1884,7 +1930,7 @@ class ColourStack(): @return :str String that should be inserted into your buffer ''' def push(self): - self.stack = [[self.bufproto, None, None, [False] * 9]] + self.stack + self.stack.insert(0, [self.bufproto, None, None, [False] * 9]) if len(self.stack) == 1: return None return '\033[0m' @@ -1896,9 +1942,10 @@ class ColourStack(): @return :str String that should be inserted into your buffer ''' def pop(self): - old = self.stack[0] - self.stack = self.stack[1:] + old = self.stack.pop(0) rc = '\033[0;' + if len(self.stack) == 0: # last resort in case something made it pop too mush + push() new = self.stack[0] if new[1] is not None: rc += new[1] + ';' if new[2] is not None: rc += new[2] + ';' -- cgit