Customizable Python quirks

This commit is contained in:
Kiooeht 2011-06-07 08:48:35 -07:00
parent 3798d04e1e
commit fbb16b9549
9 changed files with 225 additions and 12 deletions

2
.gitignore vendored
View file

@ -6,3 +6,5 @@ tutorial/*
irctest.log irctest.log
*.pyc *.pyc
pesterchum.js pesterchum.js
quirks/*
!quirks/defaults.py

View file

@ -38,6 +38,7 @@ CHANGELOG
* Memo OP options: Secret, Invite-only, Mute - Kiooeht [evacipatedBox] * Memo OP options: Secret, Invite-only, Mute - Kiooeht [evacipatedBox]
* Notify user if channel blocks message - Kiooeht [evacipatedBox] * Notify user if channel blocks message - Kiooeht [evacipatedBox]
* Bug reporter - Kiooeht [evacipatedBox] * Bug reporter - Kiooeht [evacipatedBox]
* Python quirks (users can create own quirk functions) - Kiooeht [evacipatedBox]
* Bug fixes * Bug fixes
* Logviewer updates - Kiooeht [evacipatedBox] * Logviewer updates - Kiooeht [evacipatedBox]
* Memo scrollbar thing - Kiooeht [evacipatedBox] * Memo scrollbar thing - Kiooeht [evacipatedBox]

87
PYQUIRKS.mkdn Normal file
View file

@ -0,0 +1,87 @@
Python Quirk Functions
===============
0. Table of Contents
-----------------
1. Introduction
2. Create A Module
3. Functions In A Module
4. Command Requirements
5. Completed Quirk Function
1. Introduction
---------------
Over the course of this short tutorial you will learn:
* How to create your own Quirk Functions
* VERY basic Python syntax
You will not learn:
* How to write Python
* How to bake a cake
Throughout this tutorial there will be
<pre>
Instructions in special boxes.
If you follow the instructions in these boxes, by the end of this tutorial
you will have recreated the default reverse() Quirk Function.
</pre>
2. Create A Module
-------------------
All Quirk Function Modules should be created in the 'quirks/' directory. File names <b>must</b> end in '.py'.
You can have multiple Quirk Functions per Module.
Each Module can also have a 'setup' function which will be called once, the moment the Module is loaded.
<pre>
Create 'reverse.py' in the 'quirks/' directory.
</pre>
3. Functions In A Module
--------------------------
If you've ever done programming before, you should know what a function is. If not, I suggest picking up a good programming book (or e-book).
In Python, function syntax looks like this:
def function_name(myvar1, myvar2):
'def' is used to declare that this is a function, and 'function_name' is obviously the name of your function.
'myvar1' and 'myvar2' are variables coming into your function. For most of your functions, the only variable being passed will be 'text'.
In Python, curly braces ({}) are not used to declare the beginning and end of a function. Instead, a colon (:) is used to declare the beginning of a function. After that, indentation is used to declare the body and end of a function.
<pre>
def reverserep(text):
return text[::-1]
</pre>
4. Command Requirements
------------------------
For a function to be registered as a Quirk Function, it must conform to three simple rules:
1. It must have a command name.
2. It must take exactly one arguement.
3. It must return a string.
What is meant by having a command name, is that a name for the Quirk Function has to be defined. This is done by defining the 'command' variable for a function.
function_name.command = "name"
<pre>
reverserep.command = "reverse"
</pre>
5. Completed Quirk Function
---------------------------
Below is the completed, fully working, reverse Quirk Function. After it I will break down the pieces of each line.
<pre>
def reverserep(text):
return text[::-1]
reverserep.command = "reverse"
</pre>
As stated before, to start a function, you need to use the keyword 'def'. All Quirk Functions must take exactly one argument (in this case 'text').
In this example, the text is reversed and returned all in one line. 'text[::-1]' is the Pythonic way of reversing a list or string.
The last line is the most important part. This tells Pesterchum to call this function whenever 'reverse()' is used in a quirk.

View file

@ -11,7 +11,6 @@ Features
* Spy mode * Spy mode
* Turn @ and # links on/off? * Turn @ and # links on/off?
* "someone has friended you" notifier * "someone has friended you" notifier
* Python quirks (so normal users can write their own quirk functions in python)
Bugs Bugs
---- ----

View file

@ -326,6 +326,29 @@ class RandomQuirkDialog(MultiTextDialog):
self.replacelist.takeItem(self.replacelist.currentRow()) self.replacelist.takeItem(self.replacelist.currentRow())
self.replaceinput.setFocus() self.replaceinput.setFocus()
class QuirkFuncWindow(QtGui.QDialog):
def __init__(self, parent):
QtGui.QDialog.__init__(self, parent)
self.mainwindow = parent
self.setStyleSheet(self.mainwindow.theme["main/defaultwindow/style"])
self.setWindowTitle("Quirk Functions")
self.funclist = QtGui.QListWidget(self)
self.funclist.setStyleSheet("background-color: #FFFFFF;")
from parsetools import quirkloader
funcs = [q+")" for q in quirkloader.quirks.keys()]
funcs.sort()
self.funclist.addItems(funcs)
layout_0 = QtGui.QVBoxLayout()
layout_0.addWidget(QtGui.QLabel("Avaliable Quirk Functions"))
layout_0.addWidget(self.funclist)
self.setLayout(layout_0)
def closeEvent(self, event):
self.mainwindow.quirkmenu.funclistwindow = None
class PesterChooseQuirks(QtGui.QDialog): class PesterChooseQuirks(QtGui.QDialog):
def __init__(self, config, theme, parent): def __init__(self, config, theme, parent):
QtGui.QDialog.__init__(self, parent) QtGui.QDialog.__init__(self, parent)
@ -338,6 +361,12 @@ class PesterChooseQuirks(QtGui.QDialog):
self.quirkList = PesterQuirkList(self.mainwindow, self) self.quirkList = PesterQuirkList(self.mainwindow, self)
self.viewQuirkFuncButton = QtGui.QPushButton("VIEW FUNCTIONS", self)
self.connect(self.viewQuirkFuncButton, QtCore.SIGNAL('clicked()'),
self, QtCore.SLOT('viewQuirkFuncSlot()'))
self.reloadQuirkFuncButton = QtGui.QPushButton("RELOAD FUNCTIONS", self)
self.connect(self.reloadQuirkFuncButton, QtCore.SIGNAL('clicked()'),
self, QtCore.SLOT('reloadQuirkFuncSlot()'))
self.addPrefixButton = QtGui.QPushButton("ADD PREFIX", self) self.addPrefixButton = QtGui.QPushButton("ADD PREFIX", self)
self.connect(self.addPrefixButton, QtCore.SIGNAL('clicked()'), self.connect(self.addPrefixButton, QtCore.SIGNAL('clicked()'),
self, QtCore.SLOT('addPrefixDialog()')) self, QtCore.SLOT('addPrefixDialog()'))
@ -379,6 +408,9 @@ class PesterChooseQuirks(QtGui.QDialog):
layout_quirklist.addWidget(self.quirkList) layout_quirklist.addWidget(self.quirkList)
layout_quirklist.addLayout(layout_shiftbuttons) layout_quirklist.addLayout(layout_shiftbuttons)
layout_4 = QtGui.QHBoxLayout()
layout_4.addWidget(self.viewQuirkFuncButton)
layout_4.addWidget(self.reloadQuirkFuncButton)
layout_1 = QtGui.QHBoxLayout() layout_1 = QtGui.QHBoxLayout()
layout_1.addWidget(self.addPrefixButton) layout_1.addWidget(self.addPrefixButton)
layout_1.addWidget(self.addSuffixButton) layout_1.addWidget(self.addSuffixButton)
@ -411,6 +443,7 @@ class PesterChooseQuirks(QtGui.QDialog):
layout_0 = QtGui.QVBoxLayout() layout_0 = QtGui.QVBoxLayout()
layout_0.addLayout(layout_quirklist) layout_0.addLayout(layout_quirklist)
layout_0.addLayout(layout_4)
layout_0.addLayout(layout_1) layout_0.addLayout(layout_1)
layout_0.addLayout(layout_2) layout_0.addLayout(layout_2)
layout_0.addLayout(layout_3) layout_0.addLayout(layout_3)
@ -425,6 +458,20 @@ class PesterChooseQuirks(QtGui.QDialog):
return u return u
#return [self.quirkList.item(i).quirk for i in range(self.quirkList.count())] #return [self.quirkList.item(i).quirk for i in range(self.quirkList.count())]
@QtCore.pyqtSlot()
def viewQuirkFuncSlot(self):
if not hasattr(self, 'funclistwindow'):
self.funclistwindow = None
if self.funclistwindow:
return
self.funclistwindow = QuirkFuncWindow(self.mainwindow)
self.funclistwindow.show()
@QtCore.pyqtSlot()
def reloadQuirkFuncSlot(self):
from parsetools import reloadQuirkFunctions
reloadQuirkFunctions()
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def editSelected(self): def editSelected(self):
q = self.quirkList.currentQuirk() q = self.quirkList.currentQuirk()

View file

@ -5,6 +5,7 @@ from datetime import timedelta
from PyQt4 import QtGui from PyQt4 import QtGui
from generic import mysteryTime from generic import mysteryTime
from pyquirks import PythonQuirks
_ctag_begin = re.compile(r'(?i)<c=(.*?)>') _ctag_begin = re.compile(r'(?i)<c=(.*?)>')
_gtag_begin = re.compile(r'(?i)<g[a-f]>') _gtag_begin = re.compile(r'(?i)<g[a-f]>')
@ -16,9 +17,15 @@ _handlere = re.compile(r"(\s|^)(@[A-Za-z0-9_]+)")
_imgre = re.compile(r"""(?i)<img src=['"](\S+)['"]\s*/>""") _imgre = re.compile(r"""(?i)<img src=['"](\S+)['"]\s*/>""")
_mecmdre = re.compile(r"^(/me|PESTERCHUM:ME)(\S*)") _mecmdre = re.compile(r"^(/me|PESTERCHUM:ME)(\S*)")
_functionre = re.compile(r"(upper\(|lower\(|scramble\(|reverse\(|\)|\\[0-9]+)") quirkloader = PythonQuirks()
_functionre = re.compile(r"%s" % quirkloader.funcre())
_groupre = re.compile(r"\\([0-9]+)") _groupre = re.compile(r"\\([0-9]+)")
def reloadQuirkFunctions():
quirkloader.load()
global _functionre
_functionre = re.compile(r"%s" % quirkloader.funcre())
def lexer(string, objlist): def lexer(string, objlist):
"""objlist is a list: [(objecttype, re),...] list is in order of preference""" """objlist is a list: [(objecttype, re),...] list is in order of preference"""
stringlist = [string] stringlist = [string]
@ -300,14 +307,6 @@ def timeDifference(td):
timetext = "%d HOURS %s" % (hours, when) timetext = "%d HOURS %s" % (hours, when)
return timetext return timetext
def upperrep(text):
return text.upper()
def lowerrep(text):
return text.lower()
def scramblerep(text):
return "".join(random.sample(text, len(text)))
def reverserep(text):
return text[::-1]
def nonerep(text): def nonerep(text):
return text return text
@ -339,8 +338,7 @@ def parseRegexpFunctions(to):
parsed = parseLeaf(nonerep, None) parsed = parseLeaf(nonerep, None)
current = parsed current = parsed
curi = 0 curi = 0
functiondict = {"upper(": upperrep, "lower(": lowerrep, functiondict = quirkloader.quirks
"scramble(": scramblerep, "reverse(": reverserep}
while curi < len(to): while curi < len(to):
tmp = to[curi:] tmp = to[curi:]
mo = _functionre.search(tmp) mo = _functionre.search(tmp)

