Michele Lacchia avatar Michele Lacchia committed 2b5978d

Fix #7 and move some functions to util

Comments (0)

Files changed (4)

 dist
 .tox
 docs/_build
+requirements.txt

wheel/bdist_wheel.py

 from distutils import log as logger
 import shutil
 
+from wheel.util import get_abbr_impl, get_impl_ver
 from wheel.archive import archive_wheelfile
 
 def safer_name(name):
         self.set_undefined_options('bdist',
                                    *zip(need_options, need_options))
         
-        self.root_is_purelib = self.distribution.is_pure()        
-
-    def get_abbr_impl(self):
-        """Return abbreviated implementation name"""
-        if hasattr(sys, 'pypy_version_info'):
-            pyimpl = 'pp'
-        elif sys.platform.startswith('java'):
-            pyimpl = 'jy'
-        elif sys.platform == 'cli':
-            pyimpl = 'ip'
-        else:
-            pyimpl = 'cp'
-        return pyimpl
+        self.root_is_purelib = self.distribution.is_pure()
     
     @property
     def wheel_dist_name(self):
     def get_archive_basename(self):
         """Return archive name without extension"""
         purity = self.distribution.is_pure()
-        impl_ver = sysconfig.get_config_var("py_version_nodot")
-        if not impl_ver:
-            impl_ver = ''.join(map(str, sys.version_info[:2]))
+        impl_ver = get_impl_ver()
         plat_name = 'noarch'
         abi_tag = 'noabi'
         impl_name = 'py'
         if not purity:
             plat_name = self.plat_name.replace('-', '_').replace('.', '_')
-            impl_name = self.get_abbr_impl()
+            impl_name = get_abbr_impl()
             # PEP 3149 -- no SOABI in Py 2
             # For PyPy?
             # "pp%s%s" % (sys.pypy_version_info.major, 
 from email.parser import Parser
 
 from .decorator import reify
-from .util import urlsafe_b64encode, utf8, to_json
+from .util import urlsafe_b64encode, utf8, to_json, parse_version
 
 # The next major version after this version of the 'wheel' tool:
 VERSION_TOO_HIGH = (1, 0)
         self.parsed_filename = WHEEL_INFO_RE(basename)
         if not basename.endswith('.whl') or self.parsed_filename is None:
             raise ValueError("Bad filename '%s'" % filename)
+
+    def __repr__(self):
+        return self.filename
         
     @reify
     def zipfile(self):
     def arity(self):
         return len(list(self.compatibility_tags))
 
+    def compatibility_rank(self, supported):
+        preferences = []
+        for tag in self.compatibility_tags:
+            try:
+                preferences.append(supported.index(tag))
+            # Tag not present
+            except ValueError:
+                pass
+        return (min(preferences), self.arity)
+
     @reify
     def parsed_wheel_info(self):
         """Parse wheel metadata"""
         if verify != signature:
             return False
         return True
-        
+
+
+def pick_best(candidates, supported, top=True):
+    # XXX: Are candidates Wheels or paths (strings)?
+    # For now I'll assume they're already wheels.
+
+    ranked = []
+    for whl in candidates:
+        try:
+            preference, arity = whl.compatibility_rank(supported)
+        except ValueError:  # when preferences is empty
+            continue
+        ranked.append((preference, arity, whl))
+    if top:
+        return min(ranked)
+    return sorted(ranked)
+
+
 def install(wheel_path):
     """Install a single wheel (.whl) file without regard for dependencies."""
     try:
 """Utility functions."""
 
+import re
+import sys
 import base64
 import json
+import sysconfig
+from distutils.util import get_platform
 
 __all__ = ['urlsafe_b64encode', 'urlsafe_b64decode', 'utf8', 
-           'to_json', 'from_json']
+           'to_json', 'from_json', 'parse_version', 'generate_supported',
+           'get_abbr_impl']
+
+component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE)
+replace = {'pre':'c', 'preview':'c','-':'final-','rc':'c','dev':'@'}.get
+
 
 def urlsafe_b64encode(data):
     """urlsafe_b64encode without padding"""
     return base64.urlsafe_b64encode(data).rstrip(b'=')
 
