Commit 5dfdad23 authored by Jason R. Coombs's avatar Jason R. Coombs

Encapsulate marker evaluation functionality in a class.

parent 70679787
...@@ -24,6 +24,9 @@ import warnings ...@@ -24,6 +24,9 @@ import warnings
import stat import stat
import functools import functools
import pkgutil import pkgutil
import token
import symbol
import operator
from pkgutil import get_importer from pkgutil import get_importer
try: try:
...@@ -1103,16 +1106,6 @@ def to_filename(name): ...@@ -1103,16 +1106,6 @@ def to_filename(name):
""" """
return name.replace('-','_') return name.replace('-','_')
_marker_values = {
'os_name': lambda: os.name,
'sys_platform': lambda: sys.platform,
'python_full_version': lambda: sys.version.split()[0],
'python_version': lambda:'%s.%s' % (sys.version_info[0], sys.version_info[1]),
'platform_version': lambda: _platinfo('version'),
'platform_machine': lambda: _platinfo('machine'),
'python_implementation': lambda: _platinfo('python_implementation') or _pyimp(),
}
def _platinfo(attr): def _platinfo(attr):
try: try:
import platform import platform
...@@ -1130,108 +1123,133 @@ def _pyimp(): ...@@ -1130,108 +1123,133 @@ def _pyimp():
else: else:
return 'CPython' return 'CPython'
def normalize_exception(exc):
""" class MarkerEvaluation(object):
Given a SyntaxError from a marker evaluation, normalize the error message: values = {
- Remove indications of filename and line number. 'os_name': lambda: os.name,
- Replace platform-specific error messages with standard error messages. 'sys_platform': lambda: sys.platform,
""" 'python_full_version': lambda: sys.version.split()[0],
subs = { 'python_version': lambda:'%s.%s' % (sys.version_info[0], sys.version_info[1]),
'unexpected EOF while parsing': 'invalid syntax', 'platform_version': lambda: _platinfo('version'),
'parenthesis is never closed': 'invalid syntax', 'platform_machine': lambda: _platinfo('machine'),
'python_implementation': lambda: _platinfo('python_implementation') or _pyimp(),
} }
exc.filename = None
exc.lineno = None
exc.msg = subs.get(exc.msg, exc.msg)
return exc
@classmethod
def is_invalid_marker(cls, text):
"""
Validate text as a PEP 426 environment marker; return an exception
if invalid or False otherwise.
"""
try:
cls.evaluate_marker(text)
except SyntaxError:
return cls.normalize_exception(sys.exc_info()[1])
return False
def invalid_marker(text): @staticmethod
"""Validate text as a PEP 426 environment marker; return exception or False""" def normalize_exception(exc):
try: """
evaluate_marker(text) Given a SyntaxError from a marker evaluation, normalize the error message:
except SyntaxError: - Remove indications of filename and line number.
return normalize_exception(sys.exc_info()[1]) - Replace platform-specific error messages with standard error messages.
return False """
subs = {
'unexpected EOF while parsing': 'invalid syntax',
'parenthesis is never closed': 'invalid syntax',
}
exc.filename = None
exc.lineno = None
exc.msg = subs.get(exc.msg, exc.msg)
return exc
@classmethod
def and_test(cls, nodelist):
# MUST NOT short-circuit evaluation, or invalid syntax can be skipped!
return functools.reduce(operator.and_, [cls.interpret(nodelist[i]) for i in range(1,len(nodelist),2)])
@classmethod
def test(cls, nodelist):
# MUST NOT short-circuit evaluation, or invalid syntax can be skipped!
return functools.reduce(operator.or_, [cls.interpret(nodelist[i]) for i in range(1,len(nodelist),2)])
@classmethod
def atom(cls, nodelist):
t = nodelist[1][0]
if t == token.LPAR:
if nodelist[2][0] == token.RPAR:
raise SyntaxError("Empty parentheses")
return cls.interpret(nodelist[2])
raise SyntaxError("Language feature not supported in environment markers")
def evaluate_marker(text, extra=None, _ops={}): @classmethod
""" def comparison(cls, nodelist):
Evaluate a PEP 426 environment marker on CPython 2.4+. if len(nodelist)>4:
Return a boolean indicating the marker result in this environment. raise SyntaxError("Chained comparison not allowed in environment markers")
Raise SyntaxError if marker is invalid. comp = nodelist[2][1]
cop = comp[1]
if comp[0] == token.NAME:
if len(nodelist[2]) == 3:
if cop == 'not':
cop = 'not in'
else:
cop = 'is not'
try:
cop = cls.get_op(cop)
except KeyError:
raise SyntaxError(repr(cop)+" operator not allowed in environment markers")
return cop(cls.evaluate(nodelist[1]), cls.evaluate(nodelist[3]))
@classmethod
def get_op(cls, op):
ops = {
symbol.test: cls.test,
symbol.and_test: cls.and_test,
symbol.atom: cls.atom,
symbol.comparison: cls.comparison,
'not in': lambda x, y: x not in y,
'in': lambda x, y: x in y,
'==': operator.eq,
'!=': operator.ne,
}
if hasattr(symbol, 'or_test'):
ops[symbol.or_test] = cls.test
return ops[op]
@classmethod
def evaluate_marker(cls, text, extra=None):
"""
Evaluate a PEP 426 environment marker on CPython 2.4+.
Return a boolean indicating the marker result in this environment.
Raise SyntaxError if marker is invalid.
This implementation uses the 'parser' module, which is not implemented on This implementation uses the 'parser' module, which is not implemented on
Jython and has been superseded by the 'ast' module in Python 2.6 and Jython and has been superseded by the 'ast' module in Python 2.6 and
later. later.
""" """
return cls.interpret(parser.expr(text).totuple(1)[1])
if not _ops: @classmethod
def interpret(cls, nodelist):
from token import NAME, STRING
import token
import symbol
import operator
def and_test(nodelist):
# MUST NOT short-circuit evaluation, or invalid syntax can be skipped!
return functools.reduce(operator.and_, [interpret(nodelist[i]) for i in range(1,len(nodelist),2)])
def test(nodelist):
# MUST NOT short-circuit evaluation, or invalid syntax can be skipped!
return functools.reduce(operator.or_, [interpret(nodelist[i]) for i in range(1,len(nodelist),2)])
def atom(nodelist):
t = nodelist[1][0]
if t == token.LPAR:
if nodelist[2][0] == token.RPAR:
raise SyntaxError("Empty parentheses")
return interpret(nodelist[2])
raise SyntaxError("Language feature not supported in environment markers")
def comparison(nodelist):
if len(nodelist)>4:
raise SyntaxError("Chained comparison not allowed in environment markers")
comp = nodelist[2][1]
cop = comp[1]
if comp[0] == NAME:
if len(nodelist[2]) == 3:
if cop == 'not':
cop = 'not in'
else:
cop = 'is not'
try:
cop = _ops[cop]
except KeyError:
raise SyntaxError(repr(cop)+" operator not allowed in environment markers")
return cop(evaluate(nodelist[1]), evaluate(nodelist[3]))
_ops.update({
symbol.test: test, symbol.and_test: and_test, symbol.atom: atom,
symbol.comparison: comparison, 'not in': lambda x,y: x not in y,
'in': lambda x,y: x in y, '==': operator.eq, '!=': operator.ne,
})
if hasattr(symbol,'or_test'):
_ops[symbol.or_test] = test
def interpret(nodelist):
while len(nodelist)==2: nodelist = nodelist[1] while len(nodelist)==2: nodelist = nodelist[1]
try: try:
op = _ops[nodelist[0]] op = cls.get_op(nodelist[0])
except KeyError: except KeyError:
raise SyntaxError("Comparison or logical expression expected") raise SyntaxError("Comparison or logical expression expected")
return op(nodelist) return op(nodelist)
def evaluate(nodelist): @classmethod
def evaluate(cls, nodelist):
while len(nodelist)==2: nodelist = nodelist[1] while len(nodelist)==2: nodelist = nodelist[1]
kind = nodelist[0] kind = nodelist[0]
name = nodelist[1] name = nodelist[1]
if kind==NAME: if kind==token.NAME:
try: try:
op = _marker_values[name] op = cls.values[name]
except KeyError: except KeyError:
raise SyntaxError("Unknown name %r" % name) raise SyntaxError("Unknown name %r" % name)
return op() return op()
if kind==STRING: if kind==token.STRING:
s = nodelist[1] s = nodelist[1]
if s[:1] not in "'\"" or s.startswith('"""') or s.startswith("'''") \ if s[:1] not in "'\"" or s.startswith('"""') or s.startswith("'''") \
or '\\' in s: or '\\' in s:
...@@ -1240,8 +1258,6 @@ def evaluate_marker(text, extra=None, _ops={}): ...@@ -1240,8 +1258,6 @@ def evaluate_marker(text, extra=None, _ops={}):
return s[1:-1] return s[1:-1]
raise SyntaxError("Language feature not supported in environment markers") raise SyntaxError("Language feature not supported in environment markers")
return interpret(parser.expr(text).totuple(1)[1])
def _markerlib_evaluate(text): def _markerlib_evaluate(text):
""" """
Evaluate a PEP 426 environment marker using markerlib. Evaluate a PEP 426 environment marker using markerlib.
...@@ -1262,7 +1278,11 @@ def _markerlib_evaluate(text): ...@@ -1262,7 +1278,11 @@ def _markerlib_evaluate(text):
raise SyntaxError(e.args[0]) raise SyntaxError(e.args[0])
return result return result
if 'parser' not in globals(): invalid_marker = MarkerEvaluation.is_invalid_marker
if 'parser' in globals():
evaluate_marker = MarkerEvaluation.evaluate_marker
else:
# fallback to less-complete _markerlib implementation if 'parser' module # fallback to less-complete _markerlib implementation if 'parser' module
# is not available. # is not available.
evaluate_marker = _markerlib_evaluate evaluate_marker = _markerlib_evaluate
......
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