Customizable Python quirks
This commit is contained in:
parent
3798d04e1e
commit
fbb16b9549
9 changed files with 225 additions and 12 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -6,3 +6,5 @@ tutorial/*
|
|||
irctest.log
|
||||
*.pyc
|
||||
pesterchum.js
|
||||
quirks/*
|
||||
!quirks/defaults.py
|
||||
|
|
|
@ -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
87
PYQUIRKS.mkdn
Normal 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.
|
|
@ -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
|
||||
----
|
||||
|
|
47
menus.py
47
menus.py
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
58
pyquirks.py
Normal 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
17
quirks/defaults.py
Normal 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"
|
Loading…
Reference in a new issue