SupyML: Add a lot of features, unit tests, and fill the README.txt with a description of the language syntax.
parent
62bb41045d
commit
bbf9f37356
|
@ -1 +1,65 @@
|
||||||
Insert a description of your plugin here, with any notes, etc. about using it.
|
This is a document that describe the SupyML language
|
||||||
|
|
||||||
|
Global syntax
|
||||||
|
=============
|
||||||
|
|
||||||
|
SupyML is a language based on the XML syntax. The official SupyML interpreter
|
||||||
|
uses the xml.dom.minidom parser. Minidom handles the format error; the
|
||||||
|
exceptions it raises are not handled by the SupyML parser.
|
||||||
|
|
||||||
|
Commands
|
||||||
|
========
|
||||||
|
|
||||||
|
SupyML is based on call to SupyBot commands. A command is call like that:
|
||||||
|
<commandName>arg1 arg2 arg3</commandName>
|
||||||
|
If you want to call a command in a specific plugin, use this syntax:
|
||||||
|
<pluginName>commandName arg1 arg2 arg3</pluginName>
|
||||||
|
|
||||||
|
Variables
|
||||||
|
=========
|
||||||
|
|
||||||
|
To get the value of a variable, type:
|
||||||
|
<var name="variableName" />
|
||||||
|
|
||||||
|
To set the value, type:
|
||||||
|
<set name="variableName">newValue</set>
|
||||||
|
|
||||||
|
Note that all variable are strings, because SupyML and SupyBot commands
|
||||||
|
processing both use only strings.
|
||||||
|
Some plugins, like Conditional provides different handlings for strings and
|
||||||
|
numerics, but it is their own problems.
|
||||||
|
|
||||||
|
Variable lifetime
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
When setting a new variable, the variable is readable and writable in the
|
||||||
|
node where it is created, and in the child nodes.
|
||||||
|
If a child node write a variable defined in a parent node, the new value of
|
||||||
|
the variable will be available in the parent node.
|
||||||
|
|
||||||
|
About the names
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Because of/thanks to the syntax of the language, names can easily be anything,
|
||||||
|
even if things other language doesn't like, for example: special chars,
|
||||||
|
spaces, starting with a number, empty, etc.
|
||||||
|
But, this is highly deprecated, and it raises a warning.
|
||||||
|
|
||||||
|
Conditions
|
||||||
|
==========
|
||||||
|
|
||||||
|
SupyML has not (yet) conditions. Use the Conditional plugin to do that.
|
||||||
|
|
||||||
|
Loops
|
||||||
|
=====
|
||||||
|
|
||||||
|
Loops are the main feature provided by SupyML. Here is the global syntax:
|
||||||
|
<loop><loopType>boolean</loopType>command arg1 arg2 arg3</loop>
|
||||||
|
Use conditions to have changing booleans ;)
|
||||||
|
|
||||||
|
While loop
|
||||||
|
----------
|
||||||
|
|
||||||
|
The syntax of the while loop is:
|
||||||
|
<loop><while>boolean</while>command arg1 arg2 arg3</loop>
|
||||||
|
The command is run while the boolean is true.
|
||||||
|
|
|
@ -28,7 +28,10 @@
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
|
import re
|
||||||
|
import copy
|
||||||
import Queue
|
import Queue
|
||||||
|
import supybot.world as world
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
import supybot.utils as utils
|
import supybot.utils as utils
|
||||||
from supybot.commands import *
|
from supybot.commands import *
|
||||||
|
@ -70,9 +73,11 @@ class SupyMLParser:
|
||||||
self._irc = irc
|
self._irc = irc
|
||||||
self._msg = msg
|
self._msg = msg
|
||||||
self._code = code
|
self._code = code
|
||||||
|
self.warnings = []
|
||||||
self._parse()
|
self._parse()
|
||||||
|
|
||||||
def _run(self, code, proxify):
|
def _run(self, code, proxify):
|
||||||
|
"""Runs the command using Supybot engine"""
|
||||||
tokens = callbacks.tokenize(str(code))
|
tokens = callbacks.tokenize(str(code))
|
||||||
if proxify:
|
if proxify:
|
||||||
fakeIrc = FakeIrc(self._irc)
|
fakeIrc = FakeIrc(self._irc)
|
||||||
|
@ -83,47 +88,88 @@ class SupyMLParser:
|
||||||
return fakeIrc._data
|
return fakeIrc._data
|
||||||
|
|
||||||
def _parse(self):
|
def _parse(self):
|
||||||
|
"""Returns a dom object from self._code."""
|
||||||
dom = minidom.parseString(self._code)
|
dom = minidom.parseString(self._code)
|
||||||
return self._processDocument(dom)
|
return self._processDocument(dom)
|
||||||
|
|
||||||
def _processDocument(self, dom):
|
def _processDocument(self, dom):
|
||||||
|
"""Handles the root node and call child nodes"""
|
||||||
for childNode in dom.childNodes:
|
for childNode in dom.childNodes:
|
||||||
if childNode.__class__ == minidom.Element:
|
if isinstance(childNode, minidom.Element):
|
||||||
self._processNode(childNode, False)
|
self._processNode(childNode, {}, False)
|
||||||
|
|
||||||
def _processNode(self, node, proxify=True):
|
def _processNode(self, node, variables, proxifyIrc=True):
|
||||||
if node.nodeName == 'loop':
|
"""Returns the value of an internapreted node.
|
||||||
return self._processLoop(node, proxify)
|
|
||||||
elif node.nodeName == 'if':
|
Takes an optional attribute, passed to self._run() that mean if the
|
||||||
return self._processId(node, proxify)
|
Irc object should be proxified. If it is not, the real Irc object is
|
||||||
elif node.nodeName == 'var':
|
used, datas are sent to IRC, and this function will return None."""
|
||||||
return self._processVar(node, proxify)
|
if isinstance(node, minidom.Text):
|
||||||
|
return node.data
|
||||||
output = node.nodeName + ' '
|
output = node.nodeName + ' '
|
||||||
|
newVariables = copy.deepcopy(variables)
|
||||||
for childNode in node.childNodes:
|
for childNode in node.childNodes:
|
||||||
if childNode.__class__ == minidom.Text:
|
if not repr(node) == repr(childNode.parentNode):
|
||||||
output += childNode.data
|
print "CONTINUING"
|
||||||
elif childNode.__class__ == minidom.Element:
|
continue
|
||||||
output += self._processNode(childNode)
|
if childNode.nodeName == 'loop':
|
||||||
value = self._run(str(output), proxify)
|
output += self._processLoop(childNode, newVariables)
|
||||||
|
elif childNode.nodeName == 'if':
|
||||||
|
output += self._processId(childNode, newVariables)
|
||||||
|
elif childNode.nodeName == 'var':
|
||||||
|
output += self._processVar(childNode, newVariables)
|
||||||
|
elif childNode.nodeName == 'set':
|
||||||
|
output += self._processSet(childNode, newVariables)
|
||||||
|
else:
|
||||||
|
output += self._processNode(childNode, newVariables) or ''
|
||||||
|
for key in variables:
|
||||||
|
variables[key] = newVariables[key]
|
||||||
|
value = self._run(output, proxifyIrc)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _processLoop(self, node, proxify=True):
|
# Don't proxify variables
|
||||||
raise NotImplemented
|
def _processSet(self, node, variables):
|
||||||
def _processIf(self, node, proxify=True):
|
"""Handles the <set> tag"""
|
||||||
raise NotImplemented
|
variableName = str(node.attributes['name'].value)
|
||||||
def _processVar(self, node, proxify=True):
|
value = ''
|
||||||
raise NotImplemented
|
for childNode in node.childNodes:
|
||||||
|
value += self._processNode(childNode, variables)
|
||||||
|
variables.update({variableName: value})
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def _processVar(self, node, variables):
|
||||||
|
"""Handles the <var /> tag"""
|
||||||
|
variableName = node.attributes['name'].value
|
||||||
|
try:
|
||||||
|
return variables[variableName]
|
||||||
|
except KeyError:
|
||||||
|
self.warnings.append('Access to non-existing variable: %s' %
|
||||||
|
variableName)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def _processLoop(self, node, variables):
|
||||||
|
"""Handles the <loop> tag"""
|
||||||
|
return '<NOT IMPLEMENTED>'
|
||||||
|
|
||||||
|
def _checkVariableName(self, variableName):
|
||||||
|
if len(variableName) == 0:
|
||||||
|
self.warnings.append('Empty variable name')
|
||||||
|
if re.match('\W+', variableName):
|
||||||
|
self.warnings.append('Variable name shouldn\'t contain '
|
||||||
|
'special chars (%s)' % variableName)
|
||||||
|
|
||||||
class SupyML(callbacks.Plugin):
|
class SupyML(callbacks.Plugin):
|
||||||
"""SupyML is a plugin that read SupyML scripts.
|
"""SupyML is a plugin that read SupyML scripts.
|
||||||
This scripts (Supybot Markup Language) are script written in a XML-based
|
This scripts (Supybot Markup Language) are script written in a XML-based
|
||||||
language."""
|
language."""
|
||||||
threaded = True
|
#threaded = True
|
||||||
def eval(self, irc, msg, args, code):
|
def eval(self, irc, msg, args, code):
|
||||||
"""<SupyML script>
|
"""<SupyML script>
|
||||||
|
|
||||||
Executes the <SupyML script>"""
|
Executes the <SupyML script>"""
|
||||||
parser = SupyMLParser(self, irc, msg, code)
|
parser = SupyMLParser(self, irc, msg, code)
|
||||||
|
if world.testing and len(parser.warnings) != 0:
|
||||||
|
print parser.warnings
|
||||||
|
|
||||||
eval=wrap(eval, ['text'])
|
eval=wrap(eval, ['text'])
|
||||||
SupyML = internationalizeDocstring(SupyML)
|
SupyML = internationalizeDocstring(SupyML)
|
||||||
|
|
|
@ -36,7 +36,7 @@ class SupyMLTestCase(ChannelPluginTestCase):
|
||||||
#################################
|
#################################
|
||||||
# Utilities
|
# Utilities
|
||||||
def _getIfAnswerIsEqual(self, msg):
|
def _getIfAnswerIsEqual(self, msg):
|
||||||
time.sleep(0.5)
|
time.sleep(0.2)
|
||||||
m = self.irc.takeMsg()
|
m = self.irc.takeMsg()
|
||||||
while m is not None:
|
while m is not None:
|
||||||
if repr(m) == repr(msg):
|
if repr(m) == repr(msg):
|
||||||
|
@ -57,20 +57,61 @@ class SupyMLTestCase(ChannelPluginTestCase):
|
||||||
self.failIf(self._getIfAnswerIsEqual(answer) == False)
|
self.failIf(self._getIfAnswerIsEqual(answer) == False)
|
||||||
|
|
||||||
def testNoMoreThanOneAnswer(self):
|
def testNoMoreThanOneAnswer(self):
|
||||||
self.assertResponse('SupyML eval <echo><echo>foo</echo>'
|
self.assertResponse('SupyML eval '
|
||||||
'<echo>bar</echo></echo>',
|
'<echo>'
|
||||||
|
'<echo>foo</echo>'
|
||||||
|
'<echo>bar</echo>'
|
||||||
|
'</echo>',
|
||||||
'foobar')
|
'foobar')
|
||||||
|
|
||||||
def testVar(self):
|
def testVar(self):
|
||||||
self.assertResponse('SupyML eval <echo><var>foo<set>bar</set></var>'
|
self.assertResponse('SupyML eval '
|
||||||
'<echo><var>foo</var></echo></echo>',
|
'<echo>'
|
||||||
|
'<set name="foo">bar</set>'
|
||||||
|
'<echo>'
|
||||||
|
'<var name="foo" />'
|
||||||
|
'</echo>'
|
||||||
|
'</echo>',
|
||||||
|
'bar')
|
||||||
|
|
||||||
|
def testVarLifetime(self):
|
||||||
|
self.assertResponse('SupyML eval '
|
||||||
|
'<echo>'
|
||||||
|
'<set name="foo">bar</set>'
|
||||||
|
'<echo>'
|
||||||
|
'<var name="foo" />'
|
||||||
|
'baz'
|
||||||
|
'</echo>'
|
||||||
|
'</echo>',
|
||||||
|
'barbaz')
|
||||||
|
self.assertResponse('SupyML eval '
|
||||||
|
'<echo>'
|
||||||
|
'<set name="foo">bar</set>'
|
||||||
|
'<echo>'
|
||||||
|
'<set name="foo">bar</set>'
|
||||||
|
'<var name="foo" />'
|
||||||
|
'</echo>'
|
||||||
|
'<echo>'
|
||||||
|
'<var name="foo" />'
|
||||||
|
'</echo>'
|
||||||
|
'</echo>',
|
||||||
'bar')
|
'bar')
|
||||||
|
|
||||||
def testWhile(self):
|
def testWhile(self):
|
||||||
self.assertResponse('SupyML eval <echo>'
|
self.assertResponse('SupyML eval '
|
||||||
'<var>foo<set>1</set></var>'
|
'<echo>'
|
||||||
'<loop><while><nne><var>foo</var> 5</nne></while>'
|
'<set name="foo">bar</set>'
|
||||||
'<echo>bar</echo></loop></echo>',
|
'<loop>'
|
||||||
|
'<while>'
|
||||||
|
'<nne>'
|
||||||
|
'<var name="foo" /> 5'
|
||||||
|
'</nne>'
|
||||||
|
'</while>'
|
||||||
|
'<echo>'
|
||||||
|
'bar'
|
||||||
|
'</echo>'
|
||||||
|
'</loop>'
|
||||||
|
'</echo>',
|
||||||
'bar'*5)
|
'bar'*5)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue