Commit 685d3b42 authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

Sphinx extension: Allow evaluation of Python expressions

There are quite many hardcoded constants (e.g. “[…] one of ``file``,
``lvm-pv`` or ``lvm-vg`` […]). By using constants it'll be easier to
identify these.

With such lists of values it's also easy to miss some when
extending/changing something. By adding assertions inlined with reST,
these can also be detected.
Signed-off-by: default avatarMichael Hanselmann <>
Reviewed-by: default avatarIustin Pop <>
parent dac59ac5
...@@ -27,10 +27,15 @@ import operator ...@@ -27,10 +27,15 @@ import operator
from cStringIO import StringIO from cStringIO import StringIO
import docutils.statemachine import docutils.statemachine
import docutils.nodes
import docutils.utils
import sphinx.errors import sphinx.errors
import sphinx.util.compat import sphinx.util.compat
from ganeti import constants
from ganeti import compat
from ganeti import errors
from ganeti import utils from ganeti import utils
from ganeti import opcodes from ganeti import opcodes
from ganeti import ht from ganeti import ht
...@@ -38,6 +43,9 @@ from ganeti import ht ...@@ -38,6 +43,9 @@ from ganeti import ht
COMMON_PARAM_NAMES = map(operator.itemgetter(0), opcodes.OpCode.OP_PARAMS) COMMON_PARAM_NAMES = map(operator.itemgetter(0), opcodes.OpCode.OP_PARAMS)
#: Namespace for evaluating expressions
EVAL_NS = dict(compat=compat, constants=constants, utils=utils, errors=errors)
class OpcodeError(sphinx.errors.SphinxError): class OpcodeError(sphinx.errors.SphinxError):
category = "Opcode error" category = "Opcode error"
...@@ -150,8 +158,59 @@ class OpcodeParams(sphinx.util.compat.Directive): ...@@ -150,8 +158,59 @@ class OpcodeParams(sphinx.util.compat.Directive):
return [] return []
def PythonEvalRole(role, rawtext, text, lineno, inliner,
options={}, content=[]):
"""Custom role to evaluate Python expressions.
The expression's result is included as a literal.
code = docutils.utils.unescape(text, restore_backslashes=True)
result = eval(code, EVAL_NS)
except Exception, err:
msg = inliner.reporter.error("Failed to evaluate %r: %s" % (code, err),
return ([inliner.problematic(rawtext, rawtext, msg)], [msg])
node = docutils.nodes.literal("", unicode(result), **options)
return ([node], [])
class PythonAssert(sphinx.util.compat.Directive):
"""Custom directive for writing assertions.
The content must be a valid Python expression. If its result does not
evaluate to C{True}, the assertion fails.
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
def run(self):
code = "\n".join(self.content)
result = eval(code, EVAL_NS)
except Exception, err:
raise self.error("Failed to evaluate %r: %s" % (code, err))
if not result:
raise self.error("Assertion failed: %s" % (code, ))
return []
def setup(app): def setup(app):
"""Sphinx extension callback. """Sphinx extension callback.
""" """
app.add_directive("opcode_params", OpcodeParams) app.add_directive("opcode_params", OpcodeParams)
app.add_directive("pyassert", PythonAssert)
app.add_role("pyeval", PythonEvalRole)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment