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
*.pyc
pesterchum.js
quirks/*
!quirks/defaults.py

View file

@ -38,6 +38,7 @@ CHANGELOG
* Memo OP options: Secret, Invite-only, Mute - Kiooeht [evacipatedBox]
* Notify user if channel blocks message - Kiooeht [evacipatedBox]
* Bug reporter - Kiooeht [evacipatedBox]
* Python quirks (users can create own quirk functions) - Kiooeht [evacipatedBox]
* Bug fixes
* Logviewer updates - 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
* Turn @ and # links on/off?
* "someone has friended you" notifier
* Python quirks (so normal users can write their own quirk functions in python)
Bugs
----

View file

@ -326,6 +326,29 @@ class RandomQuirkDialog(MultiTextDialog):
self.replacelist.takeItem(self.replacelist.currentRow())
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):
def __init__(self, config, theme, parent):
QtGui.QDialog.__init__(self, parent)
@ -338,6 +361,12 @@ class PesterChooseQuirks(QtGui.QDialog):
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.connect(self.addPrefixButton, QtCore.SIGNAL('clicked()'),
self, QtCore.SLOT('addPrefixDialog()'))
@ -379,6 +408,9 @@ class PesterChooseQuirks(QtGui.QDialog):
layout_quirklist.addWidget(self.quirkList)
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.addWidget(self.addPrefixButton)
layout_1.addWidget(self.addSuffixButton)
@ -411,6 +443,7 @@ class PesterChooseQuirks(QtGui.QDialog):
layout_0 = QtGui.QVBoxLayout()
layout_0.addLayout(layout_quirklist)
layout_0.addLayout(layout_4)
layout_0.addLayout(layout_1)
layout_0.addLayout(layout_2)
layout_0.addLayout(layout_3)
@ -425,6 +458,20 @@ class PesterChooseQuirks(QtGui.QDialog):
return u
#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()
def editSelected(self):
q = self.quirkList.currentQuirk()

View file

@ -5,6 +5,7 @@ from datetime import timedelta
from PyQt4 import QtGui
from generic import mysteryTime
from pyquirks import PythonQuirks
_ctag_begin = re.compile(r'(?i)<c=(.*?)>')
_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*/>""")
_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]+)")
def reloadQuirkFunctions():
quirkloader.load()
global _functionre
_functionre = re.compile(r"%s" % quirkloader.funcre())
def lexer(string, objlist):
"""objlist is a list: [(objecttype, re),...] list is in order of preference"""
stringlist = [string]
@ -300,14 +307,6 @@ def timeDifference(td):
timetext = "%d HOURS %s" % (hours, when)
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):
return text
@ -339,8 +338,7 @@ def parseRegexpFunctions(to):
parsed = parseLeaf(nonerep, None)
current = parsed
curi = 0
functiondict = {"upper(": upperrep, "lower(": lowerrep,
"scramble(": scramblerep, "reverse(": reverserep}
functiondict = quirkloader.quirks
while curi < len(to):
tmp = to[curi:]
mo = _functionre.search(tmp)

View file

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