+
 def urlsafe_b64decode(data):
     """urlsafe_b64decode without padding"""
     pad = b'=' * (4 - (len(data) & 3))
     return base64.urlsafe_b64decode(data + pad)
 
+
 def to_json(o):
     return json.dumps(o, sort_keys=True)
 
+
 def from_json(j):
     return json.loads(j)
 
+
 try:
     unicode
     def utf8(data):
         if isinstance(data, str):
             return data.encode('utf-8')
         return data
-    
+
+
+def get_abbr_impl():
+    """Return abbreviated implementation name"""
+    if hasattr(sys, 'pypy_version_info'):
+        pyimpl = 'pp'
+    elif sys.platform.startswith('java'):
+        pyimpl = 'jy'
+    elif sys.platform == 'cli':
+        pyimpl = 'ip'
+    else:
+        pyimpl = 'cp'
+    return pyimpl
+
+
+def get_impl_ver():
+    impl_ver = sysconfig.get_config_var("py_version_nodot")
+    if not impl_ver:
+        impl_ver = ''.join(map(str, sys.version_info[:2]))
+    return impl_ver
+    
+# Next two functions from distribute's pkg_resources.py
+
+def _parse_version_parts(s):
+    for part in component_re.split(s):
+        part = replace(part,part)
+        if part in ['', '.']:
+            continue
+        if part[:1] in '0123456789':
+            yield part.zfill(8)    # pad for numeric comparison
+        else:
+            yield '*'+part
+
+    yield '*final'  # ensure that alpha/beta/candidate are before final
+
+
+def parse_version(s):
+    """Convert a version string to a chronologically-sortable key
+
+    This is a rough cross between distutils' StrictVersion and LooseVersion;
+    if you give it versions that would work with StrictVersion, then it behaves
+    the same; otherwise it acts like a slightly-smarter LooseVersion. It is
+    *possible* to create pathological version coding schemes that will fool
+    this parser, but they should be very rare in practice.
+
+    The returned value will be a tuple of strings.  Numeric portions of the
+    version are padded to 8 digits so they will compare numerically, but
+    without relying on how numbers compare relative to strings.  Dots are
+    dropped, but dashes are retained.  Trailing zeros between alpha segments
+    or dashes are suppressed, so that e.g. "2.4.0" is considered the same as
+    "2.4". Alphanumeric parts are lower-cased.
+
+    The algorithm assumes that strings like "-" and any alpha string that
+    alphabetically follows "final"  represents a "patch level".  So, "2.4-1"
+    is assumed to be a branch or patch of "2.4", and therefore "2.4.1" is
+    considered newer than "2.4-1", which in turn is newer than "2.4".
+
+    Strings like "a", "b", "c", "alpha", "beta", "candidate" and so on (that
+    come before "final" alphabetically) are assumed to be pre-release versions,
+    so that the version "2.4" is considered newer than "2.4a1".
+
+    Finally, to handle miscellaneous cases, the strings "pre", "preview", and
+    "rc" are treated as if they were "c", i.e. as though they were release
+    candidates, and therefore are not as new as a version string that does not
+    contain them, and "dev" is replaced with an '@' so that it sorts lower than
+    than any other pre-release tag.
+    """
+    parts = []
+    for part in _parse_version_parts(s.lower()):
+        if part.startswith('*'):
+            # remove trailing zeros from each series of numeric parts
+            while parts and parts[-1]=='00000000':
+                parts.pop()
+        parts.append(part)
+    return tuple(parts)
+
+
+def generate_supported(versions=None):
+    # XXX: Only a draft
+    supported = []
+    current_ver = get_impl_ver()
+    # Versions must be given with respect to the preference
+    if versions is None:
+        versions = [''.join(map(str, sys.version_info[:2]))]
+    impl = get_abbr_impl()
+    abis = ['noabi']  # XXX: Fix here
+    arch = get_platform().replace('.', '_').replace('-', '_')
+    for version in versions:
+        for abi in abis:
+            supported.append(('%s%s' % (impl, version), abi, arch))    
+        if not impl.startswith('py'):
+            # Add pure Python distributions if not already done so
+            supported.append(('py%s' % (version), 'noabi', 'noarch'))
+    return supported
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.