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,7 +1123,32 @@ def _pyimp(): ...@@ -1130,7 +1123,32 @@ def _pyimp():
else: else:
return 'CPython' return 'CPython'
def normalize_exception(exc):
class MarkerEvaluation(object):
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(),
}
@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
@staticmethod
def normalize_exception(exc):
""" """
Given a SyntaxError from a marker evaluation, normalize the error message: Given a SyntaxError from a marker evaluation, normalize the error message:
- Remove indications of filename and line number. - Remove indications of filename and line number.
...@@ -1145,93 +1163,93 @@ def normalize_exception(exc): ...@@ -1145,93 +1163,93 @@ def normalize_exception(exc):
exc.msg = subs.get(exc.msg, exc.msg) exc.msg = subs.get(exc.msg, exc.msg)
return exc return exc
@classmethod
def invalid_marker(text): def and_test(cls, nodelist):
"""Validate text as a PEP 426 environment marker; return exception or False"""
try:
evaluate_marker(text)
except SyntaxError:
return normalize_exception(sys.exc_info()[1])
return False
def evaluate_marker(text, extra=None, _ops={}):
"""
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
Jython and has been superseded by the 'ast' module in Python 2.6 and
later.
"""
if not _ops:
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! # 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)]) return functools.reduce(operator.and_, [cls.interpret(nodelist[i]) for i in range(1,len(nodelist),2)])
def test(nodelist): @classmethod
def test(cls, nodelist):
# MUST NOT short-circuit evaluation, or invalid syntax can be skipped! # 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)]) return functools.reduce(operator.or_, [cls.interpret(nodelist[i]) for i in range(1,len(nodelist),2)])
def atom(nodelist): @classmethod
def atom(cls, nodelist):
t = nodelist[1][0] t = nodelist[1][0]
if t == token.LPAR: if t == token.LPAR:
if nodelist[2][0] == token.RPAR: if nodelist[2][0] == token.RPAR:
raise SyntaxError("Empty parentheses") raise SyntaxError("Empty parentheses")
return interpret(nodelist[2]) return cls.interpret(nodelist[2])
raise SyntaxError("Language feature not supported in environment markers") raise SyntaxError("Language feature not supported in environment markers")
def comparison(nodelist): @classmethod
def comparison(cls, nodelist):
if len(nodelist)>4: if len(nodelist)>4:
raise SyntaxError("Chained comparison not allowed in environment markers") raise SyntaxError("Chained comparison not allowed in environment markers")
comp = nodelist[2][1] comp = nodelist[2][1]
cop = comp[1] cop = comp[1]
if comp[0] == NAME: if comp[0] == token.NAME:
if len(nodelist[2]) == 3: if len(nodelist[2]) == 3:
if cop == 'not': if cop == 'not':
cop = 'not in' cop = 'not in'
else: else:
cop = 'is not' cop = 'is not'
try: try:
cop = _ops[cop] cop = cls.get_op(cop)
except KeyError: except KeyError:
raise SyntaxError(repr(cop)+" operator not allowed in environment markers") raise SyntaxError(repr(cop)+" operator not allowed in environment markers")
return cop(evaluate(nodelist[1]), evaluate(nodelist[3])) 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.
_ops.update({ This implementation uses the 'parser' module, which is not implemented on
symbol.test: test, symbol.and_test: and_test, symbol.atom: atom, Jython and has been superseded by the 'ast' module in Python 2.6 and
symbol.comparison: comparison, 'not in': lambda x,y: x not in y, later.
'in': lambda x,y: x in y, '==': operator.eq, '!=': operator.ne, """
}) return cls.interpret(parser.expr(text).totuple(1)[1])
if hasattr(symbol,'or_test'):
_ops[symbol.or_test] = test
def interpret(nodelist): @classmethod
def interpret(cls, 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