View file

@ -2355,9 +2355,13 @@ class PesterWindow(MovingWindow):
item.quirk.quirk["group"] = item.quirk.group = curgroup item.quirk.quirk["group"] = item.quirk.group = curgroup
quirks = pesterQuirks(self.quirkmenu.quirks()) quirks = pesterQuirks(self.quirkmenu.quirks())
self.userprofile.setQuirks(quirks) self.userprofile.setQuirks(quirks)
if hasattr(self.quirkmenu, 'funclistwindow') and self.quirkmenu.funclistwindow:
self.quirkmenu.funclistwindow.close()
self.quirkmenu = None self.quirkmenu = None
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def closeQuirks(self): def closeQuirks(self):
if hasattr(self.quirkmenu, 'funclistwindow') and self.quirkmenu.funclistwindow:
self.quirkmenu.funclistwindow.close()
self.quirkmenu = None self.quirkmenu = None
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def openLogv(self): def openLogv(self):

58
pyquirks.py Normal file
View file

@ -0,0 +1,58 @@
import os, sys, imp, re
class PythonQuirks(object):
def __init__(self):
self.home = os.getcwd()
self.quirks = {}
self.last = {}
self.load()
def load(self):
self.last = self.quirks.copy()
self.quirks.clear()
filenames = []
if not os.path.exists(os.path.join(self.home, 'quirks')):
os.mkdir(os.path.join(self.home, 'quirks'))
for fn in os.listdir(os.path.join(self.home, 'quirks')):
if fn.endswith('.py') and not fn.startswith('_'):
filenames.append(os.path.join(self.home, 'quirks', fn))
modules = []
for filename in filenames:
name = os.path.basename(filename)[:-3]
try: module = imp.load_source(name, filename)
except Exception, e:
print "Error loading %s: %s (in pyquirks.py)" % (name, e)
else:
if hasattr(module, 'setup'):
module.setup()
self.register(vars(module))
modules.append(name)
for k in self.last:
if k in self.quirks:
if self.last[k] == self.quirks[k]:
del self.quirks[k]
if self.quirks:
print 'Registered quirks:', '), '.join(self.quirks) + ")"
else:print "Warning: Couldn't find any python quirks"
def register(self, variables):
for name, obj in variables.iteritems():
if hasattr(obj, 'command'):
try:
if not isinstance(obj("test"), basestring):
raise Exception
except:
print "Quirk malformed: %s" % (obj.command)
else:
self.quirks[obj.command+"("] = obj
def funcre(self):
if not self.quirks:
return r""
f = r"("
for q in self.quirks:
f = f + q[:-1]+r"\(|"
f = f + r"\)|\\[0-9]+)"
return f

17
quirks/defaults.py Normal file
View file

@ -0,0 +1,17 @@
import random
def upperrep(text):
return text.upper()
upperrep.command = "upper"
def lowerrep(text):
return text.lower()
lowerrep.command = "lower"
def scramblerep(text):
return "".join(random.sample(text, len(text)))
scramblerep.command = "scramble"
def reverserep(text):
return text[::-1]
reverserep.command = "reverse"