2013-04-02 11:21:33 +02:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
ponysay - Ponysay, cowsay reimplementation for ponies
|
2013-04-03 20:34:46 +02:00
|
|
|
|
|
2013-04-02 11:21:33 +02:00
|
|
|
|
Copyright (C) 2012, 2013 Erkin Batu Altunbaş et al.
|
|
|
|
|
|
2013-04-03 20:34:46 +02:00
|
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
If you intend to redistribute ponysay or a fork of it commercially,
|
|
|
|
|
it contains aggregated images, some of which may not be commercially
|
|
|
|
|
redistribute, you would be required to remove those. To determine
|
|
|
|
|
whether or not you may commercially redistribute an image make use
|
|
|
|
|
that line ‘FREE: yes’, is included inside the image between two ‘$$$’
|
|
|
|
|
lines and the ‘FREE’ is and upper case and directly followed by
|
|
|
|
|
the colon.
|
2013-04-02 11:21:33 +02:00
|
|
|
|
'''
|
|
|
|
|
from common import *
|
2013-04-02 11:26:38 +02:00
|
|
|
|
from balloon import *
|
|
|
|
|
from colourstack import *
|
|
|
|
|
from ucs import *
|
2013-04-02 11:21:33 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Super-ultra-extreme-awesomazing replacement for cowsay
|
|
|
|
|
'''
|
|
|
|
|
class Backend():
|
|
|
|
|
'''
|
|
|
|
|
Constructor
|
|
|
|
|
|
|
|
|
|
@param message:str The message spoken by the pony
|
|
|
|
|
@param ponyfile:str The pony file
|
|
|
|
|
@param wrapcolumn:int The column at where to wrap the message, `None` for no wrapping
|
|
|
|
|
@param width:int The width of the screen, `None` if truncation should not be applied
|
|
|
|
|
@param balloon:Balloon The balloon style object, `None` if only the pony should be printed
|
|
|
|
|
@param hyphen:str How hyphens added by the wordwrapper should be printed
|
|
|
|
|
@param linkcolour:str How to colour the link character, empty string if none
|
|
|
|
|
@param ballooncolour:str How to colour the balloon, empty string if none
|
|
|
|
|
@param mode:str Mode string for the pony
|
|
|
|
|
@parma infolevel:int 2 if ++info is used, 1 if --info is used and 0 otherwise
|
|
|
|
|
'''
|
|
|
|
|
def __init__(self, message, ponyfile, wrapcolumn, width, balloon, hyphen, linkcolour, ballooncolour, mode, infolevel):
|
|
|
|
|
self.message = message
|
|
|
|
|
self.ponyfile = ponyfile
|
|
|
|
|
self.wrapcolumn = None if wrapcolumn is None else wrapcolumn - (0 if balloon is None else balloon.minwidth)
|
|
|
|
|
self.width = width
|
|
|
|
|
self.balloon = balloon
|
|
|
|
|
self.hyphen = hyphen
|
|
|
|
|
self.ballooncolour = ballooncolour
|
|
|
|
|
self.mode = mode
|
|
|
|
|
self.balloontop = 0
|
|
|
|
|
self.balloonbottom = 0
|
|
|
|
|
self.infolevel = infolevel
|
|
|
|
|
|
|
|
|
|
if self.balloon is not None:
|
|
|
|
|
self.link = {'\\' : linkcolour + self.balloon.link,
|
|
|
|
|
'/' : linkcolour + self.balloon.linkmirror}
|
|
|
|
|
else:
|
|
|
|
|
self.link = {}
|
|
|
|
|
|
|
|
|
|
self.output = ''
|
|
|
|
|
self.pony = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Process all data
|
|
|
|
|
'''
|
|
|
|
|
def parse(self):
|
|
|
|
|
self.__loadFile()
|
|
|
|
|
|
|
|
|
|
if self.pony.startswith('$$$\n'):
|
|
|
|
|
self.pony = self.pony[4:]
|
|
|
|
|
if self.pony.startswith('$$$\n'):
|
|
|
|
|
infoend = 4
|
|
|
|
|
info = ''
|
|
|
|
|
else:
|
|
|
|
|
infoend = self.pony.index('\n$$$\n')
|
|
|
|
|
info = self.pony[:infoend]
|
|
|
|
|
infoend += 5
|
|
|
|
|
if self.infolevel == 2:
|
|
|
|
|
self.message = Backend.formatInfo(info)
|
|
|
|
|
elif self.infolevel == 1:
|
|
|
|
|
self.pony = Backend.formatInfo(info).replace('$', '$$')
|
|
|
|
|
else:
|
|
|
|
|
info = info.split('\n')
|
|
|
|
|
for line in info:
|
|
|
|
|
sep = line.find(':')
|
|
|
|
|
if sep > 0:
|
|
|
|
|
key = line[:sep].strip()
|
|
|
|
|
if key == 'BALLOON TOP':
|
|
|
|
|
value = line[sep + 1:].strip()
|
|
|
|
|
if len(value) > 0:
|
|
|
|
|
self.balloontop = int(value)
|
|
|
|
|
if key == 'BALLOON BOTTOM':
|
|
|
|
|
value = line[sep + 1:].strip()
|
|
|
|
|
if len(value) > 0:
|
|
|
|
|
self.balloonbottom = int(value)
|
|
|
|
|
printinfo(info)
|
|
|
|
|
self.pony = self.pony[infoend:]
|
|
|
|
|
elif self.infolevel == 2:
|
|
|
|
|
self.message = '\033[01;31mI am the mysterious mare...\033[21;39m'
|
|
|
|
|
elif self.infolevel == 1:
|
|
|
|
|
self.pony = 'There is not metadata for this pony file'
|
|
|
|
|
self.pony = self.mode + self.pony
|
|
|
|
|
|
|
|
|
|
self.__expandMessage()
|
|
|
|
|
self.__unpadMessage()
|
|
|
|
|
self.__processPony()
|
|
|
|
|
self.__truncate()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Format metadata to be nicely printed, this include bold keys
|
|
|
|
|
|
|
|
|
|
@param info:str The metadata
|
|
|
|
|
@return :str The metadata nicely formated
|
|
|
|
|
'''
|
|
|
|
|
@staticmethod
|
|
|
|
|
def formatInfo(info):
|
|
|
|
|
info = info.split('\n')
|
|
|
|
|
tags = ''
|
|
|
|
|
comment = ''
|
|
|
|
|
for line in info:
|
|
|
|
|
sep = line.find(':')
|
|
|
|
|
if sep > 0:
|
|
|
|
|
key = line[:sep]
|
|
|
|
|
test = key
|
|
|
|
|
for c in 'ABCDEFGHIJKLMN OPQRSTUVWXYZ':
|
|
|
|
|
test = test.replace(c, '')
|
|
|
|
|
if (len(test) == 0) and (len(key.replace(' ', '')) > 0):
|
|
|
|
|
value = line[sep + 1:].strip()
|
|
|
|
|
line = '\033[1m%s\033[21m: %s\n' % (key.strip(), value)
|
|
|
|
|
tags += line
|
|
|
|
|
continue
|
|
|
|
|
comment += '\n' + line
|
|
|
|
|
comment = comment.lstrip('\n')
|
|
|
|
|
if len(comment) > 0:
|
|
|
|
|
comment = '\n' + comment
|
|
|
|
|
return tags + comment
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
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
|
|
|
|
|
'''
|
|
|
|
|
def __expandMessage(self):
|
|
|
|
|
lines = self.message.split('\n')
|
|
|
|
|
buf = ''
|
|
|
|
|
for line in lines:
|
|
|
|
|
(i, n, x) = (0, len(line), 0)
|
|
|
|
|
while i < n:
|
|
|
|
|
c = line[i]
|
|
|
|
|
i += 1
|
|
|
|
|
if c == '\033':
|
|
|
|
|
colour = Backend.getcolour(line, i - 1)
|
|
|
|
|
i += len(colour) - 1
|
|
|
|
|
buf += colour
|
|
|
|
|
elif c == '\t':
|
|
|
|
|
nx = 8 - (x & 7)
|
|
|
|
|
buf += ' ' * nx
|
|
|
|
|
x += nx
|
|
|
|
|
else:
|
|
|
|
|
buf += c
|
|
|
|
|
if not UCS.isCombining(c):
|
|
|
|
|
x += 1
|
|
|
|
|
buf += '\n'
|
|
|
|
|
self.message = buf[:-1]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Loads the pony file
|
|
|
|
|
'''
|
|
|
|
|
def __loadFile(self):
|
|
|
|
|
with open(self.ponyfile, 'rb') as ponystream:
|
|
|
|
|
self.pony = ponystream.read().decode('utf8', 'replace')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Truncate output to the width of the screen
|
|
|
|
|
'''
|
|
|
|
|
def __truncate(self):
|
|
|
|
|
if self.width is None:
|
|
|
|
|
return
|
|
|
|
|
lines = self.output.split('\n')
|
|
|
|
|
self.output = ''
|
|
|
|
|
for line in lines:
|
|
|
|
|
(i, n, x) = (0, len(line), 0)
|
|
|
|
|
while i < n:
|
|
|
|
|
c = line[i]
|
|
|
|
|
i += 1
|
|
|
|
|
if c == '\033':
|
|
|
|
|
colour = Backend.getcolour(line, i - 1)
|
|
|
|
|
i += len(colour) - 1
|
|
|
|
|
self.output += colour
|
|
|
|
|
else:
|
|
|
|
|
if x < self.width:
|
|
|
|
|
self.output += c
|
|
|
|
|
if not UCS.isCombining(c):
|
|
|
|
|
x += 1
|
|
|
|
|
self.output += '\n'
|
|
|
|
|
self.output = self.output[:-1]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Process the pony file and generate output to self.output
|
|
|
|
|
'''
|
|
|
|
|
def __processPony(self):
|
|
|
|
|
self.output = ''
|
|
|
|
|
|
|
|
|
|
AUTO_PUSH = '\033[01010~'
|
|
|
|
|
AUTO_POP = '\033[10101~'
|
|
|
|
|
|
|
|
|
|
variables = {'' : '$'}
|
|
|
|
|
for key in self.link:
|
|
|
|
|
variables[key] = AUTO_PUSH + self.link[key] + AUTO_POP
|
|
|
|
|
|
|
|
|
|
indent = 0
|
|
|
|
|
dollar = None
|
|
|
|
|
balloonLines = None
|
|
|
|
|
colourstack = ColourStack(AUTO_PUSH, AUTO_POP)
|
|
|
|
|
|
|
|
|
|
(i, n, lineindex, skip, nonskip) = (0, len(self.pony), 0, 0, 0)
|
|
|
|
|
while i < n:
|
|
|
|
|
c = self.pony[i]
|
|
|
|
|
if c == '\t':
|
|
|
|
|
n += 7 - (indent & 7)
|
|
|
|
|
ed = ' ' * (8 - (indent & 7))
|
|
|
|
|
c = ' '
|
|
|
|
|
self.pony = self.pony[:i] + ed + self.pony[i + 1:]
|
|
|
|
|
i += 1
|
|
|
|
|
if c == '$':
|
|
|
|
|
if dollar is not None:
|
|
|
|
|
if '=' in dollar:
|
|
|
|
|
name = dollar[:dollar.find('=')]
|
|
|
|
|
value = dollar[dollar.find('=') + 1:]
|
|
|
|
|
variables[name] = value
|
|
|
|
|
elif not dollar.startswith('balloon'):
|
|
|
|
|
data = variables[dollar].replace('$', '$$')
|
|
|
|
|
if data == '$$': # if not handled specially we will get an infinity loop
|
|
|
|
|
if (skip == 0) or (nonskip > 0):
|
|
|
|
|
if nonskip > 0:
|
|
|
|
|
nonskip -= 1
|
|
|
|
|
self.output += '$'
|
|
|
|
|
indent += 1
|
|
|
|
|
else:
|
|
|
|
|
skip -= 1
|
|
|
|
|
else:
|
|
|
|
|
n += len(data)
|
|
|
|
|
self.pony = self.pony[:i] + data + self.pony[i:]
|
|
|
|
|
elif self.balloon is not None:
|
|
|
|
|
(w, h, x, justify) = ('0', 0, 0, None)
|
|
|
|
|
props = dollar[7:]
|
|
|
|
|
if len(props) > 0:
|
|
|
|
|
if ',' in props:
|
|
|
|
|
if props[0] is not ',':
|
|
|
|
|
w = props[:props.index(',')]
|
|
|
|
|
h = int(props[props.index(',') + 1:])
|
|
|
|
|
else:
|
|
|
|
|
w = props
|
|
|
|
|
if 'l' in w:
|
|
|
|
|
(x, w) = (int(w[:w.find('l')]), int(w[w.find('l') + 1:]))
|
|
|
|
|
justify = 'l'
|
|
|
|
|
w -= x;
|
|
|
|
|
elif 'c' in w:
|
|
|
|
|
(x, w) = (int(w[:w.find('c')]), int(w[w.find('c') + 1:]))
|
|
|
|
|
justify = 'c'
|
|
|
|
|
w -= x;
|
|
|
|
|
elif 'r' in w:
|
|
|
|
|
(x, w) = (int(w[:w.find('r')]), int(w[w.find('r') + 1:]))
|
|
|
|
|
justify = 'r'
|
|
|
|
|
w -= x;
|
|
|
|
|
else:
|
|
|
|
|
w = int(w)
|
|
|
|
|
balloon = self.__getballoon(w, h, x, justify, indent)
|
|
|
|
|
balloon = balloon.split('\n')
|
|
|
|
|
balloon = [AUTO_PUSH + self.ballooncolour + item + AUTO_POP for item in balloon]
|
|
|
|
|
for b in balloon[0]:
|
|
|
|
|
self.output += b + colourstack.feed(b)
|
|
|
|
|
if lineindex == 0:
|
|
|
|
|
balloonpre = '\n' + (' ' * indent)
|
|
|
|
|
for line in balloon[1:]:
|
|
|
|
|
self.output += balloonpre;
|
|
|
|
|
for b in line:
|
|
|
|
|
self.output += b + colourstack.feed(b);
|
|
|
|
|
indent = 0
|
|
|
|
|
elif len(balloon) > 1:
|
|
|
|
|
balloonLines = balloon
|
|
|
|
|
balloonLine = 0
|
|
|
|
|
balloonIndent = indent
|
|
|
|
|
indent += Backend.len(balloonLines[0])
|
|
|
|
|
balloonLines[0] = None
|
|
|
|
|
dollar = None
|
|
|
|
|
else:
|
|
|
|
|
dollar = ''
|
|
|
|
|
elif dollar is not None:
|
|
|
|
|
if c == '\033':
|
|
|
|
|
c = self.pony[i]
|
|
|
|
|
i += 1
|
|
|
|
|
dollar += c
|
|
|
|
|
elif c == '\033':
|
|
|
|
|
colour = Backend.getcolour(self.pony, i - 1)
|
|
|
|
|
for b in colour:
|
|
|
|
|
self.output += b + colourstack.feed(b);
|
|
|
|
|
i += len(colour) - 1
|
|
|
|
|
elif c == '\n':
|
|
|
|
|
self.output += c
|
|
|
|
|
indent = 0
|
|
|
|
|
(skip, nonskip) = (0, 0)
|
|
|
|
|
lineindex += 1
|
|
|
|
|
if balloonLines is not None:
|
|
|
|
|
balloonLine += 1
|
|
|
|
|
if balloonLine == len(balloonLines):
|
|
|
|
|
balloonLines = None
|
|
|
|
|
else:
|
|
|
|
|
if (balloonLines is not None) and (balloonLines[balloonLine] is not None) and (balloonIndent == indent):
|
|
|
|
|
data = balloonLines[balloonLine]
|
|
|
|
|
datalen = Backend.len(data)
|
|
|
|
|
skip += datalen
|
|
|
|
|
nonskip += datalen
|
|
|
|
|
data = data.replace('$', '$$')
|
|
|
|
|
n += len(data)
|
|
|
|
|
self.pony = self.pony[:i] + data + self.pony[i:]
|
|
|
|
|
balloonLines[balloonLine] = None
|
|
|
|
|
else:
|
|
|
|
|
if (skip == 0) or (nonskip > 0):
|
|
|
|
|
if nonskip > 0:
|
|
|
|
|
nonskip -= 1
|
|
|
|
|
self.output += c + colourstack.feed(c);
|
|
|
|
|
if not UCS.isCombining(c):
|
|
|
|
|
indent += 1
|
|
|
|
|
else:
|
|
|
|
|
skip -= 1
|
|
|
|
|
|
|
|
|
|
if balloonLines is not None:
|
|
|
|
|
for line in balloonLines[balloonLine:]:
|
|
|
|
|
data = ' ' * (balloonIndent - indent) + line + '\n'
|
|
|
|
|
for b in data:
|
|
|
|
|
self.output += b + colourstack.feed(b);
|
|
|
|
|
indent = 0
|
|
|
|
|
|
|
|
|
|
self.output = self.output.replace(AUTO_PUSH, '').replace(AUTO_POP, '')
|
|
|
|
|
|
2013-04-03 23:49:21 +02:00
|
|
|
|
if self.balloon is None:
|
2013-04-02 11:21:33 +02:00
|
|
|
|
if (self.balloontop > 0) or (self.balloonbottom > 0):
|
|
|
|
|
self.output = self.output.split('\n')
|
|
|
|
|
self.output = self.output[self.balloontop : ~(self.balloonbottom)]
|
|
|
|
|
self.output = '\n'.join(self.output)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Gets colour code att the currect offset in a buffer
|
|
|
|
|
|
|
|
|
|
@param input:str The input buffer
|
|
|
|
|
@param offset:int The offset at where to start reading, a escape must begin here
|
|
|
|
|
@return :str The escape sequence
|
|
|
|
|
'''
|
|
|
|
|
@staticmethod
|
|
|
|
|
def getcolour(input, offset):
|
|
|
|
|
(i, n) = (offset, len(input))
|
|
|
|
|
rc = input[i]
|
|
|
|
|
i += 1
|
|
|
|
|
if i == n: return rc
|
|
|
|
|
c = input[i]
|
|
|
|
|
i += 1
|
|
|
|
|
rc += c
|
|
|
|
|
|
|
|
|
|
if c == ']':
|
|
|
|
|
if i == n: return rc
|
|
|
|
|
c = input[i]
|
|
|
|
|
i += 1
|
|
|
|
|
rc += c
|
|
|
|
|
if c == 'P':
|
|
|
|
|
di = 0
|
|
|
|
|
while (di < 7) and (i < n):
|
|
|
|
|
c = input[i]
|
|
|
|
|
i += 1
|
|
|
|
|
di += 1
|
|
|
|
|
rc += c
|
|
|
|
|
while c == '0':
|
|
|
|
|
c = input[i]
|
|
|
|
|
i += 1
|
|
|
|
|
rc += c
|
|
|
|
|
if c == '4':
|
|
|
|
|
c = input[i]
|
|
|
|
|
i += 1
|
|
|
|
|
rc += c
|
|
|
|
|
if c == ';':
|
|
|
|
|
c = input[i]
|
|
|
|
|
i += 1
|
|
|
|
|
rc += c
|
|
|
|
|
while c != '\\':
|
|
|
|
|
c = input[i]
|
|
|
|
|
i += 1
|
|
|
|
|
rc += c
|
|
|
|
|
elif c == '[':
|
|
|
|
|
while i < n:
|
|
|
|
|
c = input[i]
|
|
|
|
|
i += 1
|
|
|
|
|
rc += c
|
|
|
|
|
if (c == '~') or (('a' <= c) and (c <= 'z')) or (('A' <= c) and (c <= 'Z')):
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
return rc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Calculates the number of visible characters in a text
|
|
|
|
|
|
|
|
|
|
@param input:str The input buffer
|
|
|
|
|
@return :int The number of visible characters
|
|
|
|
|
'''
|
|
|
|
|
@staticmethod
|
|
|
|
|
def len(input):
|
|
|
|
|
(rc, i, n) = (0, 0, len(input))
|
|
|
|
|
while i < n:
|
|
|
|
|
c = input[i]
|
|
|
|
|
if c == '\033':
|
|
|
|
|
i += len(Backend.getcolour(input, i))
|
|
|
|
|
else:
|
|
|
|
|
i += 1
|
|
|
|
|
if not UCS.isCombining(c):
|
|
|
|
|
rc += 1
|
|
|
|
|
return rc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Generates a balloon with the message
|
|
|
|
|
|
|
|
|
|
@param width:int The minimum width of the balloon
|
|
|
|
|
@param height:int The minimum height of the balloon
|
|
|
|
|
@param innerleft:int The left column of the required span, excluding that of `left`
|
|
|
|
|
@param justify:str Balloon placement justification, 'c' → centered,
|
|
|
|
|
'l' → left (expand to right), 'r' → right (expand to left)
|
|
|
|
|
@param left:int The column where the balloon starts
|
|
|
|
|
@return :str The balloon the the message as a string
|
|
|
|
|
'''
|
|
|
|
|
def __getballoon(self, width, height, innerleft, justify, left):
|
|
|
|
|
wrap = None
|
|
|
|
|
if self.wrapcolumn is not None:
|
|
|
|
|
wrap = self.wrapcolumn - left
|
|
|
|
|
if wrap < 8:
|
|
|
|
|
wrap = 8
|
|
|
|
|
|
|
|
|
|
msg = self.message
|
|
|
|
|
if wrap is not None:
|
|
|
|
|
msg = self.__wrapMessage(msg, wrap)
|
|
|
|
|
|
|
|
|
|
msg = msg.replace('\n', '\033[0m%s\n' % (self.ballooncolour)) + '\033[0m' + self.ballooncolour
|
|
|
|
|
msg = msg.split('\n')
|
|
|
|
|
|
|
|
|
|
extraleft = 0
|
|
|
|
|
if justify is not None:
|
|
|
|
|
msgwidth = self.len(max(msg, key = self.len)) + self.balloon.minwidth
|
|
|
|
|
extraleft = innerleft
|
|
|
|
|
if msgwidth > width:
|
|
|
|
|
if (justify == 'l') and (wrap is not None):
|
|
|
|
|
if innerleft + msgwidth > wrap:
|
|
|
|
|
extraleft -= msgwidth - wrap
|
|
|
|
|
elif justify == 'r':
|
|
|
|
|
extraleft -= msgwidth - width
|
|
|
|
|
elif justify == 'c':
|
|
|
|
|
extraleft -= (msgwidth - width) >> 1
|
|
|
|
|
if extraleft < 0:
|
|
|
|
|
extraleft = 0
|
|
|
|
|
if wrap is not None:
|
|
|
|
|
if extraleft + msgwidth > wrap:
|
|
|
|
|
extraleft -= msgwidth - wrap
|
|
|
|
|
|
|
|
|
|
rc = self.balloon.get(width, height, msg, Backend.len);
|
|
|
|
|
if extraleft > 0:
|
|
|
|
|
rc = ' ' * extraleft + rc.replace('\n', '\n' + ' ' * extraleft)
|
|
|
|
|
return rc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
Wraps the message
|
|
|
|
|
|
|
|
|
|
@param message:str The message to wrap
|
|
|
|
|
@param wrap:int The width at where to force wrapping
|
|
|
|
|
@return :str The message wrapped
|
|
|
|
|
'''
|
|
|
|
|
def __wrapMessage(self, message, wrap):
|
|
|
|
|
wraplimit = os.environ['PONYSAY_WRAP_LIMIT'] if 'PONYSAY_WRAP_LIMIT' in os.environ else ''
|
|
|
|
|
wraplimit = 8 if len(wraplimit) == 0 else int(wraplimit)
|
|
|
|
|
|
|
|
|
|
wrapexceed = os.environ['PONYSAY_WRAP_EXCEED'] if 'PONYSAY_WRAP_EXCEED' in os.environ else ''
|
|
|
|
|
wrapexceed = 5 if len(wrapexceed) == 0 else int(wrapexceed)
|
|
|
|
|
|
|
|
|
|
buf = ''
|
|
|
|
|
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 = ''
|
|
|
|
|
|
|
|
|
|
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 == '\033':
|
|
|
|
|
## Invisible stuff
|
|
|
|
|
i -= 1
|
|
|
|
|
colourseq = Backend.getcolour(line, i)
|
|
|
|
|
b[bi : bi + len(colourseq)] = colourseq
|
|
|
|
|
i += len(colourseq)
|
|
|
|
|
bi += len(colourseq)
|
|
|
|
|
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 > wraplimit) and (cols > w + wrapexceed)) or (cols > iwrap):
|
|
|
|
|
## wrap
|
|
|
|
|
x = w;
|
|
|
|
|
if mm + x not in map: # Too much whitespace?
|
|
|
|
|
cols = 0
|
|
|
|
|
break
|
|
|
|
|
nbsp = b[map[mm + x]] == ' ' # nbsp
|
|
|
|
|
m = map[mm + x]
|
|
|
|
|
|
|
|
|
|
if ('' in b[bisub : m]) and not nbsp: # sort hyphen
|
|
|
|
|
hyphen = m - 1
|
|
|
|
|
while b[hyphen] != '': # sort 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]
|
|
|
|
|
|
|
|
|
|
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]:
|
|
|
|
|
if bb is not None:
|
|
|
|
|
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
|
|
|
|
|
|