Skip to content

Commit

Permalink
Use RangeMap to define the ranges in one place.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaraco committed Oct 29, 2022
1 parent 15266db commit c5f0b27
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 24 deletions.
115 changes: 115 additions & 0 deletions distutils/_collections.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import collections
import functools
import itertools
import operator


# from jaraco.collections 3.5.1
Expand Down Expand Up @@ -54,3 +56,116 @@ def __contains__(self, other):

def __len__(self):
return len(list(iter(self)))


# from jaraco.collections 3.7
class RangeMap(dict):
"""
A dictionary-like object that uses the keys as bounds for a range.
Inclusion of the value for that range is determined by the
key_match_comparator, which defaults to less-than-or-equal.
A value is returned for a key if it is the first key that matches in
the sorted list of keys.
One may supply keyword parameters to be passed to the sort function used
to sort keys (i.e. key, reverse) as sort_params.
Let's create a map that maps 1-3 -> 'a', 4-6 -> 'b'
>>> r = RangeMap({3: 'a', 6: 'b'}) # boy, that was easy
>>> r[1], r[2], r[3], r[4], r[5], r[6]
('a', 'a', 'a', 'b', 'b', 'b')
Even float values should work so long as the comparison operator
supports it.
>>> r[4.5]
'b'
But you'll notice that the way rangemap is defined, it must be open-ended
on one side.
>>> r[0]
'a'
>>> r[-1]
'a'
One can close the open-end of the RangeMap by using undefined_value
>>> r = RangeMap({0: RangeMap.undefined_value, 3: 'a', 6: 'b'})
>>> r[0]
Traceback (most recent call last):
...
KeyError: 0
One can get the first or last elements in the range by using RangeMap.Item
>>> last_item = RangeMap.Item(-1)
>>> r[last_item]
'b'
.last_item is a shortcut for Item(-1)
>>> r[RangeMap.last_item]
'b'
Sometimes it's useful to find the bounds for a RangeMap
>>> r.bounds()
(0, 6)
RangeMap supports .get(key, default)
>>> r.get(0, 'not found')
'not found'
>>> r.get(7, 'not found')
'not found'
One often wishes to define the ranges by their left-most values,
which requires use of sort params and a key_match_comparator.
>>> r = RangeMap({1: 'a', 4: 'b'},
... sort_params=dict(reverse=True),
... key_match_comparator=operator.ge)
>>> r[1], r[2], r[3], r[4], r[5], r[6]
('a', 'a', 'a', 'b', 'b', 'b')
That wasn't nearly as easy as before, so an alternate constructor
is provided:
>>> r = RangeMap.left({1: 'a', 4: 'b', 7: RangeMap.undefined_value})
>>> r[1], r[2], r[3], r[4], r[5], r[6]
('a', 'a', 'a', 'b', 'b', 'b')
"""

def __init__(self, source, sort_params={}, key_match_comparator=operator.le):
dict.__init__(self, source)
self.sort_params = sort_params
self.match = key_match_comparator

@classmethod
def left(cls, source):
return cls(
source, sort_params=dict(reverse=True), key_match_comparator=operator.ge
)

def __getitem__(self, item):
sorted_keys = sorted(self.keys(), **self.sort_params)
if isinstance(item, RangeMap.Item):
result = self.__getitem__(sorted_keys[item])
else:
key = self._find_first_match_(sorted_keys, item)
result = dict.__getitem__(self, key)
if result is RangeMap.undefined_value:
raise KeyError(key)
return result

def get(self, key, default=None):
"""
Return the value for key if key is in the dictionary, else default.
If default is not given, it defaults to None, so that this method
never raises a KeyError.
"""
try:
return self[key]
except KeyError:
return default

def _find_first_match_(self, keys, item):
is_match = functools.partial(self.match, item)
matches = list(filter(is_match, keys))
if matches:
return matches[0]
raise KeyError(item)

def bounds(self):
sorted_keys = sorted(self.keys(), **self.sort_params)
return (sorted_keys[RangeMap.first_item], sorted_keys[RangeMap.last_item])

# some special values for the RangeMap
undefined_value = type(str('RangeValueUndefined'), (), {})()

class Item(int):
"RangeMap Item"

first_item = Item(0)
last_item = Item(-1)
52 changes: 28 additions & 24 deletions distutils/cygwinccompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import copy
import shlex
import warnings
import operator
from subprocess import check_output

from .unixccompiler import UnixCCompiler
Expand All @@ -23,24 +24,30 @@
CompileError,
)
from .version import LooseVersion, suppress_known_deprecation


_msvcr_lookup = {
# MSVC 7.0
1300: ['msvcr70'],
# MSVC 7.1
1310: ['msvcr71'],
# VS2005 / MSVC 8.0
1400: ['msvcr80'],
# VS2008 / MSVC 9.0
1500: ['msvcr90'],
# VS2010 / MSVC 10.0
1600: ['msvcr100'],
# VS2012 / MSVC 11.0
1700: ['msvcr110'],
# VS2013 / MSVC 12.0
1800: ['msvcr120'],
}
from ._collections import RangeMap


_msvcr_lookup = RangeMap.left(
{
# MSVC 7.0
1300: ['msvcr70'],
# MSVC 7.1
1310: ['msvcr71'],
# VS2005 / MSVC 8.0
1400: ['msvcr80'],
# VS2008 / MSVC 9.0
1500: ['msvcr90'],
# VS2010 / MSVC 10.0
1600: ['msvcr100'],
# VS2012 / MSVC 11.0
1700: ['msvcr110'],
# VS2013 / MSVC 12.0
1800: ['msvcr120'],
# VS2015 / MSVC 14.0
1900: ['ucrt', 'vcruntime140'],
2000: RangeMap.undefined_value,
},
)


def get_msvcr():
Expand All @@ -52,13 +59,10 @@ def get_msvcr():
return

msc_ver = int(match.group(1))
if 1900 <= msc_ver < 2000:
# VS2015 / MSVC 14.0
return ['ucrt', 'vcruntime140']
if msc_ver in _msvcr_lookup:
try:
return _msvcr_lookup[msc_ver]

raise ValueError("Unknown MS Compiler version %s " % msc_ver)
except KeyError:
raise ValueError("Unknown MS Compiler version %s " % msc_ver)


_runtime_library_dirs_msg = (
Expand Down

0 comments on commit c5f0b27

Please sign in to comment.