From c5f0b2714691a83f868b7d8d69ea3de6a82bc797 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 29 Oct 2022 10:25:24 -0400 Subject: [PATCH] Use RangeMap to define the ranges in one place. --- distutils/_collections.py | 115 +++++++++++++++++++++++++++++++++++ distutils/cygwinccompiler.py | 52 ++++++++-------- 2 files changed, 143 insertions(+), 24 deletions(-) diff --git a/distutils/_collections.py b/distutils/_collections.py index 98fce800..87055635 100644 --- a/distutils/_collections.py +++ b/distutils/_collections.py @@ -1,5 +1,7 @@ import collections +import functools import itertools +import operator # from jaraco.collections 3.5.1 @@ -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) diff --git a/distutils/cygwinccompiler.py b/distutils/cygwinccompiler.py index 9332db1d..db10bf46 100644 --- a/distutils/cygwinccompiler.py +++ b/distutils/cygwinccompiler.py @@ -12,6 +12,7 @@ import copy import shlex import warnings +import operator from subprocess import check_output from .unixccompiler import UnixCCompiler @@ -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(): @@ -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 = (