Signed-off-by: Mattias Andrée <maandree@operamail.com>
This commit is contained in:
Mattias Andrée 2013-04-03 10:33:57 +02:00
parent 5456154b80
commit 64b7b6a2ec
10 changed files with 652 additions and 530 deletions

3
.gitignore vendored
View file

@ -28,6 +28,9 @@
*.dvi
/Makefile
/quotes/
/*ponies/heights
/*ponies/onlyheights
/*ponies/widths
## Texinfo manual stuff

View file

@ -2850,7 +2850,7 @@ English, e.g. British English, as the base language.
@item best.pony
@cindex best.pony
The pony you think is [the] best pony. It should be a symlink pony. It is a feature
affecting the @option{-f}, @option{+f} and @option{-q} options.
affecting the @option{-f}, @option{+f}, @option{-F} and @option{-q} options.
@item pony symlink
@itemx symlink pony

View file

@ -39,7 +39,8 @@ miscfiles = [('COPYING', '/usr/share/licenses/ponysay/COPYING'),
ponysaysrc = [src + '.py' for src in
('__main__', 'common', 'ponysay', 'argparser', 'balloon',
'backend', 'colourstack', 'ucs', 'spellocorrecter')]
'backend', 'colourstack', 'ucs', 'spellocorrecter, kms',
'list', 'metadata')]
@ -428,7 +429,7 @@ class Setup():
if filein is not None: filein .close()
try:
os.chdir('src')
os.system('zip ../ponysay.zip ' + ' '.join(ponysaysrc))
os.system('zip -0 ../ponysay.zip ' + ' '.join(ponysaysrc)) # use not compress, prefer speed
finally:
os.chdir('..')
try:

View file

@ -113,3 +113,45 @@ class Balloon():
return '\n'.join(rc)
'''
Creates the balloon style object
@param balloonfile:str The file with the balloon style, may be `None`
@param isthink:bool Whether the ponythink command is used
@return :Balloon Instance describing the balloon's style
'''
@staticmethod
def fromfile(balloonfile, isthink):
## Use default balloon if none is specified
if balloonfile is None:
if isthink:
return Balloon('o', 'o', '( ', ' )', [' _'], ['_'], ['_'], ['_'], ['_ '], ' )', ' )', ' )', ['- '], ['-'], ['-'], ['-'], [' -'], '( ', '( ', '( ')
return Balloon('\\', '/', '< ', ' >', [' _'], ['_'], ['_'], ['_'], ['_ '], ' \\', ' |', ' /', ['- '], ['-'], ['-'], ['-'], [' -'], '\\ ', '| ', '/ ')
## Initialise map for balloon parts
map = {}
for elem in ('\\', '/', 'ww', 'ee', 'nw', 'nnw', 'n', 'nne', 'ne', 'nee', 'e', 'see', 'se', 'sse', 's', 'ssw', 'sw', 'sww', 'w', 'nww'):
map[elem] = []
## Read all lines in the balloon file
with open(balloonfile, 'rb') as balloonstream:
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
for line in data:
if len(line) > 0:
if line[0] == ':':
map[last].append(line[1:])
else:
last = line[:line.index(':')]
value = line[len(last) + 1:]
map[last].append(value)
## Return the balloon
return Balloon(map['\\'][0], map['/'][0], map['ww'][0], map['ee'][0], map['nw'], map['nnw'], map['n'],
map['nne'], map['ne'], map['nee'][0], map['e'][0], map['see'][0], map['se'], map['sse'],
map['s'], map['ssw'], map['sw'], map['sww'][0], map['w'][0], map['nww'][0])

View file

@ -72,3 +72,19 @@ Checks whether a text ends with a specific text, but has more
def endswith(text, ending):
return text.endswith(ending) and not (text == ending)
'''
Gets the size of the terminal in (rows, columns)
@return (rows, columns):(int, int) The number or lines and the number of columns in the terminal's display area
'''
def gettermsize():
## Call `stty` to determine the size of the terminal, this way is better than using python's ncurses
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

142
src/kms.py Normal file
View file

@ -0,0 +1,142 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
ponysay - Ponysay, cowsay reimplementation for ponies
Copyright (C) 2012, 2013 Erkin Batu Altunbaş et al.
This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.
'''
from common import *
'''
KMS support utilisation
'''
class KMS():
'''
Identifies whether KMS support is utilised
@param linuxvt:bool Whether Linux VT is used
@return :bool Whether KMS support is utilised
'''
@staticmethod
def usingkms(linuxvt):
## KMS is not utilised if Linux VT is not used
if not linuxvt:
return False
## Read the PONYSAY_KMS_PALETTE environment variable
env_kms = os.environ['PONYSAY_KMS_PALETTE'] if 'PONYSAY_KMS_PALETTE' in os.environ else None
if env_kms is None: env_kms = ''
## Read the PONYSAY_KMS_PALETTE_CMD environment variable, and run it
env_kms_cmd = os.environ['PONYSAY_KMS_PALETTE_CMD'] if 'PONYSAY_KMS_PALETTE_CMD' in os.environ else None
if (env_kms_cmd is not None) and (not env_kms_cmd == ''):
env_kms = Popen(shlex.split(env_kms_cmd), stdout=PIPE, stdin=sys.stderr).communicate()[0].decode('utf8', 'replace')
if env_kms[-1] == '\n':
env_kms = env_kms[:-1]
## If the palette string is empty KMS is not utilised
return env_kms != ''
'''
Returns the file name of the input pony converted to a KMS pony, or if KMS is not used, the input pony itself
@param pony:str Choosen pony file
@param home:str The home directory
@param linuxvt:bool Whether Linux VT is used
@return :str Pony file to display
'''
@staticmethod
def kms(pony, home, linuxvt):
## If not in Linux VT, return the pony as is
if not linuxvt:
return pony
## KMS support version constant
KMS_VERSION = '2'
## Read the PONYSAY_KMS_PALETTE environment variable
env_kms = os.environ['PONYSAY_KMS_PALETTE'] if 'PONYSAY_KMS_PALETTE' in os.environ else None
if env_kms is None: env_kms = ''
## Read the PONYSAY_KMS_PALETTE_CMD environment variable, and run it
env_kms_cmd = os.environ['PONYSAY_KMS_PALETTE_CMD'] if 'PONYSAY_KMS_PALETTE_CMD' in os.environ else None
if (env_kms_cmd is not None) and (not env_kms_cmd == ''):
env_kms = Popen(shlex.split(env_kms_cmd), stdout=PIPE, stdin=sys.stderr).communicate()[0].decode('utf8', 'replace')
if env_kms[-1] == '\n':
env_kms = env_kms[:-1]
## If not using KMS, return the pony as is
if env_kms == '':
return pony
## Store palette string and a clone with just the essentials
palette = env_kms
palettefile = env_kms.replace('\033]P', '')
## Get and in necessary make cache directory
cachedir = '/var/cache/ponysay'
shared = True
if not os.path.isdir(cachedir):
cachedir = home + '/.cache/ponysay'
shared = False
if not os.path.isdir(cachedir):
os.makedirs(cachedir)
_cachedir = '\'' + cachedir.replace('\'', '\'\\\'\'') + '\''
## KMS support version control, clean everything if not matching
newversion = False
if not os.path.isfile(cachedir + '/.version'):
newversion = True
else:
with open(cachedir + '/.version', 'rb') as cachev:
if cachev.read().decode('utf8', 'replace').replace('\n', '') != KMS_VERSION:
newversion = True
if newversion:
for cached in os.listdir(cachedir):
cached = cachedir + '/' + cached
if os.path.isdir(cached) and not os.path.islink(cached):
shutil.rmtree(cached, False)
else:
os.remove(cached)
with open(cachedir + '/.version', 'w+') as cachev:
cachev.write(KMS_VERSION)
if shared:
Popen('chmod 666 -- ' + _cachedir + '/.version', shell=True).wait()
## Get kmspony directory and kmspony file
kmsponies = cachedir + '/kmsponies/' + palettefile
kmspony = (kmsponies + pony).replace('//', '/')
## If the kmspony is missing, create it
if not os.path.isfile(kmspony):
## kmspony directory
kmsponydir = kmspony[:kmspony.rindex('/')]
## Change file names to be shell friendly
_kmspony = '\'' + kmspony.replace('\'', '\'\\\'\'') + '\''
_pony = '\'' + pony.replace('\'', '\'\\\'\'') + '\''
## Create kmspony
if not os.path.isdir(kmsponydir):
os.makedirs(kmsponydir)
if shared:
Popen('chmod -R 6777 -- ' + _cachedir, shell=True).wait()
ponytoolcmd = 'ponytool --import ponysay --file %s --export ponysay --file %s --platform linux '
ponytoolcmd += '--balloon n --colourful y --fullcolour y --left - --right - --top - --bottom - --palette %s'
if not os.system(ponytoolcmd % (_pony, _kmspony, palette)) == 0:
printerr('Unable to run ponytool successfully, you need util-say>=3 for KMS support')
exit(1)
if shared:
Popen('chmod 666 -- ' + _kmspony, shell=True).wait()
return kmspony

251
src/list.py Normal file
View file

@ -0,0 +1,251 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
ponysay - Ponysay, cowsay reimplementation for ponies
Copyright (C) 2012, 2013 Erkin Batu Altunbaş et al.
This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.
'''
from common import *
from ucs import *
'''
File listing functions
'''
class List():
'''
Columnise a list and prints it
@param ponies:list<(str, str)> All items to list, each item should have to elements: unformated name, formated name
'''
@staticmethod
def __columnise(ponies):
## Get terminal width, and a 2 which is the space between columns
termwidth = gettermsize()[1] + 2
## Sort the ponies, and get the cells' widths, and the largest width + 2
ponies.sort(key = lambda pony : pony[0])
widths = [UCS.dispLen(pony[0]) for pony in ponies]
width = max(widths) + 2 # longest pony file name + space between columns
## Calculate the number of rows and columns, can create a list of empty columns
cols = termwidth // width # do not believe electricians, this means ⌊termwidth / width⌋
rows = (len(ponies) + cols - 1) // cols
columns = []
for c in range(0, cols): columns.append([])
## Fill the columns with cells of ponies
(y, x) = (0, 0)
for j in range(0, len(ponies)):
cell = ponies[j][1] + ' ' * (width - widths[j]);
columns[x].append(cell)
y += 1
if y == rows:
x += 1
y = 0
## Make the columnisation nicer by letting the last row be partially empty rather than the last column
diff = rows * cols - len(ponies)
if (diff > 2) and (rows > 1):
c = cols - 1
diff -= 1
while diff > 0:
columns[c] = columns[c - 1][-diff:] + columns[c]
c -= 1
columns[c] = columns[c][:-diff]
diff -= 1
## Create rows from columns
lines = []
for r in range(0, rows):
lines.append([])
for c in range(0, cols):
if r < len(columns[c]):
line = lines[r].append(columns[c][r])
## Print the matrix, with one extra blank row
print('\n'.join([''.join(line)[:-2] for line in lines]))
print()
'''
Lists the available ponies
@param ponydirs:itr<str> The pony directories to use
@param quoters:__in__(str)bool Set of ponies that of quotes
@param ucsiser:(list<str>)?void Function used to UCS:ise names
'''
@staticmethod
def simplelist(ponydirs, quoters = [], ucsiser = None):
for ponydir in ponydirs: # Loop ponydirs
## Get all ponies in the directory
_ponies = os.listdir(ponydir)
## Remove .pony from all files and skip those that does not have .pony
ponies = []
for pony in _ponies:
if endswith(pony, '.pony'):
ponies.append(pony[:-5])
## UCS:ise pony names, they are already sorted
if ucsiser is not None:
ucsiser(ponies)
## If ther directory is not empty print its name and all ponies, columnised
if len(ponies) == 0:
continue
print('\033[1mponies located in ' + ponydir + '\033[21m')
List.__columnise([(pony, '\033[1m' + pony + '\033[21m' if pony in quoters else pony) for pony in ponies])
'''
Lists the available ponies with alternatives inside brackets
@param ponydirs:itr<str> The pony directories to use
@param quoters:__in__(str)bool Set of ponies that of quotes
@param ucsiser:(list<str>, map<str, str>)?void Function used to UCS:ise names
'''
@staticmethod
def linklist(ponydirs = None, quoters = [], ucsiser = None):
## Get the size of the terminal
termsize = gettermsize()
for ponydir in ponydirs: # Loop ponydirs
## Get all pony files in the directory
_ponies = os.listdir(ponydir)
## Remove .pony from all files and skip those that does not have .pony
ponies = []
for pony in _ponies:
if endswith(pony, '.pony'):
ponies.append(pony[:-5])
## If there are no ponies in the directory skip to next directory, otherwise, print the directories name
if len(ponies) == 0:
continue
print('\033[1mponies located in ' + ponydir + '\033[21m')
## UCS:ise pony names
pseudolinkmap = {}
if ucsiser is not None:
ucsiser(ponies, pseudolinkmap)
## Create targetlink-pair, with `None` as link if the file is not a symlink or in `pseudolinkmap`
pairs = []
for pony in ponies:
if pony in pseudolinkmap:
pairs.append((pony, pseudolinkmap[pony] + '.pony'));
else:
pairs.append((pony, os.path.realpath(ponydir + pony + '.pony') if os.path.islink(ponydir + pony + '.pony') else None))
## Create map from source pony to alias ponies for each pony
ponymap = {}
for pair in pairs:
if (pair[1] is None) or (pair[1] == ''):
if pair[0] not in ponymap:
ponymap[pair[0]] = []
else:
target = pair[1][:-5]
if '/' in target:
target = target[target.rindex('/') + 1:]
if target in ponymap:
ponymap[target].append(pair[0])
else:
ponymap[target] = [pair[0]]
## Create list of source ponies concatenated with alias ponies in brackets
ponies = {}
for pony in ponymap:
w = UCS.dispLen(pony)
item = '\033[1m' + pony + '\033[21m' if (pony in quoters) else pony
syms = ponymap[pony]
syms.sort()
if len(syms) > 0:
w += 2 + len(syms)
item += ' ('
first = True
for sym in syms:
w += UCS.dispLen(sym)
if first: first = False
else: item += ' '
item += '\033[1m' + sym + '\033[21m' if (sym in quoters) else sym
item += ')'
ponies[(item.replace('\033[1m', '').replace('\033[21m', ''), item)] = w
## Print the ponies, columnised
List.__columnise(list(ponies))
'''
Lists the available ponies on one column without anything bold or otherwise formated
@param standard:itr<str>? Include standard ponies
@param extra:itr<str>? Include extra ponies
@param ucsiser:(list<str>)?void Function used to UCS:ise names
'''
@staticmethod
def onelist(standarddirs, extradirs = None, ucsiser = None):
## Get all pony files
_ponies = []
if standarddirs is not None:
for ponydir in standarddirs:
_ponies += os.listdir(ponydir)
if extradirs is not None:
for ponydir in extradirs:
_ponies += os.listdir(ponydir)
## Remove .pony from all files and skip those that does not have .pony
ponies = []
for pony in _ponies:
if endswith(pony, '.pony'):
ponies.append(pony[:-5])
## UCS:ise and sort
if ucsiser is not None:
ucsiser(ponies)
ponies.sort()
## Print each one on a seperate line, but skip duplicates
last = ''
for pony in ponies:
if not pony == last:
last = pony
print(pony)
'''
Prints a list of all balloons
@param balloondirs:itr<str> The balloon directories to use
@param isthink:bool Whether the ponythink command is used
'''
@staticmethod
def balloonlist(balloondirs, isthink):
## Get the size of the terminal
termsize = gettermsize()
## Get all balloons
balloonset = set()
for balloondir in self.balloondirs:
for balloon in os.listdir(balloondir):
## Use .think if running ponythink, otherwise .say
if isthink and endswith(balloon, '.think'):
balloon = balloon[:-6]
elif (not isthink) and endswith(balloon, '.say'):
balloon = balloon[:-4]
else:
continue
## Add the balloon if there is none with the same name
if balloon not in balloonset:
balloonset.add(balloon)
## Print all balloos, columnised
List.__columnise([(balloon, balloon) for balloon in list(balloonset)])

103
src/metadata.py Normal file
View file

@ -0,0 +1,103 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
ponysay - Ponysay, cowsay reimplementation for ponies
Copyright (C) 2012, 2013 Erkin Batu Altunbaş et al.
This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.
'''
from common import *
'''
Metadata functions
'''
class Metadata():
'''
Make restriction test logic function
@param restriction:list<string> Metadata based restrictions
@return :dict<str, str>bool Test function
'''
@staticmethod
def makeRestrictionLogic(restriction):
table = [(get_test(cell[:cell.index('=')],
cell[cell.index('=') + 1:]
)
for cell in clause.lower().replace('_', '').replace(' ', '').split('+'))
for clause in restriction
]
def get_test(cell):
strict = cell[0][-1] != '?'
key = cell[0][:-2 if strict else -1]
invert = cell[1][0] == '!'
value = cell[1][1 if invert else 0:]
class SITest():
def __init__(self, cellkey, cellvalue):
(self.cellkey, self.callvalue) = (key, value)
def __call__(self, has):
return False if key not in has else (value not in has[key])
class STest():
def __init__(self, cellkey, cellvalue):
(self.cellkey, self.callvalue) = (key, value)
def __call__(self, has):
return False if key not in has else (value in has[key])
class ITest():
def __init__(self, cellkey, cellvalue):
(self.cellkey, self.callvalue) = (key, value)
def __call__(self, has):
return True if key not in has else (value not in has[key])
class NTest():
def __init__(self, cellkey, cellvalue):
(self.cellkey, self.callvalue) = (key, value)
def __call__(self, has):
return True if key not in has else (value in has[key])
if strict and invert: return SITest(key, value)
if strict: return STest(key, value)
if invert: return ITest(key, value)
return NTest(key, value)
def logic(cells):
for alternative in table:
ok = True
for cell in alternative:
if not cell(cells):
ok = False
break
if ok:
return True
return False
return logic
'''
Get ponies that pass restriction
@param ponydir:str Pony directory, must end with `os.sep`
@param logic:dict<str, str>bool Restriction test functor
@return :list<str> Passed ponies
'''
@staticmethod
def restrictedPonies(ponydir, logic):
import cPickle
passed = []
if os.path.exists(ponydir + 'metadata'):
data = None
with open(ponydir + 'metadata', 'rb') as file:
data = cPickle.load(file)
for ponydata in data:
(pony, meta) = ponydata
if logic(meta):
passed.append(pony)
return passed

View file

@ -16,7 +16,9 @@ import os
import sys
from subprocess import Popen, PIPE
from argparser import *
from ponysay import *
from metadata import *
'''
@ -181,7 +183,7 @@ class PonysayTool():
@param message Message
'''
def execPonysay(self, args, message = ''):
class PhonyArgParser:
class PhonyArgParser():
def __init__(self, args, message):
self.argcount = len(args) + (0 if message is None else 1)
for key in args:
@ -196,10 +198,10 @@ class PonysayTool():
return key in args;
stdout = sys.stdout
class StringInputStream:
class StringInputStream():
def __init__(self):
self.buf = ''
class Buffer:
class Buffer():
def __init__(self, parent):
self.parent = parent
def write(self, data):
@ -244,9 +246,9 @@ class PonysayTool():
ponies.add(ponyfile)
if restriction is not None:
oldponies = ponies
logic = Ponysay.makeRestrictionLogic(restriction)
logic = Metadata.makeRestrictionLogic(restriction)
ponies = set()
for pony in Ponysay.restrictedPonies(ponydir, logic):
for pony in Metadata.restrictedPonies(ponydir, logic):
if (pony not in ponies) and (pony in oldponies):
ponies.add(pony)
oldponies = ponies
@ -415,7 +417,7 @@ class PonysayTool():
Generate all kmsponies for the current TTY palette
'''
def generateKMS(self):
class PhonyArgParser:
class PhonyArgParser():
def __init__(self, key, value):
self.argcount = 3
self.message = ponyfile
@ -427,10 +429,10 @@ class PonysayTool():
def __contains__(self, key):
return key == self.key;
class StringInputStream:
class StringInputStream():
def __init__(self):
self.buf = ''
class Buffer:
class Buffer():
def __init__(self, parent):
self.parent = parent
def write(self, data):
@ -479,7 +481,7 @@ class PonysayTool():
dimensions = []
for ponyfile in os.listdir(ponydir):
if ponyfile.endswith('.pony') and (ponyfile != '.pony'):
class PhonyArgParser:
class PhonyArgParser():
def __init__(self, balloon):
self.argcount = 5
self.message = ''
@ -495,10 +497,10 @@ class PonysayTool():
def __contains__(self, key):
return key in ('-f', '-W', '-b');
stdout = sys.stdout
class StringInputStream:
class StringInputStream():
def __init__(self):
self.buf = ''
class Buffer:
class Buffer():
def __init__(self, parent):
self.parent = parent
def write(self, data):
@ -524,11 +526,11 @@ class PonysayTool():
dimensions.append((ponywidth, ponyheight, ponyonlyheight, ponyfile[:-5]))
(widths, heights, onlyheights) = ([], [], [])
for item in dimensions:
widths .append(item[0], item[3])
heights .append(item[1], item[3])
onlyheights.append(item[2], item[3])
widths .append((item[0], item[3]))
heights .append((item[1], item[3]))
onlyheights.append((item[2], item[3]))
for items in (widths, heights, onlyheights):
sort(items, key = lambda item : item[0])
sorted(items, key = lambda item : item[0])
for pair in ((widths, 'widths'), (heights, 'heights'), (onlyheights, 'onlyheights')):
(items, dimfile) = pair
dimfile = (ponydir + '/' + dimfile).replace('//', '/')
@ -617,7 +619,7 @@ class PonysayTool():
test = test.replace(c, '')
if (len(test) == 0) and (len(key) > 0):
data.append((key, makeset(value.replace(' ', ''))))
everything.append(ponyfile[:-5], data)
everything.append((ponyfile[:-5], data))
import cPickle
with open(ponydir + 'metadata', 'wb') as file:
cPickle.dump(everything, file, -1)
@ -647,7 +649,7 @@ class PonysayTool():
image = data[sep + 1:]
class PhonyArgParser:
class PhonyArgParser():
def __init__(self):
self.argcount = 5
self.message = ponyfile
@ -689,10 +691,10 @@ class PonysayTool():
stdout = sys.stdout
class StringInputStream:
class StringInputStream():
def __init__(self):
self.buf = ''
class Buffer:
class Buffer():
def __init__(self, parent):
self.parent = parent
def write(self, data):
@ -763,7 +765,7 @@ class PonysayTool():
fields = standardfields[:-1] + fields + [standardfields[-1]]
def saver(ponyfile, ponyheight, ponywidth, data, image):
class Saver:
class Saver():
def __init__(self, ponyfile, ponyheight, ponywidth, data, image):
(self.ponyfile, self.ponyheight, self.ponywidth, self.data, self.image) = (ponyfile, ponyheight, ponywidth, data, image)
def __call__(self): # functor
@ -802,7 +804,7 @@ class PonysayTool():
'''
GNU Emacs alike text area
'''
class TextArea: # TODO support small screens
class TextArea(): # TODO support small screens
'''
Constructor

View file

@ -16,6 +16,9 @@ from backend import *
from balloon import *
from spellocorrecter import *
from ucs import *
from kms import *
from list import *
from metadata import *
@ -100,8 +103,12 @@ class Ponysay():
'''
Whether the script is executed as ponythink
'''
self.isthink = (len(__file__) >= len('think')) and (__file__.endswith('think'))
self.isthink = ((len(__file__) >= len('think.py')) and (__file__.endswith('think.py'))) or self.isthink
self.isthink = sys.argv[0]
if os.sep in self.isthink:
self.isthink = self.isthink[self.isthink.rfind(os.sep) + 1:]
if os.extsep in self.isthink:
self.isthink = self.isthink[:self.isthink.find(os.extsep)]
self.isthink = self.isthink.endswith('think')
'''
@ -123,7 +130,7 @@ class Ponysay():
'''
Whether KMS is used
'''
self.usekms = self.isUsingKMS()
self.usekms = KMS.usingkms(self.linuxvt)
'''
@ -295,7 +302,6 @@ class Ponysay():
if args.opts['--colour-bubble'] is None: args.opts['--colour-bubble'] = args.opts['+c']
## Other extra features
self.__extraponies(args)
self.__bestpony(args)
self.__ucsremap(args)
if test('-o'):
@ -310,16 +316,12 @@ class Ponysay():
## The stuff
if test('-q'):
warn = test('-f', '+f')
if (len(args.opts['-q']) == 1) and ((args.opts['-q'][0] == '-f') or (args.opts['-q'][0] == '+f')):
warn = True
if args.opts['-q'][0] == '-f':
args.opts['-q'] = args.files
if test('-f'):
args.opts['-q'] += args.opts['-f']
self.quote(args)
if warn:
printerr('-q cannot be used at the same time as -f or +f.')
elif not self.unrecognised:
self.print_pony(args)
else:
@ -334,33 +336,42 @@ class Ponysay():
'''
Use extra ponies
@param args:ArgParser Parsed command line arguments, `None` to force rather than by reading arguments
'''
def __extraponies(self, args = None):
def __extraponies(self):
## If extraponies are used, change ponydir to extraponydir
if args is None:
self.ponydirs[:] = self.extraponydirs
elif args.opts['+f'] is not None:
args.opts['-f'] = args.opts['+f']
self.ponydirs[:] = self.extraponydirs
self.quotedirs[:] = [] ## TODO +q
'''
Use best.pony if nothing else is set
@param args:ArgParser Parsed command line arguments
@param ponydirs:itr<str> Pony directories to use
'''
def __bestpony(self, args, ponydirs = None):
if ponydirs is None: ponydirs = self.ponydirs
def __bestpony(self, args):
## Set best.pony as the pony to display if none is selected
if (args.opts['-f'] is None) or (args.opts['-q'] is None) or (len(args.opts['-q']) == 0):
for ponydir in ponydirs:
def test(keys, strict):
if strict:
for key in keys:
if (args.opts[key] is not None) and (len(args.opts[key]) != 0):
return False
else:
for key in keys:
if args.opts[key] is not None:
return False
return True
keys = ['-f', '+f', '-F', '-q'] ## TODO +q -Q
if test(keys, False):
for ponydir in self.ponydirs:
if os.path.isfile(ponydir + 'best.pony') or os.path.islink(ponydir + 'best.pony'):
pony = os.path.realpath(ponydir + 'best.pony') # Canonical path
args.opts['-f' if args.opts['-q'] is None else '-q'] = [pony]
if test(keys, True):
args.opts['-f'] = [pony]
else:
for key in keys:
if test(key, True):
args.opts[key] = [pony]
break
@ -397,8 +408,8 @@ class Ponysay():
ascii = line[s + 1:].strip(stripset)
map[ucs] = ascii
## Apply UCS → ASCII mapping to -f and -q arguments
for flag in ('-f', '-q'):
## Apply UCS → ASCII mapping to -f, +f, -F and -q arguments
for flag in ('-f', '+f', '-F', '-q'): ## TODO +q -Q
if args.opts[flag] is not None:
for i in range(0, len(args.opts[flag])):
if args.opts[flag][i] in map:
@ -413,7 +424,7 @@ class Ponysay():
Apply UCS:ise pony names according to UCS settings
@param ponies:list<str> List of all ponies (of interrest)
@param links:map<str> Map to fill with simulated symlink ponies, may be `None`
@param links:map<str, str> Map to fill with simulated symlink ponies, may be `None`
'''
def __ucsise(self, ponies, links = None):
## Read UCS configurations
@ -535,17 +546,17 @@ class Ponysay():
if (names is None) or (len(names) == 0):
oldponies = ponies
if self.restriction is not None:
logic = Ponysay.makeRestrictionLogic(self.restriction)
logic = Metadata.makeRestrictionLogic(self.restriction)
ponies = {}
for ponydir in ponydirs:
for pony in Ponysay.restrictedPonies(ponydir, logic):
for pony in Metadata.restrictedPonies(ponydir, logic):
if (pony not in passed) and (pony in oldponies):
ponyfile = ponydir + pony + '.pony'
if oldponies[pony] == ponyfile:
ponies[pony] = ponyfile
oldponies = ponies
ponies = {}
(termh, termw) = self.__gettermsize()
(termh, termw) = gettermsize()
for ponydir in ponydirs:
(fitw, fith) = (None, None)
if os.path.exists(ponydir + 'widths'):
@ -591,89 +602,6 @@ class Ponysay():
return ponies[pony]
'''
Make restriction test logic function
@param restriction:list<string> Metadata based restrictions
@return :dict<str, str>bool Test function
'''
@staticmethod
def makeRestrictionLogic(restriction):
table = [(get_test(cell[:cell.index('=')],
cell[cell.index('=') + 1:]
)
for cell in clause.lower().replace('_', '').replace(' ', '').split('+'))
for clause in restriction
]
def get_test(cell):
strict = cell[0][-1] != '?'
key = cell[0][:-2 if strict else -1]
invert = cell[1][0] == '!'
value = cell[1][1 if invert else 0:]
class SITest:
def __init__(self, cellkey, cellvalue):
(self.cellkey, self.callvalue) = (key, value)
def __call__(self, has):
return False if key not in has else (value not in has[key])
class STest:
def __init__(self, cellkey, cellvalue):
(self.cellkey, self.callvalue) = (key, value)
def __call__(self, has):
return False if key not in has else (value in has[key])
class ITest:
def __init__(self, cellkey, cellvalue):
(self.cellkey, self.callvalue) = (key, value)
def __call__(self, has):
return True if key not in has else (value not in has[key])
class NTest:
def __init__(self, cellkey, cellvalue):
(self.cellkey, self.callvalue) = (key, value)
def __call__(self, has):
return True if key not in has else (value in has[key])
if strict and invert: return SITest(key, value)
if strict: return STest(key, value)
if invert: return ITest(key, value)
return NTest(key, value)
def logic(cells):
for alternative in table:
ok = True
for cell in alternative:
if not cell(cells):
ok = False
break
if ok:
return True
return False
return logic
'''
Get ponies that pass restriction
@param ponydir:str Pony directory, must end with `os.sep`
@param logic:dict<str, str>bool Restriction test functor
@return :list<str> Passed ponies
'''
@staticmethod
def restrictedPonies(ponydir, logic):
import cPickle
passed = []
if os.path.exists(ponydir + 'metadata'):
data = None
with open(ponydir + 'metadata', 'rb') as file:
data = cPickle.load(file)
for ponydata in data:
(pony, meta) = ponydata
if logic(meta):
passed.append(pony)
return passed
'''
Returns a set with all ponies that have quotes and are displayable
@ -742,79 +670,11 @@ class Ponysay():
return rc
'''
Gets the size of the terminal in (rows, columns)
@return (rows, columns):(int, int) The number or lines and the number of columns in the terminal's display area
'''
def __gettermsize(self):
## Call `stty` to determine the size of the terminal, this way is better than using python's ncurses
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
#####################
## Listing methods ##
#####################
'''
Columnise a list and prints it
@param ponies:list<(str, str)> All items to list, each item should have to elements: unformated name, formated name
'''
def __columnise(self, ponies):
## Get terminal width, and a 2 which is the space between columns
termwidth = self.__gettermsize()[1] + 2
## Sort the ponies, and get the cells' widths, and the largest width + 2
ponies.sort(key = lambda pony : pony[0])
widths = [UCS.dispLen(pony[0]) for pony in ponies]
width = max(widths) + 2 # longest pony file name + space between columns
## Calculate the number of rows and columns, can create a list of empty columns
cols = termwidth // width # do not believe electricians, this means ⌊termwidth / width⌋
rows = (len(ponies) + cols - 1) // cols
columns = []
for c in range(0, cols): columns.append([])
## Fill the columns with cells of ponies
(y, x) = (0, 0)
for j in range(0, len(ponies)):
cell = ponies[j][1] + ' ' * (width - widths[j]);
columns[x].append(cell)
y += 1
if y == rows:
x += 1
y = 0
## Make the columnisation nicer by letting the last row be partially empty rather than the last column
diff = rows * cols - len(ponies)
if (diff > 2) and (rows > 1):
c = cols - 1
diff -= 1
while diff > 0:
columns[c] = columns[c - 1][-diff:] + columns[c]
c -= 1
columns[c] = columns[c][:-diff]
diff -= 1
## Create rows from columns
lines = []
for r in range(0, rows):
lines.append([])
for c in range(0, cols):
if r < len(columns[c]):
line = lines[r].append(columns[c][r])
## Print the matrix, with one extra blank row
print('\n'.join([''.join(line)[:-2] for line in lines]))
print()
'''
Lists the available ponies
@ -822,29 +682,8 @@ class Ponysay():
@param ponydirs:itr<str> The pony directories to use
'''
def list(self, ponydirs = None):
if ponydirs is None: ponydirs = self.ponydirs
## Get all quoters
quoters = self.__quoters()
for ponydir in ponydirs: # Loop ponydirs
## Get all ponies in the directory
_ponies = os.listdir(ponydir)
## Remove .pony from all files and skip those that does not have .pony
ponies = []
for pony in _ponies:
if endswith(pony, '.pony'):
ponies.append(pony[:-5])
## UCS:ise pony names, they are already sorted
self.__ucsise(ponies)
## If ther directory is not empty print its name and all ponies, columnised
if len(ponies) == 0:
continue
print('\033[1mponies located in ' + ponydir + '\033[21m')
self.__columnise([(pony, '\033[1m' + pony + '\033[21m' if pony in quoters else pony) for pony in ponies])
List.simplelist(self.ponydirs if ponydirs is None else ponydirs,
self.__quoters(), lambda x : self.__ucsise(x))
'''
@ -853,75 +692,20 @@ class Ponysay():
@param ponydirs:itr<str> The pony directories to use
'''
def linklist(self, ponydirs = None):
if ponydirs is None: ponydirs = self.ponydirs
List.linklist(self.ponydirs if ponydirs is None else ponydirs,
self.__quoters(), lambda x, y : self.__ucsise(x, y))
## Get the size of the terminal and all ponies with quotes
termsize = self.__gettermsize()
quoters = self.__quoters()
for ponydir in ponydirs: # Loop ponydirs
## Get all pony files in the directory
_ponies = os.listdir(ponydir)
'''
Lists the available ponies on one column without anything bold or otherwise formated
## Remove .pony from all files and skip those that does not have .pony
ponies = []
for pony in _ponies:
if endswith(pony, '.pony'):
ponies.append(pony[:-5])
## If there are no ponies in the directory skip to next directory, otherwise, print the directories name
if len(ponies) == 0:
continue
print('\033[1mponies located in ' + ponydir + '\033[21m')
## UCS:ise pony names
pseudolinkmap = {}
self.__ucsise(ponies, pseudolinkmap)
## Create targetlink-pair, with `None` as link if the file is not a symlink or in `pseudolinkmap`
pairs = []
for pony in ponies:
if pony in pseudolinkmap:
pairs.append((pony, pseudolinkmap[pony] + '.pony'));
else:
pairs.append((pony, os.path.realpath(ponydir + pony + '.pony') if os.path.islink(ponydir + pony + '.pony') else None))
## Create map from source pony to alias ponies for each pony
ponymap = {}
for pair in pairs:
if (pair[1] is None) or (pair[1] == ''):
if pair[0] not in ponymap:
ponymap[pair[0]] = []
else:
target = pair[1][:-5]
if '/' in target:
target = target[target.rindex('/') + 1:]
if target in ponymap:
ponymap[target].append(pair[0])
else:
ponymap[target] = [pair[0]]
## Create list of source ponies concatenated with alias ponies in brackets
ponies = {}
for pony in ponymap:
w = UCS.dispLen(pony)
item = '\033[1m' + pony + '\033[21m' if (pony in quoters) else pony
syms = ponymap[pony]
syms.sort()
if len(syms) > 0:
w += 2 + len(syms)
item += ' ('
first = True
for sym in syms:
w += UCS.dispLen(sym)
if first: first = False
else: item += ' '
item += '\033[1m' + sym + '\033[21m' if (sym in quoters) else sym
item += ')'
ponies[(item.replace('\033[1m', '').replace('\033[21m', ''), item)] = w
## Print the ponies, columnised
self.__columnise(list(ponies))
@param standard:bool Include standard ponies
@param extra:bool Include extra ponies
'''
def onelist(self, standard = True, extra = False):
List.onelist(self.ponydirs if standard else None,
self.extraponydirs if extra else None,
lambda x : self.__ucsise(x))
'''
@ -934,74 +718,17 @@ class Ponysay():
## Get all quoters
ponies = list(self.__quoters()) if standard else []
if standard:
## UCS:ise
self.__ucsise(ponies)
## And now the extra ponies
if extra:
self.__extraponies()
xponies = list(self.__quoters())
self.__ucsise(xponies)
ponies += xponies
ponies += list(self.__quoters())
## Print each one on a seperate line, but skip duplicates
last = ''
ponies.sort()
for pony in ponies:
if not pony == last:
last = pony
print(pony)
'''
Lists the available ponies on one column without anything bold or otherwise formated
@param standard:bool Include standard ponies
@param extra:bool Include extra ponies
'''
def onelist(self, standard = True, extra = False):
## All ponies
ponies = []
if standard:
## Get all pony files
_ponies = []
for ponydir in self.ponydirs: # Loop ponydirs
_ponies += os.listdir(ponydir)
## Remove .pony from all files and skip those that does not have .pony
for pony in _ponies:
if endswith(pony, '.pony'):
ponies.append(pony[:-5])
## UCS:ise
## UCS:ise here
self.__ucsise(ponies)
if extra:
## Swap to extra ponies
self.__extraponies()
## Get all pony files
_ponies = []
for ponydir in self.ponydirs: # Loop ponydirs
_ponies += os.listdir(ponydir)
## Remove .pony from all files and skip those that does not have .pony
xponies = []
for pony in _ponies:
if endswith(pony, '.pony'):
xponies.append(pony[:-5])
## UCS:ise
self.__ucsise(xponies)
## Add found extra ponies
ponies += xponies
ponies.sort()
## Print each one on a seperate line, but skip duplicates
last = ''
ponies.sort()
for pony in ponies:
if not pony == last:
last = pony
@ -1016,27 +743,7 @@ class Ponysay():
Prints a list of all balloons
'''
def balloonlist(self):
## Get the size of the terminal
termsize = self.__gettermsize()
## Get all balloons
balloonset = set()
for balloondir in self.balloondirs:
for balloon in os.listdir(balloondir):
## Use .think if running ponythink, otherwise .say
if self.isthink and endswith(balloon, '.think'):
balloon = balloon[:-6]
elif (not self.isthink) and endswith(balloon, '.say'):
balloon = balloon[:-4]
else:
continue
## Add the balloon if there is none with the same name
if balloon not in balloonset:
balloonset.add(balloon)
## Print all balloos, columnised
self.__columnise([(balloon, balloon) for balloon in list(balloonset)])
List.balloonlist(self.balloondirs, self.isthink)
'''
@ -1083,7 +790,7 @@ class Ponysay():
limit = 5 if len(limit) == 0 else int(dist)
if (len(alternatives) > 0) and (dist <= limit):
return self.__getballoonpath(alternatives, True)
sys.stderr.write('That balloon style %s does not exist\n' % (balloon));
printerr('That balloon style %s does not exist' % (balloon));
exit(1)
else:
return balloons[balloon]
@ -1096,37 +803,7 @@ class Ponysay():
@return :Balloon Instance describing the balloon's style
'''
def __getballoon(self, balloonfile):
## Use default balloon if none is specified
if balloonfile is None:
if self.isthink:
return Balloon('o', 'o', '( ', ' )', [' _'], ['_'], ['_'], ['_'], ['_ '], ' )', ' )', ' )', ['- '], ['-'], ['-'], ['-'], [' -'], '( ', '( ', '( ')
return Balloon('\\', '/', '< ', ' >', [' _'], ['_'], ['_'], ['_'], ['_ '], ' \\', ' |', ' /', ['- '], ['-'], ['-'], ['-'], [' -'], '\\ ', '| ', '/ ')
## Initialise map for balloon parts
map = {}
for elem in ('\\', '/', 'ww', 'ee', 'nw', 'nnw', 'n', 'nne', 'ne', 'nee', 'e', 'see', 'se', 'sse', 's', 'ssw', 'sw', 'sww', 'w', 'nww'):
map[elem] = []
## Read all lines in the balloon file
with open(balloonfile, 'rb') as balloonstream:
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
for line in data:
if len(line) > 0:
if line[0] == ':':
map[last].append(line[1:])
else:
last = line[:line.index(':')]
value = line[len(last) + 1:]
map[last].append(value)
## Return the balloon
return Balloon(map['\\'][0], map['/'][0], map['ww'][0], map['ee'][0], map['nw'], map['nnw'], map['n'],
map['nne'], map['ne'], map['nee'][0], map['e'][0], map['see'][0], map['se'], map['sse'],
map['s'], map['ssw'], map['sw'], map['sww'][0], map['w'][0], map['nww'][0])
return Balloon.fromfile(balloonfile, self.isthink)
@ -1191,7 +868,7 @@ class Ponysay():
pony = '/proc/' + str(os.getpid()) + '/fd/' + str(pngpipe[0])
## If KMS is utilies, select a KMS pony file and create it if necessary
pony = self.__kms(pony)
pony = KMS.kms(pony, self.HOME, self.linuxvt)
## If in Linux VT clean the terminal (See info/pdf-manual [Printing in TTY with KMS])
if self.linuxvt:
@ -1200,19 +877,19 @@ class Ponysay():
## Get width truncation and wrapping
env_width = os.environ['PONYSAY_FULL_WIDTH'] if 'PONYSAY_FULL_WIDTH' in os.environ else None
if env_width is None: env_width = ''
widthtruncation = self.__gettermsize()[1] if env_width not in ('yes', 'y', '1') else None
widthtruncation = gettermsize()[1] if env_width not in ('yes', 'y', '1') else None
messagewrap = 40
if (args.opts['-W'] is not None) and (len(args.opts['-W'][0]) > 0):
messagewrap = args.opts['-W'][0]
if messagewrap[0] in 'nmsNMS': # m is left to n on QWERTY and s is left to n on Dvorak
messagewrap = None
elif messagewrap[0] in 'iouIOU': # o is left to i on QWERTY and u is right to i on Dvorak
messagewrap = self.__gettermsize()[1]
messagewrap = gettermsize()[1]
else:
messagewrap = int(args.opts['-W'][0])
## Get balloon object
balloonfile = self.__getballoonpath(args.opts['-b'])
balloonfile = self.__getballoonpath(args.opts['-b'][0] if args.opts['-b'] is not None else None)
printinfo('balloon style file: ' + str(balloonfile))
balloon = self.__getballoon(balloonfile) if args.opts['-o'] is None else None
@ -1258,7 +935,7 @@ class Ponysay():
if (env_lines is None) or (env_lines == ''): env_lines = '2'
## Print the output, truncated on height is so set
lines = self.__gettermsize()[0] - int(env_lines)
lines = gettermsize()[0] - int(env_lines)
if self.linuxvt or (env_height is ('yes', 'y', '1')):
if env_bottom is ('yes', 'y', '1'):
for line in output.split('\n')[: -lines]:
@ -1310,118 +987,3 @@ class Ponysay():
self.print_pony(args)
'''
Identifies whether KMS support is utilised
'''
def isUsingKMS(self):
## KMS is not utilised if Linux VT is not used
if not self.linuxvt:
return False
## Read the PONYSAY_KMS_PALETTE environment variable
env_kms = os.environ['PONYSAY_KMS_PALETTE'] if 'PONYSAY_KMS_PALETTE' in os.environ else None
if env_kms is None: env_kms = ''
## Read the PONYSAY_KMS_PALETTE_CMD environment variable, and run it
env_kms_cmd = os.environ['PONYSAY_KMS_PALETTE_CMD'] if 'PONYSAY_KMS_PALETTE_CMD' in os.environ else None
if (env_kms_cmd is not None) and (not env_kms_cmd == ''):
env_kms = Popen(shlex.split(env_kms_cmd), stdout=PIPE, stdin=sys.stderr).communicate()[0].decode('utf8', 'replace')
if env_kms[-1] == '\n':
env_kms = env_kms[:-1]
## If the palette string is empty KMS is not utilised
return env_kms != ''
'''
Returns the file name of the input pony converted to a KMS pony, or if KMS is not used, the input pony itself
@param pony:str Choosen pony file
@return :str Pony file to display
'''
def __kms(self, pony):
## If not in Linux VT, return the pony as is
if not self.linuxvt:
return pony
## KMS support version constant
KMS_VERSION = '2'
## Read the PONYSAY_KMS_PALETTE environment variable
env_kms = os.environ['PONYSAY_KMS_PALETTE'] if 'PONYSAY_KMS_PALETTE' in os.environ else None
if env_kms is None: env_kms = ''
## Read the PONYSAY_KMS_PALETTE_CMD environment variable, and run it
env_kms_cmd = os.environ['PONYSAY_KMS_PALETTE_CMD'] if 'PONYSAY_KMS_PALETTE_CMD' in os.environ else None
if (env_kms_cmd is not None) and (not env_kms_cmd == ''):
env_kms = Popen(shlex.split(env_kms_cmd), stdout=PIPE, stdin=sys.stderr).communicate()[0].decode('utf8', 'replace')
if env_kms[-1] == '\n':
env_kms = env_kms[:-1]
## If not using KMS, return the pony as is
if env_kms == '':
return pony
## Store palette string and a clone with just the essentials
palette = env_kms
palettefile = env_kms.replace('\033]P', '')
## Get and in necessary make cache directory
cachedir = '/var/cache/ponysay'
shared = True
if not os.path.isdir(cachedir):
cachedir = self.HOME + '/.cache/ponysay'
shared = False
if not os.path.isdir(cachedir):
os.makedirs(cachedir)
_cachedir = '\'' + cachedir.replace('\'', '\'\\\'\'') + '\''
## KMS support version control, clean everything if not matching
newversion = False
if not os.path.isfile(cachedir + '/.version'):
newversion = True
else:
with open(cachedir + '/.version', 'rb') as cachev:
if cachev.read().decode('utf8', 'replace').replace('\n', '') != KMS_VERSION:
newversion = True
if newversion:
for cached in os.listdir(cachedir):
cached = cachedir + '/' + cached
if os.path.isdir(cached) and not os.path.islink(cached):
shutil.rmtree(cached, False)
else:
os.remove(cached)
with open(cachedir + '/.version', 'w+') as cachev:
cachev.write(KMS_VERSION)
if shared:
Popen('chmod 666 -- ' + _cachedir + '/.version', shell=True).wait()
## Get kmspony directory and kmspony file
kmsponies = cachedir + '/kmsponies/' + palettefile
kmspony = (kmsponies + pony).replace('//', '/')
## If the kmspony is missing, create it
if not os.path.isfile(kmspony):
## kmspony directory
kmsponydir = kmspony[:kmspony.rindex('/')]
## Change file names to be shell friendly
_kmspony = '\'' + kmspony.replace('\'', '\'\\\'\'') + '\''
_pony = '\'' + pony.replace('\'', '\'\\\'\'') + '\''
## Create kmspony
if not os.path.isdir(kmsponydir):
os.makedirs(kmsponydir)
if shared:
Popen('chmod -R 6777 -- ' + _cachedir, shell=True).wait()
ponytoolcmd = 'ponytool --import ponysay --file %s --export ponysay --file %s --platform linux '
ponytoolcmd += '--balloon n --colourful y --fullcolour y --left - --right - --top - --bottom - --palette %s'
if not os.system(ponytoolcmd % (_pony, _kmspony, palette)) == 0:
sys.stderr.write('Unable to run ponytool successfully, you need util-say>=3 for KMS support\n')
exit(1)
if shared:
Popen('chmod 666 -- ' + _kmspony, shell=True).wait()
return kmspony