Skip to content

Commit

Permalink
Introduce parsing and normalization for markers with an extra value. …
Browse files Browse the repository at this point in the history
…Add unit tests.
  • Loading branch information
GabrielC101 committed Dec 11, 2017
1 parent d2ed39a commit fc7d3db
Show file tree
Hide file tree
Showing 2 changed files with 386 additions and 12 deletions.
129 changes: 122 additions & 7 deletions packaging/markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from ._compat import string_types
from .specifiers import Specifier, InvalidSpecifier
from .utils import canonicalize_name


__all__ = [
Expand Down Expand Up @@ -91,7 +92,7 @@ def serialize(self):
L("platform.version") | # PEP-345
L("platform.machine") | # PEP-345
L("platform.python_implementation") | # PEP-345
L("python_implementation") | # undocumented setuptools legacy
L("python_implementation") | # undocumented setuptools legacy
L("extra")
)
ALIASES = {
Expand All @@ -115,6 +116,12 @@ def serialize(self):
L("<")
)

MARKER_EXTRA_VARIABLE = L('extra')
MARKER_EXTRA_VARIABLE.setParseAction(lambda s, l, t: Variable(t[0]))
MARKER_EXTRA_OP = (L("==") | L("===")) | L("not in") | L("in")
MARKER_EXTRA_OP.setParseAction(lambda s, l, t: Op(t[0]))


MARKER_OP = VERSION_CMP | L("not in") | L("in")
MARKER_OP.setParseAction(lambda s, l, t: Op(t[0]))

Expand All @@ -138,6 +145,20 @@ def serialize(self):
MARKER = stringStart + MARKER_EXPR + stringEnd


MARKER_EXTRA_ITEM = Group(
MARKER_EXTRA_VARIABLE + MARKER_EXTRA_OP + MARKER_VALUE
)
MARKER_EXTRA_ITEM.setParseAction(lambda s, l, t: tuple(t[0]))
MARKER_EXTRA_EXPR = Forward()

MARKER_EXTRA_GROUP = Group(LPAREN + MARKER_EXTRA_EXPR + RPAREN)
MARKER_EXTRA_ATOM = MARKER_EXTRA_ITEM | MARKER_EXTRA_GROUP


MARKER_EXTRA_EXPR << MARKER_EXTRA_ATOM + ZeroOrMore(BOOLOP + MARKER_EXTRA_EXPR)
MARKER_EXTRA = stringStart + MARKER_EXTRA_EXPR + stringEnd


def _coerce_parse_result(results):
if isinstance(results, ParseResults):
return [_coerce_parse_result(i) for i in results]
Expand Down Expand Up @@ -272,19 +293,32 @@ def default_environment():
class Marker(object):

def __init__(self, marker):
try:
self._markers = _coerce_parse_result(MARKER.parseString(marker))
except ParseException as e:
err_str = "Invalid marker: {0!r}, parse error at {1!r}".format(
marker, marker[e.loc:e.loc + 8])
raise InvalidMarker(err_str)
self._marker_string = marker
extra_markers = MarkerExtraParser().get_extra_markers(
self._marker_string
)
if extra_markers:
good_names = MarkerExtraCleaner().clean_marker_extras(
extra_markers
)
self._markers = good_names
else:
self._markers = self.get_marker_not_extra(self._marker_string)

def __str__(self):
return _format_marker(self._markers)

def __repr__(self):
return "<Marker({0!r})>".format(str(self))

def get_marker_not_extra(self, marker):
try:
return _coerce_parse_result(MARKER.parseString(marker))
except ParseException as e2:
err_str = "Invalid marker: {0!r}, parse error at {1!r}".format(
marker, marker[e2.loc:e2.loc + 8])
raise InvalidMarker(err_str)

def evaluate(self, environment=None):
"""Evaluate a marker.
Expand All @@ -299,3 +333,84 @@ def evaluate(self, environment=None):
current_environment.update(environment)

return _evaluate_markers(self._markers, current_environment)


class MarkerExtraParser(object):

@classmethod
def get_extra_markers(cls, marker):
try:
tmp_markers = _coerce_parse_result(
MARKER_EXTRA.parseString(marker)
)
return tmp_markers
except ParseException:
return False


class MarkerExtraCleaner(object):

@classmethod
def clean_marker_extras(cls, markers):
clean_markers = []
for parsed_marker in markers:
clean_marker = cls._clean_marker_extra(parsed_marker)
clean_markers.append(clean_marker)
return clean_markers

@classmethod
def _clean_marker_extra(cls, marker):
extra_locations = cls._get_extra_index_location(marker)
if extra_locations:
return cls._fix_extra_values(extra_locations, marker)
else:
return marker

@classmethod
def _get_extra_index_location(cls, marker):
locations = []
if len(marker) < 3:
return locations
for index in range(len(marker)):
if cls._is_variable(marker[index]):
if cls._is_op(marker[index + 1]):
if cls._is_value(marker[index + 2]):
locations.append(index)
return locations

@classmethod
def _is_variable(cls, variable):
return cls.check_attribute(variable, Variable, 'value', 'extra')

@classmethod
def _is_op(cls, op):
return cls.check_attribute(op, Op, 'value', ('==', '===', 'is'))

@classmethod
def _is_value(cls, value):
return isinstance(value, Value)

@staticmethod
def check_attribute(obj, object_types, attribute_names, attribute_values):
if not isinstance(attribute_values, (list, tuple)):
attribute_values = (attribute_values,)
if not isinstance(object_types, (list, tuple)):
object_types = (object_types,)
if not isinstance(attribute_names, (list, tuple)):
attribute_names = (attribute_names,)
for attribute_value in attribute_values:
for object_type in object_types:
for attribute_name in attribute_names:
if isinstance(obj, object_type):
if getattr(obj, attribute_name) == attribute_value:
return True
return False

@classmethod
def _fix_extra_values(cls, extra_locations, marker):
parsed_marker = list(marker)
for extra_location in extra_locations:
parsed_marker[extra_location + 2].value = canonicalize_name(
parsed_marker[extra_location + 2].value
)
return tuple(parsed_marker)
Loading

0 comments on commit fc7d3db

Please sign in to comment.