mirror of https://github.com/pypa/pip
Merge branch '1.5.X'
Conflicts: .travis/py34.sh CHANGES.txt PROJECT.txt
This commit is contained in:
commit
0e72f65bf4
|
@ -13,12 +13,6 @@ before_install:
|
|||
- "[ -d ~/.distlib ] && sudo chown -R travis:travis ~/.distlib || true"
|
||||
|
||||
install:
|
||||
- "if [[ $TOXENV == 'pypy' ]]; then sudo add-apt-repository -y ppa:pypy/ppa; fi"
|
||||
- "if [[ $TOXENV == 'pypy' ]]; then sudo apt-get -y update && sudo apt-get -y install pypy; fi"
|
||||
# This is required because we need to get rid of the Travis installed PyPy
|
||||
# or it'll take precedence over the PPA installed one.
|
||||
- "if [[ $TOXENV == 'pypy' ]]; then sudo rm -rf /usr/local/pypy/bin; fi"
|
||||
- "if [[ $TOXENV == 'py34' ]]; then .travis/py34.sh; fi"
|
||||
- sudo apt-get install subversion bzr mercurial
|
||||
- echo -e "[web]\ncacerts = /etc/ssl/certs/ca-certificates.crt" >> ~/.hgrc
|
||||
- git config --global user.email "python-virtualenv@googlegroups.com"
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
#!/bin/sh
|
||||
sudo add-apt-repository -y ppa:fkrull/deadsnakes
|
||||
sudo apt-get update
|
||||
sudo apt-get install python3.4
|
14
CHANGES.txt
14
CHANGES.txt
|
@ -1,3 +1,17 @@
|
|||
**1.5.5 (2014-05-03)**
|
||||
|
||||
* Fixes #1632. Uninstall issues on debianized pypy, specifically issues with
|
||||
setuptools upgrades. (PR #1743)
|
||||
|
||||
* Update documentation to point at https://bootstrap.pypa.io/get-pip.py for
|
||||
bootstrapping pip.
|
||||
|
||||
* Update docs to point to https://pip.pypa.io/
|
||||
|
||||
* Upgrade the bundled projects (distlib==0.1.8, html5lib==1.0b3, six==1.6.1,
|
||||
colorama==0.3.1, setuptools==3.4.4).
|
||||
|
||||
|
||||
**1.5.4 (2014-02-21)**
|
||||
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@ Project Info
|
|||
============
|
||||
|
||||
* Project Page: https://github.com/pypa/pip
|
||||
* Install howto: http://www.pip-installer.org/en/latest/installing.html
|
||||
* Changelog: http://www.pip-installer.org/en/latest/news.html
|
||||
* Install howto: https://pip.pypa.io/en/latest/installing.html
|
||||
* Changelog: https://pip.pypa.io/en/latest/news.html
|
||||
* Bug Tracking: https://github.com/pypa/pip/issues
|
||||
* Mailing list: http://groups.google.com/group/python-virtualenv
|
||||
* Docs: http://www.pip-installer.org/
|
||||
* Docs: https://pip.pypa.io/
|
||||
* User IRC: #pypa on Freenode.
|
||||
* Dev IRC: #pypa-dev on Freenode.
|
||||
|
|
|
@ -2,9 +2,9 @@ pip
|
|||
===
|
||||
|
||||
.. image:: https://pypip.in/v/pip/badge.png
|
||||
:target: https://crate.io/packages/pip
|
||||
:target: https://pypi.python.org/pypi/pip
|
||||
|
||||
.. image:: https://secure.travis-ci.org/pypa/pip.png?branch=develop
|
||||
:target: http://travis-ci.org/pypa/pip
|
||||
|
||||
For documentation, see http://www.pip-installer.org
|
||||
For documentation, see https://pip.pypa.io/
|
||||
|
|
|
@ -21,7 +21,7 @@ Install pip
|
|||
-----------
|
||||
|
||||
To install or upgrade pip, securely download `get-pip.py
|
||||
<https://raw.github.com/pypa/pip/master/contrib/get-pip.py>`_. [1]_
|
||||
<https://bootstrap.pypa.io/get-pip.py>`_. [1]_
|
||||
|
||||
Then run the following (which may require administrator access)::
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import pip.cmdoptions
|
|||
cmdoptions = pip.cmdoptions
|
||||
|
||||
# The version as used in the setup.py and the docs conf.py
|
||||
__version__ = "1.5.4"
|
||||
__version__ = "1.5.5"
|
||||
|
||||
|
||||
def autocomplete():
|
||||
|
|
|
@ -16,7 +16,7 @@ Modifications
|
|||
Markerlib and pkg_resources
|
||||
===========================
|
||||
|
||||
Markerlib and pkg_resources has been pulled in from setuptools 2.1
|
||||
Markerlib and pkg_resources has been pulled in from setuptools 3.4.4
|
||||
|
||||
|
||||
Note to Downstream Distributors
|
||||
|
|
|
@ -3,5 +3,5 @@ from .initialise import init, deinit, reinit
|
|||
from .ansi import Fore, Back, Style
|
||||
from .ansitowin32 import AnsiToWin32
|
||||
|
||||
VERSION = '0.2.7'
|
||||
__version__ = '0.3.1'
|
||||
|
||||
|
|
|
@ -1,50 +1,50 @@
|
|||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
'''
|
||||
This module generates ANSI character codes to printing colors to terminals.
|
||||
See: http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
'''
|
||||
|
||||
CSI = '\033['
|
||||
|
||||
def code_to_chars(code):
|
||||
return CSI + str(code) + 'm'
|
||||
|
||||
class AnsiCodes(object):
|
||||
def __init__(self, codes):
|
||||
for name in dir(codes):
|
||||
if not name.startswith('_'):
|
||||
value = getattr(codes, name)
|
||||
setattr(self, name, code_to_chars(value))
|
||||
|
||||
class AnsiFore:
|
||||
BLACK = 30
|
||||
RED = 31
|
||||
GREEN = 32
|
||||
YELLOW = 33
|
||||
BLUE = 34
|
||||
MAGENTA = 35
|
||||
CYAN = 36
|
||||
WHITE = 37
|
||||
RESET = 39
|
||||
|
||||
class AnsiBack:
|
||||
BLACK = 40
|
||||
RED = 41
|
||||
GREEN = 42
|
||||
YELLOW = 43
|
||||
BLUE = 44
|
||||
MAGENTA = 45
|
||||
CYAN = 46
|
||||
WHITE = 47
|
||||
RESET = 49
|
||||
|
||||
class AnsiStyle:
|
||||
BRIGHT = 1
|
||||
DIM = 2
|
||||
NORMAL = 22
|
||||
RESET_ALL = 0
|
||||
|
||||
Fore = AnsiCodes( AnsiFore )
|
||||
Back = AnsiCodes( AnsiBack )
|
||||
Style = AnsiCodes( AnsiStyle )
|
||||
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
'''
|
||||
This module generates ANSI character codes to printing colors to terminals.
|
||||
See: http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
'''
|
||||
|
||||
CSI = '\033['
|
||||
|
||||
def code_to_chars(code):
|
||||
return CSI + str(code) + 'm'
|
||||
|
||||
class AnsiCodes(object):
|
||||
def __init__(self, codes):
|
||||
for name in dir(codes):
|
||||
if not name.startswith('_'):
|
||||
value = getattr(codes, name)
|
||||
setattr(self, name, code_to_chars(value))
|
||||
|
||||
class AnsiFore:
|
||||
BLACK = 30
|
||||
RED = 31
|
||||
GREEN = 32
|
||||
YELLOW = 33
|
||||
BLUE = 34
|
||||
MAGENTA = 35
|
||||
CYAN = 36
|
||||
WHITE = 37
|
||||
RESET = 39
|
||||
|
||||
class AnsiBack:
|
||||
BLACK = 40
|
||||
RED = 41
|
||||
GREEN = 42
|
||||
YELLOW = 43
|
||||
BLUE = 44
|
||||
MAGENTA = 45
|
||||
CYAN = 46
|
||||
WHITE = 47
|
||||
RESET = 49
|
||||
|
||||
class AnsiStyle:
|
||||
BRIGHT = 1
|
||||
DIM = 2
|
||||
NORMAL = 22
|
||||
RESET_ALL = 0
|
||||
|
||||
Fore = AnsiCodes( AnsiFore )
|
||||
Back = AnsiCodes( AnsiBack )
|
||||
Style = AnsiCodes( AnsiStyle )
|
||||
|
||||
|
|
|
@ -1,189 +1,190 @@
|
|||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
import re
|
||||
import sys
|
||||
|
||||
from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style
|
||||
from .winterm import WinTerm, WinColor, WinStyle
|
||||
from .win32 import windll
|
||||
|
||||
|
||||
if windll is not None:
|
||||
winterm = WinTerm()
|
||||
|
||||
|
||||
def is_a_tty(stream):
|
||||
return hasattr(stream, 'isatty') and stream.isatty()
|
||||
|
||||
|
||||
class StreamWrapper(object):
|
||||
'''
|
||||
Wraps a stream (such as stdout), acting as a transparent proxy for all
|
||||
attribute access apart from method 'write()', which is delegated to our
|
||||
Converter instance.
|
||||
'''
|
||||
def __init__(self, wrapped, converter):
|
||||
# double-underscore everything to prevent clashes with names of
|
||||
# attributes on the wrapped stream object.
|
||||
self.__wrapped = wrapped
|
||||
self.__convertor = converter
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.__wrapped, name)
|
||||
|
||||
def write(self, text):
|
||||
self.__convertor.write(text)
|
||||
|
||||
|
||||
class AnsiToWin32(object):
|
||||
'''
|
||||
Implements a 'write()' method which, on Windows, will strip ANSI character
|
||||
sequences from the text, and if outputting to a tty, will convert them into
|
||||
win32 function calls.
|
||||
'''
|
||||
ANSI_RE = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
|
||||
|
||||
def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
|
||||
# The wrapped stream (normally sys.stdout or sys.stderr)
|
||||
self.wrapped = wrapped
|
||||
|
||||
# should we reset colors to defaults after every .write()
|
||||
self.autoreset = autoreset
|
||||
|
||||
# create the proxy wrapping our output stream
|
||||
self.stream = StreamWrapper(wrapped, self)
|
||||
|
||||
on_windows = sys.platform.startswith('win')
|
||||
|
||||
# should we strip ANSI sequences from our output?
|
||||
if strip is None:
|
||||
strip = on_windows
|
||||
self.strip = strip
|
||||
|
||||
# should we should convert ANSI sequences into win32 calls?
|
||||
if convert is None:
|
||||
convert = on_windows and is_a_tty(wrapped)
|
||||
self.convert = convert
|
||||
|
||||
# dict of ansi codes to win32 functions and parameters
|
||||
self.win32_calls = self.get_win32_calls()
|
||||
|
||||
# are we wrapping stderr?
|
||||
self.on_stderr = self.wrapped is sys.stderr
|
||||
|
||||
|
||||
def should_wrap(self):
|
||||
'''
|
||||
True if this class is actually needed. If false, then the output
|
||||
stream will not be affected, nor will win32 calls be issued, so
|
||||
wrapping stdout is not actually required. This will generally be
|
||||
False on non-Windows platforms, unless optional functionality like
|
||||
autoreset has been requested using kwargs to init()
|
||||
'''
|
||||
return self.convert or self.strip or self.autoreset
|
||||
|
||||
|
||||
def get_win32_calls(self):
|
||||
if self.convert and winterm:
|
||||
return {
|
||||
AnsiStyle.RESET_ALL: (winterm.reset_all, ),
|
||||
AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),
|
||||
AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),
|
||||
AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),
|
||||
AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),
|
||||
AnsiFore.RED: (winterm.fore, WinColor.RED),
|
||||
AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),
|
||||
AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),
|
||||
AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),
|
||||
AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),
|
||||
AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),
|
||||
AnsiFore.WHITE: (winterm.fore, WinColor.GREY),
|
||||
AnsiFore.RESET: (winterm.fore, ),
|
||||
AnsiBack.BLACK: (winterm.back, WinColor.BLACK),
|
||||
AnsiBack.RED: (winterm.back, WinColor.RED),
|
||||
AnsiBack.GREEN: (winterm.back, WinColor.GREEN),
|
||||
AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),
|
||||
AnsiBack.BLUE: (winterm.back, WinColor.BLUE),
|
||||
AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),
|
||||
AnsiBack.CYAN: (winterm.back, WinColor.CYAN),
|
||||
AnsiBack.WHITE: (winterm.back, WinColor.GREY),
|
||||
AnsiBack.RESET: (winterm.back, ),
|
||||
}
|
||||
|
||||
|
||||
def write(self, text):
|
||||
if self.strip or self.convert:
|
||||
self.write_and_convert(text)
|
||||
else:
|
||||
self.wrapped.write(text)
|
||||
self.wrapped.flush()
|
||||
if self.autoreset:
|
||||
self.reset_all()
|
||||
|
||||
|
||||
def reset_all(self):
|
||||
if self.convert:
|
||||
self.call_win32('m', (0,))
|
||||
elif is_a_tty(self.wrapped):
|
||||
self.wrapped.write(Style.RESET_ALL)
|
||||
|
||||
|
||||
def write_and_convert(self, text):
|
||||
'''
|
||||
Write the given text to our wrapped stream, stripping any ANSI
|
||||
sequences from the text, and optionally converting them into win32
|
||||
calls.
|
||||
'''
|
||||
cursor = 0
|
||||
for match in self.ANSI_RE.finditer(text):
|
||||
start, end = match.span()
|
||||
self.write_plain_text(text, cursor, start)
|
||||
self.convert_ansi(*match.groups())
|
||||
cursor = end
|
||||
self.write_plain_text(text, cursor, len(text))
|
||||
|
||||
|
||||
def write_plain_text(self, text, start, end):
|
||||
if start < end:
|
||||
self.wrapped.write(text[start:end])
|
||||
self.wrapped.flush()
|
||||
|
||||
|
||||
def convert_ansi(self, paramstring, command):
|
||||
if self.convert:
|
||||
params = self.extract_params(paramstring)
|
||||
self.call_win32(command, params)
|
||||
|
||||
|
||||
def extract_params(self, paramstring):
|
||||
def split(paramstring):
|
||||
for p in paramstring.split(';'):
|
||||
if p != '':
|
||||
yield int(p)
|
||||
return tuple(split(paramstring))
|
||||
|
||||
|
||||
def call_win32(self, command, params):
|
||||
if params == []:
|
||||
params = [0]
|
||||
if command == 'm':
|
||||
for param in params:
|
||||
if param in self.win32_calls:
|
||||
func_args = self.win32_calls[param]
|
||||
func = func_args[0]
|
||||
args = func_args[1:]
|
||||
kwargs = dict(on_stderr=self.on_stderr)
|
||||
func(*args, **kwargs)
|
||||
elif command in ('H', 'f'): # set cursor position
|
||||
func = winterm.set_cursor_position
|
||||
func(params, on_stderr=self.on_stderr)
|
||||
elif command in ('J'):
|
||||
func = winterm.erase_data
|
||||
func(params, on_stderr=self.on_stderr)
|
||||
elif command == 'A':
|
||||
if params == () or params == None:
|
||||
num_rows = 1
|
||||
else:
|
||||
num_rows = params[0]
|
||||
func = winterm.cursor_up
|
||||
func(num_rows, on_stderr=self.on_stderr)
|
||||
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
import re
|
||||
import sys
|
||||
|
||||
from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style
|
||||
from .winterm import WinTerm, WinColor, WinStyle
|
||||
from .win32 import windll
|
||||
|
||||
|
||||
winterm = None
|
||||
if windll is not None:
|
||||
winterm = WinTerm()
|
||||
|
||||
|
||||
def is_a_tty(stream):
|
||||
return hasattr(stream, 'isatty') and stream.isatty()
|
||||
|
||||
|
||||
class StreamWrapper(object):
|
||||
'''
|
||||
Wraps a stream (such as stdout), acting as a transparent proxy for all
|
||||
attribute access apart from method 'write()', which is delegated to our
|
||||
Converter instance.
|
||||
'''
|
||||
def __init__(self, wrapped, converter):
|
||||
# double-underscore everything to prevent clashes with names of
|
||||
# attributes on the wrapped stream object.
|
||||
self.__wrapped = wrapped
|
||||
self.__convertor = converter
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.__wrapped, name)
|
||||
|
||||
def write(self, text):
|
||||
self.__convertor.write(text)
|
||||
|
||||
|
||||
class AnsiToWin32(object):
|
||||
'''
|
||||
Implements a 'write()' method which, on Windows, will strip ANSI character
|
||||
sequences from the text, and if outputting to a tty, will convert them into
|
||||
win32 function calls.
|
||||
'''
|
||||
ANSI_RE = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
|
||||
|
||||
def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
|
||||
# The wrapped stream (normally sys.stdout or sys.stderr)
|
||||
self.wrapped = wrapped
|
||||
|
||||
# should we reset colors to defaults after every .write()
|
||||
self.autoreset = autoreset
|
||||
|
||||
# create the proxy wrapping our output stream
|
||||
self.stream = StreamWrapper(wrapped, self)
|
||||
|
||||
on_windows = sys.platform.startswith('win')
|
||||
|
||||
# should we strip ANSI sequences from our output?
|
||||
if strip is None:
|
||||
strip = on_windows
|
||||
self.strip = strip
|
||||
|
||||
# should we should convert ANSI sequences into win32 calls?
|
||||
if convert is None:
|
||||
convert = on_windows and is_a_tty(wrapped)
|
||||
self.convert = convert
|
||||
|
||||
# dict of ansi codes to win32 functions and parameters
|
||||
self.win32_calls = self.get_win32_calls()
|
||||
|
||||
# are we wrapping stderr?
|
||||
self.on_stderr = self.wrapped is sys.stderr
|
||||
|
||||
|
||||
def should_wrap(self):
|
||||
'''
|
||||
True if this class is actually needed. If false, then the output
|
||||
stream will not be affected, nor will win32 calls be issued, so
|
||||
wrapping stdout is not actually required. This will generally be
|
||||
False on non-Windows platforms, unless optional functionality like
|
||||
autoreset has been requested using kwargs to init()
|
||||
'''
|
||||
return self.convert or self.strip or self.autoreset
|
||||
|
||||
|
||||
def get_win32_calls(self):
|
||||
if self.convert and winterm:
|
||||
return {
|
||||
AnsiStyle.RESET_ALL: (winterm.reset_all, ),
|
||||
AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),
|
||||
AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),
|
||||
AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),
|
||||
AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),
|
||||
AnsiFore.RED: (winterm.fore, WinColor.RED),
|
||||
AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),
|
||||
AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),
|
||||
AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),
|
||||
AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),
|
||||
AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),
|
||||
AnsiFore.WHITE: (winterm.fore, WinColor.GREY),
|
||||
AnsiFore.RESET: (winterm.fore, ),
|
||||
AnsiBack.BLACK: (winterm.back, WinColor.BLACK),
|
||||
AnsiBack.RED: (winterm.back, WinColor.RED),
|
||||
AnsiBack.GREEN: (winterm.back, WinColor.GREEN),
|
||||
AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),
|
||||
AnsiBack.BLUE: (winterm.back, WinColor.BLUE),
|
||||
AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),
|
||||
AnsiBack.CYAN: (winterm.back, WinColor.CYAN),
|
||||
AnsiBack.WHITE: (winterm.back, WinColor.GREY),
|
||||
AnsiBack.RESET: (winterm.back, ),
|
||||
}
|
||||
|
||||
|
||||
def write(self, text):
|
||||
if self.strip or self.convert:
|
||||
self.write_and_convert(text)
|
||||
else:
|
||||
self.wrapped.write(text)
|
||||
self.wrapped.flush()
|
||||
if self.autoreset:
|
||||
self.reset_all()
|
||||
|
||||
|
||||
def reset_all(self):
|
||||
if self.convert:
|
||||
self.call_win32('m', (0,))
|
||||
elif not self.wrapped.closed and is_a_tty(self.wrapped):
|
||||
self.wrapped.write(Style.RESET_ALL)
|
||||
|
||||
|
||||
def write_and_convert(self, text):
|
||||
'''
|
||||
Write the given text to our wrapped stream, stripping any ANSI
|
||||
sequences from the text, and optionally converting them into win32
|
||||
calls.
|
||||
'''
|
||||
cursor = 0
|
||||
for match in self.ANSI_RE.finditer(text):
|
||||
start, end = match.span()
|
||||
self.write_plain_text(text, cursor, start)
|
||||
self.convert_ansi(*match.groups())
|
||||
cursor = end
|
||||
self.write_plain_text(text, cursor, len(text))
|
||||
|
||||
|
||||
def write_plain_text(self, text, start, end):
|
||||
if start < end:
|
||||
self.wrapped.write(text[start:end])
|
||||
self.wrapped.flush()
|
||||
|
||||
|
||||
def convert_ansi(self, paramstring, command):
|
||||
if self.convert:
|
||||
params = self.extract_params(paramstring)
|
||||
self.call_win32(command, params)
|
||||
|
||||
|
||||
def extract_params(self, paramstring):
|
||||
def split(paramstring):
|
||||
for p in paramstring.split(';'):
|
||||
if p != '':
|
||||
yield int(p)
|
||||
return tuple(split(paramstring))
|
||||
|
||||
|
||||
def call_win32(self, command, params):
|
||||
if params == []:
|
||||
params = [0]
|
||||
if command == 'm':
|
||||
for param in params:
|
||||
if param in self.win32_calls:
|
||||
func_args = self.win32_calls[param]
|
||||
func = func_args[0]
|
||||
args = func_args[1:]
|
||||
kwargs = dict(on_stderr=self.on_stderr)
|
||||
func(*args, **kwargs)
|
||||
elif command in ('H', 'f'): # set cursor position
|
||||
func = winterm.set_cursor_position
|
||||
func(params, on_stderr=self.on_stderr)
|
||||
elif command in ('J'):
|
||||
func = winterm.erase_data
|
||||
func(params, on_stderr=self.on_stderr)
|
||||
elif command == 'A':
|
||||
if params == () or params == None:
|
||||
num_rows = 1
|
||||
else:
|
||||
num_rows = params[0]
|
||||
func = winterm.cursor_up
|
||||
func(num_rows, on_stderr=self.on_stderr)
|
||||
|
||||
|
|
|
@ -1,56 +1,56 @@
|
|||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
import atexit
|
||||
import sys
|
||||
|
||||
from .ansitowin32 import AnsiToWin32
|
||||
|
||||
|
||||
orig_stdout = sys.stdout
|
||||
orig_stderr = sys.stderr
|
||||
|
||||
wrapped_stdout = sys.stdout
|
||||
wrapped_stderr = sys.stderr
|
||||
|
||||
atexit_done = False
|
||||
|
||||
|
||||
def reset_all():
|
||||
AnsiToWin32(orig_stdout).reset_all()
|
||||
|
||||
|
||||
def init(autoreset=False, convert=None, strip=None, wrap=True):
|
||||
|
||||
if not wrap and any([autoreset, convert, strip]):
|
||||
raise ValueError('wrap=False conflicts with any other arg=True')
|
||||
|
||||
global wrapped_stdout, wrapped_stderr
|
||||
sys.stdout = wrapped_stdout = \
|
||||
wrap_stream(orig_stdout, convert, strip, autoreset, wrap)
|
||||
sys.stderr = wrapped_stderr = \
|
||||
wrap_stream(orig_stderr, convert, strip, autoreset, wrap)
|
||||
|
||||
global atexit_done
|
||||
if not atexit_done:
|
||||
atexit.register(reset_all)
|
||||
atexit_done = True
|
||||
|
||||
|
||||
def deinit():
|
||||
sys.stdout = orig_stdout
|
||||
sys.stderr = orig_stderr
|
||||
|
||||
|
||||
def reinit():
|
||||
sys.stdout = wrapped_stdout
|
||||
sys.stderr = wrapped_stdout
|
||||
|
||||
|
||||
def wrap_stream(stream, convert, strip, autoreset, wrap):
|
||||
if wrap:
|
||||
wrapper = AnsiToWin32(stream,
|
||||
convert=convert, strip=strip, autoreset=autoreset)
|
||||
if wrapper.should_wrap():
|
||||
stream = wrapper.stream
|
||||
return stream
|
||||
|
||||
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
import atexit
|
||||
import sys
|
||||
|
||||
from .ansitowin32 import AnsiToWin32
|
||||
|
||||
|
||||
orig_stdout = sys.stdout
|
||||
orig_stderr = sys.stderr
|
||||
|
||||
wrapped_stdout = sys.stdout
|
||||
wrapped_stderr = sys.stderr
|
||||
|
||||
atexit_done = False
|
||||
|
||||
|
||||
def reset_all():
|
||||
AnsiToWin32(orig_stdout).reset_all()
|
||||
|
||||
|
||||
def init(autoreset=False, convert=None, strip=None, wrap=True):
|
||||
|
||||
if not wrap and any([autoreset, convert, strip]):
|
||||
raise ValueError('wrap=False conflicts with any other arg=True')
|
||||
|
||||
global wrapped_stdout, wrapped_stderr
|
||||
sys.stdout = wrapped_stdout = \
|
||||
wrap_stream(orig_stdout, convert, strip, autoreset, wrap)
|
||||
sys.stderr = wrapped_stderr = \
|
||||
wrap_stream(orig_stderr, convert, strip, autoreset, wrap)
|
||||
|
||||
global atexit_done
|
||||
if not atexit_done:
|
||||
atexit.register(reset_all)
|
||||
atexit_done = True
|
||||
|
||||
|
||||
def deinit():
|
||||
sys.stdout = orig_stdout
|
||||
sys.stderr = orig_stderr
|
||||
|
||||
|
||||
def reinit():
|
||||
sys.stdout = wrapped_stdout
|
||||
sys.stderr = wrapped_stdout
|
||||
|
||||
|
||||
def wrap_stream(stream, convert, strip, autoreset, wrap):
|
||||
if wrap:
|
||||
wrapper = AnsiToWin32(stream,
|
||||
convert=convert, strip=strip, autoreset=autoreset)
|
||||
if wrapper.should_wrap():
|
||||
stream = wrapper.stream
|
||||
return stream
|
||||
|
||||
|
||||
|
|
|
@ -4,10 +4,13 @@
|
|||
STDOUT = -11
|
||||
STDERR = -12
|
||||
|
||||
import ctypes
|
||||
from ctypes import LibraryLoader
|
||||
|
||||
try:
|
||||
from ctypes import windll
|
||||
windll = LibraryLoader(ctypes.WinDLL)
|
||||
from ctypes import wintypes
|
||||
except ImportError:
|
||||
except (AttributeError, ImportError):
|
||||
windll = None
|
||||
SetConsoleTextAttribute = lambda *_: None
|
||||
else:
|
||||
|
|
|
@ -1,120 +1,120 @@
|
|||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
from . import win32
|
||||
|
||||
|
||||
# from wincon.h
|
||||
class WinColor(object):
|
||||
BLACK = 0
|
||||
BLUE = 1
|
||||
GREEN = 2
|
||||
CYAN = 3
|
||||
RED = 4
|
||||
MAGENTA = 5
|
||||
YELLOW = 6
|
||||
GREY = 7
|
||||
|
||||
# from wincon.h
|
||||
class WinStyle(object):
|
||||
NORMAL = 0x00 # dim text, dim background
|
||||
BRIGHT = 0x08 # bright text, dim background
|
||||
|
||||
|
||||
class WinTerm(object):
|
||||
|
||||
def __init__(self):
|
||||
self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes
|
||||
self.set_attrs(self._default)
|
||||
self._default_fore = self._fore
|
||||
self._default_back = self._back
|
||||
self._default_style = self._style
|
||||
|
||||
def get_attrs(self):
|
||||
return self._fore + self._back * 16 + self._style
|
||||
|
||||
def set_attrs(self, value):
|
||||
self._fore = value & 7
|
||||
self._back = (value >> 4) & 7
|
||||
self._style = value & WinStyle.BRIGHT
|
||||
|
||||
def reset_all(self, on_stderr=None):
|
||||
self.set_attrs(self._default)
|
||||
self.set_console(attrs=self._default)
|
||||
|
||||
def fore(self, fore=None, on_stderr=False):
|
||||
if fore is None:
|
||||
fore = self._default_fore
|
||||
self._fore = fore
|
||||
self.set_console(on_stderr=on_stderr)
|
||||
|
||||
def back(self, back=None, on_stderr=False):
|
||||
if back is None:
|
||||
back = self._default_back
|
||||
self._back = back
|
||||
self.set_console(on_stderr=on_stderr)
|
||||
|
||||
def style(self, style=None, on_stderr=False):
|
||||
if style is None:
|
||||
style = self._default_style
|
||||
self._style = style
|
||||
self.set_console(on_stderr=on_stderr)
|
||||
|
||||
def set_console(self, attrs=None, on_stderr=False):
|
||||
if attrs is None:
|
||||
attrs = self.get_attrs()
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
win32.SetConsoleTextAttribute(handle, attrs)
|
||||
|
||||
def get_position(self, handle):
|
||||
position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition
|
||||
# Because Windows coordinates are 0-based,
|
||||
# and win32.SetConsoleCursorPosition expects 1-based.
|
||||
position.X += 1
|
||||
position.Y += 1
|
||||
return position
|
||||
|
||||
def set_cursor_position(self, position=None, on_stderr=False):
|
||||
if position is None:
|
||||
#I'm not currently tracking the position, so there is no default.
|
||||
#position = self.get_position()
|
||||
return
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
win32.SetConsoleCursorPosition(handle, position)
|
||||
|
||||
def cursor_up(self, num_rows=0, on_stderr=False):
|
||||
if num_rows == 0:
|
||||
return
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
position = self.get_position(handle)
|
||||
adjusted_position = (position.Y - num_rows, position.X)
|
||||
self.set_cursor_position(adjusted_position, on_stderr)
|
||||
|
||||
def erase_data(self, mode=0, on_stderr=False):
|
||||
# 0 (or None) should clear from the cursor to the end of the screen.
|
||||
# 1 should clear from the cursor to the beginning of the screen.
|
||||
# 2 should clear the entire screen. (And maybe move cursor to (1,1)?)
|
||||
#
|
||||
# At the moment, I only support mode 2. From looking at the API, it
|
||||
# should be possible to calculate a different number of bytes to clear,
|
||||
# and to do so relative to the cursor position.
|
||||
if mode[0] not in (2,):
|
||||
return
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
# here's where we'll home the cursor
|
||||
coord_screen = win32.COORD(0,0)
|
||||
csbi = win32.GetConsoleScreenBufferInfo(handle)
|
||||
# get the number of character cells in the current buffer
|
||||
dw_con_size = csbi.dwSize.X * csbi.dwSize.Y
|
||||
# fill the entire screen with blanks
|
||||
win32.FillConsoleOutputCharacter(handle, ' ', dw_con_size, coord_screen)
|
||||
# now set the buffer's attributes accordingly
|
||||
win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen );
|
||||
# put the cursor at (0, 0)
|
||||
win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y))
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
from . import win32
|
||||
|
||||
|
||||
# from wincon.h
|
||||
class WinColor(object):
|
||||
BLACK = 0
|
||||
BLUE = 1
|
||||
GREEN = 2
|
||||
CYAN = 3
|
||||
RED = 4
|
||||
MAGENTA = 5
|
||||
YELLOW = 6
|
||||
GREY = 7
|
||||
|
||||
# from wincon.h
|
||||
class WinStyle(object):
|
||||
NORMAL = 0x00 # dim text, dim background
|
||||
BRIGHT = 0x08 # bright text, dim background
|
||||
|
||||
|
||||
class WinTerm(object):
|
||||
|
||||
def __init__(self):
|
||||
self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes
|
||||
self.set_attrs(self._default)
|
||||
self._default_fore = self._fore
|
||||
self._default_back = self._back
|
||||
self._default_style = self._style
|
||||
|
||||
def get_attrs(self):
|
||||
return self._fore + self._back * 16 + self._style
|
||||
|
||||
def set_attrs(self, value):
|
||||
self._fore = value & 7
|
||||
self._back = (value >> 4) & 7
|
||||
self._style = value & WinStyle.BRIGHT
|
||||
|
||||
def reset_all(self, on_stderr=None):
|
||||
self.set_attrs(self._default)
|
||||
self.set_console(attrs=self._default)
|
||||
|
||||
def fore(self, fore=None, on_stderr=False):
|
||||
if fore is None:
|
||||
fore = self._default_fore
|
||||
self._fore = fore
|
||||
self.set_console(on_stderr=on_stderr)
|
||||
|
||||
def back(self, back=None, on_stderr=False):
|
||||
if back is None:
|
||||
back = self._default_back
|
||||
self._back = back
|
||||
self.set_console(on_stderr=on_stderr)
|
||||
|
||||
def style(self, style=None, on_stderr=False):
|
||||
if style is None:
|
||||
style = self._default_style
|
||||
self._style = style
|
||||
self.set_console(on_stderr=on_stderr)
|
||||
|
||||
def set_console(self, attrs=None, on_stderr=False):
|
||||
if attrs is None:
|
||||
attrs = self.get_attrs()
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
win32.SetConsoleTextAttribute(handle, attrs)
|
||||
|
||||
def get_position(self, handle):
|
||||
position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition
|
||||
# Because Windows coordinates are 0-based,
|
||||
# and win32.SetConsoleCursorPosition expects 1-based.
|
||||
position.X += 1
|
||||
position.Y += 1
|
||||
return position
|
||||
|
||||
def set_cursor_position(self, position=None, on_stderr=False):
|
||||
if position is None:
|
||||
#I'm not currently tracking the position, so there is no default.
|
||||
#position = self.get_position()
|
||||
return
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
win32.SetConsoleCursorPosition(handle, position)
|
||||
|
||||
def cursor_up(self, num_rows=0, on_stderr=False):
|
||||
if num_rows == 0:
|
||||
return
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
position = self.get_position(handle)
|
||||
adjusted_position = (position.Y - num_rows, position.X)
|
||||
self.set_cursor_position(adjusted_position, on_stderr)
|
||||
|
||||
def erase_data(self, mode=0, on_stderr=False):
|
||||
# 0 (or None) should clear from the cursor to the end of the screen.
|
||||
# 1 should clear from the cursor to the beginning of the screen.
|
||||
# 2 should clear the entire screen. (And maybe move cursor to (1,1)?)
|
||||
#
|
||||
# At the moment, I only support mode 2. From looking at the API, it
|
||||
# should be possible to calculate a different number of bytes to clear,
|
||||
# and to do so relative to the cursor position.
|
||||
if mode[0] not in (2,):
|
||||
return
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
# here's where we'll home the cursor
|
||||
coord_screen = win32.COORD(0,0)
|
||||
csbi = win32.GetConsoleScreenBufferInfo(handle)
|
||||
# get the number of character cells in the current buffer
|
||||
dw_con_size = csbi.dwSize.X * csbi.dwSize.Y
|
||||
# fill the entire screen with blanks
|
||||
win32.FillConsoleOutputCharacter(handle, ' ', dw_con_size, coord_screen)
|
||||
# now set the buffer's attributes accordingly
|
||||
win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen );
|
||||
# put the cursor at (0, 0)
|
||||
win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y))
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2012-2013 Vinay Sajip.
|
||||
# Copyright (C) 2012-2014 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
import logging
|
||||
|
||||
__version__ = '0.1.7'
|
||||
__version__ = '0.1.8'
|
||||
|
||||
class DistlibException(Exception):
|
||||
pass
|
||||
|
@ -17,6 +17,7 @@ except ImportError: # pragma: no cover
|
|||
class NullHandler(logging.Handler):
|
||||
def handle(self, record): pass
|
||||
def emit(self, record): pass
|
||||
def createLock(self): self.lock = None
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.addHandler(NullHandler())
|
||||
|
|
|
@ -68,7 +68,7 @@ _cfg_read = False
|
|||
def _ensure_cfg_read():
|
||||
global _cfg_read
|
||||
if not _cfg_read:
|
||||
from distlib.resources import finder
|
||||
from ..resources import finder
|
||||
backport_package = __name__.rsplit('.', 1)[0]
|
||||
_finder = finder(backport_package)
|
||||
_cfgfile = _finder.find('sysconfig.cfg')
|
||||
|
|
|
@ -15,10 +15,10 @@ try:
|
|||
except ImportError:
|
||||
from dummy_threading import Thread
|
||||
|
||||
from distlib import DistlibException
|
||||
from distlib.compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr,
|
||||
urlparse, build_opener)
|
||||
from distlib.util import cached_property, zip_dir
|
||||
from . import DistlibException
|
||||
from .compat import (HTTPBasicAuthHandler, Request, HTTPPasswordMgr,
|
||||
urlparse, build_opener, string_types)
|
||||
from .util import cached_property, zip_dir, ServerProxy
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -49,6 +49,7 @@ class PackageIndex(object):
|
|||
self.ssl_verifier = None
|
||||
self.gpg = None
|
||||
self.gpg_home = None
|
||||
self.rpc_proxy = None
|
||||
with open(os.devnull, 'w') as sink:
|
||||
for s in ('gpg2', 'gpg'):
|
||||
try:
|
||||
|
@ -478,3 +479,10 @@ class PackageIndex(object):
|
|||
'Content-length': str(len(body))
|
||||
}
|
||||
return Request(self.url, body, headers)
|
||||
|
||||
def search(self, terms, operator=None):
|
||||
if isinstance(terms, string_types):
|
||||
terms = {'name': terms}
|
||||
if self.rpc_proxy is None:
|
||||
self.rpc_proxy = ServerProxy(self.url, timeout=3.0)
|
||||
return self.rpc_proxy.search(terms, operator or 'and')
|
||||
|
|
|
@ -534,6 +534,11 @@ class SimpleScrapingLocator(Locator):
|
|||
self.skip_externals = False
|
||||
self.num_workers = num_workers
|
||||
self._lock = threading.RLock()
|
||||
# See issue #45: we need to be resilient when the locator is used
|
||||
# in a thread, e.g. with concurrent.futures. We can't use self._lock
|
||||
# as it is for coordinating our internal threads - the ones created
|
||||
# in _prepare_threads.
|
||||
self._gplock = threading.RLock()
|
||||
|
||||
def _prepare_threads(self):
|
||||
"""
|
||||
|
@ -562,19 +567,21 @@ class SimpleScrapingLocator(Locator):
|
|||
self._threads = []
|
||||
|
||||
def _get_project(self, name):
|
||||
self.result = result = {}
|
||||
self.project_name = name
|
||||
url = urljoin(self.base_url, '%s/' % quote(name))
|
||||
self._seen.clear()
|
||||
self._page_cache.clear()
|
||||
self._prepare_threads()
|
||||
try:
|
||||
logger.debug('Queueing %s', url)
|
||||
self._to_fetch.put(url)
|
||||
self._to_fetch.join()
|
||||
finally:
|
||||
self._wait_threads()
|
||||
del self.result
|
||||
result = {}
|
||||
with self._gplock:
|
||||
self.result = result
|
||||
self.project_name = name
|
||||
url = urljoin(self.base_url, '%s/' % quote(name))
|
||||
self._seen.clear()
|
||||
self._page_cache.clear()
|
||||
self._prepare_threads()
|
||||
try:
|
||||
logger.debug('Queueing %s', url)
|
||||
self._to_fetch.put(url)
|
||||
self._to_fetch.join()
|
||||
finally:
|
||||
self._wait_threads()
|
||||
del self.result
|
||||
return result
|
||||
|
||||
platform_dependent = re.compile(r'\b(linux-(i\d86|x86_64|arm\w+)|'
|
||||
|
|
|
@ -1006,7 +1006,18 @@ class Metadata(object):
|
|||
if self._legacy:
|
||||
self._legacy.add_requirements(requirements)
|
||||
else:
|
||||
self._data.setdefault('run_requires', []).extend(requirements)
|
||||
run_requires = self._data.setdefault('run_requires', [])
|
||||
always = None
|
||||
for entry in run_requires:
|
||||
if 'environment' not in entry and 'extra' not in entry:
|
||||
always = entry
|
||||
break
|
||||
if always is None:
|
||||
always = { 'requires': requirements }
|
||||
run_requires.insert(0, always)
|
||||
else:
|
||||
rset = set(always['requires']) | set(requirements)
|
||||
always['requires'] = sorted(rset)
|
||||
|
||||
def __repr__(self):
|
||||
name = self.name or '(no name)'
|
||||
|
|
|
@ -17,39 +17,20 @@ import types
|
|||
import zipimport
|
||||
|
||||
from . import DistlibException
|
||||
from .util import cached_property, get_cache_base, path_to_cache_dir
|
||||
from .util import cached_property, get_cache_base, path_to_cache_dir, Cache
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Cache(object):
|
||||
"""
|
||||
A class implementing a cache for resources that need to live in the file system
|
||||
e.g. shared libraries.
|
||||
"""
|
||||
cache = None # created when needed
|
||||
|
||||
|
||||
class ResourceCache(Cache):
|
||||
def __init__(self, base=None):
|
||||
"""
|
||||
Initialise an instance.
|
||||
|
||||
:param base: The base directory where the cache should be located. If
|
||||
not specified, this will be the ``resource-cache``
|
||||
directory under whatever :func:`get_cache_base` returns.
|
||||
"""
|
||||
if base is None:
|
||||
# Use native string to avoid issues on 2.x: see Python #20140.
|
||||
base = os.path.join(get_cache_base(), str('resource-cache'))
|
||||
# we use 'isdir' instead of 'exists', because we want to
|
||||
# fail if there's a file with that name
|
||||
if not os.path.isdir(base):
|
||||
os.makedirs(base)
|
||||
self.base = os.path.abspath(os.path.normpath(base))
|
||||
|
||||
def prefix_to_dir(self, prefix):
|
||||
"""
|
||||
Converts a resource prefix to a directory name in the cache.
|
||||
"""
|
||||
return path_to_cache_dir(prefix)
|
||||
super(ResourceCache, self).__init__(base)
|
||||
|
||||
def is_stale(self, resource, path):
|
||||
"""
|
||||
|
@ -87,24 +68,6 @@ class Cache(object):
|
|||
f.write(resource.bytes)
|
||||
return result
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Clear the cache.
|
||||
"""
|
||||
not_removed = []
|
||||
for fn in os.listdir(self.base):
|
||||
fn = os.path.join(self.base, fn)
|
||||
try:
|
||||
if os.path.islink(fn) or os.path.isfile(fn):
|
||||
os.remove(fn)
|
||||
elif os.path.isdir(fn):
|
||||
shutil.rmtree(fn)
|
||||
except Exception:
|
||||
not_removed.append(fn)
|
||||
return not_removed
|
||||
|
||||
cache = Cache()
|
||||
|
||||
|
||||
class ResourceBase(object):
|
||||
def __init__(self, finder, name):
|
||||
|
@ -131,6 +94,9 @@ class Resource(ResourceBase):
|
|||
|
||||
@cached_property
|
||||
def file_path(self):
|
||||
global cache
|
||||
if cache is None:
|
||||
cache = ResourceCache()
|
||||
return cache.get(self)
|
||||
|
||||
@cached_property
|
||||
|
|
|
@ -92,8 +92,10 @@ class ScriptMaker(object):
|
|||
return executable
|
||||
|
||||
def _get_shebang(self, encoding, post_interp=b'', options=None):
|
||||
enquote = True
|
||||
if self.executable:
|
||||
executable = self.executable
|
||||
enquote = False # assume this will be taken care of
|
||||
elif not sysconfig.is_python_build():
|
||||
executable = get_executable()
|
||||
elif in_venv():
|
||||
|
@ -107,6 +109,10 @@ class ScriptMaker(object):
|
|||
if options:
|
||||
executable = self._get_alternate_executable(executable, options)
|
||||
|
||||
# If the user didn't specify an executable, it may be necessary to
|
||||
# cater for executable paths with spaces (not uncommon on Windows)
|
||||
if enquote and ' ' in executable:
|
||||
executable = '"%s"' % executable
|
||||
executable = fsencode(executable)
|
||||
shebang = b'#!' + executable + post_interp + b'\n'
|
||||
# Python parser starts to read a script using UTF-8 until
|
||||
|
|
|
@ -154,9 +154,9 @@ def in_venv():
|
|||
|
||||
|
||||
def get_executable():
|
||||
if sys.platform == 'darwin' and ('__VENV_LAUNCHER__'
|
||||
if sys.platform == 'darwin' and ('__PYVENV_LAUNCHER__'
|
||||
in os.environ):
|
||||
result = os.environ['__VENV_LAUNCHER__']
|
||||
result = os.environ['__PYVENV_LAUNCHER__']
|
||||
else:
|
||||
result = sys.executable
|
||||
return result
|
||||
|
@ -595,7 +595,6 @@ def get_cache_base(suffix=None):
|
|||
else:
|
||||
# Assume posix, or old Windows
|
||||
result = os.path.expanduser('~')
|
||||
result = os.path.join(result, suffix)
|
||||
# we use 'isdir' instead of 'exists', because we want to
|
||||
# fail if there's a file with that name
|
||||
if os.path.isdir(result):
|
||||
|
@ -612,7 +611,7 @@ def get_cache_base(suffix=None):
|
|||
if not usable:
|
||||
result = tempfile.mkdtemp()
|
||||
logger.warning('Default location unusable, using %s', result)
|
||||
return result
|
||||
return os.path.join(result, suffix)
|
||||
|
||||
|
||||
def path_to_cache_dir(path):
|
||||
|
@ -768,6 +767,50 @@ def get_package_data(name, version):
|
|||
return _get_external_data(url)
|
||||
|
||||
|
||||
class Cache(object):
|
||||
"""
|
||||
A class implementing a cache for resources that need to live in the file system
|
||||
e.g. shared libraries. This class was moved from resources to here because it
|
||||
could be used by other modules, e.g. the wheel module.
|
||||
"""
|
||||
|
||||
def __init__(self, base):
|
||||
"""
|
||||
Initialise an instance.
|
||||
|
||||
:param base: The base directory where the cache should be located.
|
||||
"""
|
||||
# we use 'isdir' instead of 'exists', because we want to
|
||||
# fail if there's a file with that name
|
||||
if not os.path.isdir(base):
|
||||
os.makedirs(base)
|
||||
if (os.stat(base).st_mode & 0o77) != 0:
|
||||
logger.warning('Directory \'%s\' is not private', base)
|
||||
self.base = os.path.abspath(os.path.normpath(base))
|
||||
|
||||
def prefix_to_dir(self, prefix):
|
||||
"""
|
||||
Converts a resource prefix to a directory name in the cache.
|
||||
"""
|
||||
return path_to_cache_dir(prefix)
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Clear the cache.
|
||||
"""
|
||||
not_removed = []
|
||||
for fn in os.listdir(self.base):
|
||||
fn = os.path.join(self.base, fn)
|
||||
try:
|
||||
if os.path.islink(fn) or os.path.isfile(fn):
|
||||
os.remove(fn)
|
||||
elif os.path.isdir(fn):
|
||||
shutil.rmtree(fn)
|
||||
except Exception:
|
||||
not_removed.append(fn)
|
||||
return not_removed
|
||||
|
||||
|
||||
class EventMixin(object):
|
||||
"""
|
||||
A very simple publish/subscribe system.
|
||||
|
|
|
@ -181,7 +181,7 @@ class Matcher(object):
|
|||
return self._string
|
||||
|
||||
|
||||
PEP426_VERSION_RE = re.compile(r'^(\d+\.\d+(\.\d+)*)((a|b|c|rc)(\d+))?'
|
||||
PEP426_VERSION_RE = re.compile(r'^(\d+(\.\d+)*)((a|b|c|rc)(\d+))?'
|
||||
r'(\.(post)(\d+))?(\.(dev)(\d+))?'
|
||||
r'(-(\d+(\.\d+)?))?$')
|
||||
|
||||
|
@ -297,7 +297,22 @@ class NormalizedMatcher(Matcher):
|
|||
'!=': '_match_ne',
|
||||
}
|
||||
|
||||
def _adjust_local(self, version, constraint, prefix):
|
||||
if prefix:
|
||||
strip_local = '-' not in constraint and version._parts[-1]
|
||||
else:
|
||||
# both constraint and version are
|
||||
# NormalizedVersion instances.
|
||||
# If constraint does not have a local component,
|
||||
# ensure the version doesn't, either.
|
||||
strip_local = not constraint._parts[-1] and version._parts[-1]
|
||||
if strip_local:
|
||||
s = version._string.split('-', 1)[0]
|
||||
version = self.version_class(s)
|
||||
return version, constraint
|
||||
|
||||
def _match_lt(self, version, constraint, prefix):
|
||||
version, constraint = self._adjust_local(version, constraint, prefix)
|
||||
if version >= constraint:
|
||||
return False
|
||||
release_clause = constraint._release_clause
|
||||
|
@ -305,6 +320,7 @@ class NormalizedMatcher(Matcher):
|
|||
return not _match_prefix(version, pfx)
|
||||
|
||||
def _match_gt(self, version, constraint, prefix):
|
||||
version, constraint = self._adjust_local(version, constraint, prefix)
|
||||
if version <= constraint:
|
||||
return False
|
||||
release_clause = constraint._release_clause
|
||||
|
@ -312,12 +328,15 @@ class NormalizedMatcher(Matcher):
|
|||
return not _match_prefix(version, pfx)
|
||||
|
||||
def _match_le(self, version, constraint, prefix):
|
||||
version, constraint = self._adjust_local(version, constraint, prefix)
|
||||
return version <= constraint
|
||||
|
||||
def _match_ge(self, version, constraint, prefix):
|
||||
version, constraint = self._adjust_local(version, constraint, prefix)
|
||||
return version >= constraint
|
||||
|
||||
def _match_eq(self, version, constraint, prefix):
|
||||
version, constraint = self._adjust_local(version, constraint, prefix)
|
||||
if not prefix:
|
||||
result = (version == constraint)
|
||||
else:
|
||||
|
@ -325,6 +344,7 @@ class NormalizedMatcher(Matcher):
|
|||
return result
|
||||
|
||||
def _match_ne(self, version, constraint, prefix):
|
||||
version, constraint = self._adjust_local(version, constraint, prefix)
|
||||
if not prefix:
|
||||
result = (version != constraint)
|
||||
else:
|
||||
|
@ -332,6 +352,7 @@ class NormalizedMatcher(Matcher):
|
|||
return result
|
||||
|
||||
def _match_compatible(self, version, constraint, prefix):
|
||||
version, constraint = self._adjust_local(version, constraint, prefix)
|
||||
if version == constraint:
|
||||
return True
|
||||
if version < constraint:
|
||||
|
@ -569,13 +590,15 @@ class LegacyVersion(Version):
|
|||
def parse(self, s):
|
||||
return _legacy_key(s)
|
||||
|
||||
PREREL_TAGS = set(
|
||||
['*a', '*alpha', '*b', '*beta', '*c', '*rc', '*r', '*@', '*pre']
|
||||
)
|
||||
|
||||
@property
|
||||
def is_prerelease(self):
|
||||
return any(x in self.PREREL_TAGS for x in self._parts)
|
||||
result = False
|
||||
for x in self._parts:
|
||||
if (isinstance(x, string_types) and x.startswith('*') and
|
||||
x < '*final'):
|
||||
result = True
|
||||
break
|
||||
return result
|
||||
|
||||
|
||||
class LegacyMatcher(Matcher):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2013 Vinay Sajip.
|
||||
# Copyright (C) 2013-2014 Vinay Sajip.
|
||||
# Licensed to the Python Software Foundation under a contributor agreement.
|
||||
# See LICENSE.txt and CONTRIBUTORS.txt.
|
||||
#
|
||||
|
@ -27,12 +27,13 @@ from . import __version__, DistlibException
|
|||
from .compat import sysconfig, ZipFile, fsdecode, text_type, filter
|
||||
from .database import InstalledDistribution
|
||||
from .metadata import Metadata, METADATA_FILENAME
|
||||
from .util import (FileOperator, convert_path, CSVReader, CSVWriter,
|
||||
cached_property, get_cache_base, read_exports)
|
||||
|
||||
from .util import (FileOperator, convert_path, CSVReader, CSVWriter, Cache,
|
||||
cached_property, get_cache_base, read_exports, tempdir)
|
||||
from .version import NormalizedVersion, UnsupportedVersionError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
cache = None # created when needed
|
||||
|
||||
if hasattr(sys, 'pypy_version_info'):
|
||||
IMP_PREFIX = 'pp'
|
||||
|
@ -55,7 +56,17 @@ ABI = sysconfig.get_config_var('SOABI')
|
|||
if ABI and ABI.startswith('cpython-'):
|
||||
ABI = ABI.replace('cpython-', 'cp')
|
||||
else:
|
||||
ABI = 'none'
|
||||
def _derive_abi():
|
||||
parts = ['cp', VER_SUFFIX]
|
||||
if sysconfig.get_config_var('Py_DEBUG'):
|
||||
parts.append('d')
|
||||
if sysconfig.get_config_var('WITH_PYMALLOC'):
|
||||
parts.append('m')
|
||||
if sysconfig.get_config_var('Py_UNICODE_SIZE') == 4:
|
||||
parts.append('u')
|
||||
return ''.join(parts)
|
||||
ABI = _derive_abi()
|
||||
del _derive_abi
|
||||
|
||||
FILENAME_RE = re.compile(r'''
|
||||
(?P<nm>[^-]+)
|
||||
|
@ -132,7 +143,7 @@ class Wheel(object):
|
|||
Initialise an instance using a (valid) filename.
|
||||
"""
|
||||
self.sign = sign
|
||||
self.verify = verify
|
||||
self.should_verify = verify
|
||||
self.buildver = ''
|
||||
self.pyver = [PYVER]
|
||||
self.abi = ['none']
|
||||
|
@ -147,7 +158,8 @@ class Wheel(object):
|
|||
if m:
|
||||
info = m.groupdict('')
|
||||
self.name = info['nm']
|
||||
self.version = info['vn']
|
||||
# Reinstate the local version separator
|
||||
self.version = info['vn'].replace('_', '-')
|
||||
self.buildver = info['bn']
|
||||
self._filename = self.filename
|
||||
else:
|
||||
|
@ -179,9 +191,16 @@ class Wheel(object):
|
|||
pyver = '.'.join(self.pyver)
|
||||
abi = '.'.join(self.abi)
|
||||
arch = '.'.join(self.arch)
|
||||
return '%s-%s%s-%s-%s-%s.whl' % (self.name, self.version, buildver,
|
||||
# replace - with _ as a local version separator
|
||||
version = self.version.replace('-', '_')
|
||||
return '%s-%s%s-%s-%s-%s.whl' % (self.name, version, buildver,
|
||||
pyver, abi, arch)
|
||||
|
||||
@property
|
||||
def exists(self):
|
||||
path = os.path.join(self.dirname, self.filename)
|
||||
return os.path.isfile(path)
|
||||
|
||||
@property
|
||||
def tags(self):
|
||||
for pyver in self.pyver:
|
||||
|
@ -195,29 +214,38 @@ class Wheel(object):
|
|||
name_ver = '%s-%s' % (self.name, self.version)
|
||||
info_dir = '%s.dist-info' % name_ver
|
||||
wrapper = codecs.getreader('utf-8')
|
||||
metadata_filename = posixpath.join(info_dir, METADATA_FILENAME)
|
||||
with ZipFile(pathname, 'r') as zf:
|
||||
wheel_metadata = self.get_wheel_metadata(zf)
|
||||
wv = wheel_metadata['Wheel-Version'].split('.', 1)
|
||||
file_version = tuple([int(i) for i in wv])
|
||||
if file_version < (1, 1):
|
||||
fn = 'METADATA'
|
||||
else:
|
||||
fn = METADATA_FILENAME
|
||||
try:
|
||||
metadata_filename = posixpath.join(info_dir, fn)
|
||||
with zf.open(metadata_filename) as bf:
|
||||
wf = wrapper(bf)
|
||||
result = Metadata(fileobj=wf)
|
||||
except KeyError:
|
||||
raise ValueError('Invalid wheel, because %s is '
|
||||
'missing' % METADATA_FILENAME)
|
||||
'missing' % fn)
|
||||
return result
|
||||
|
||||
def get_wheel_metadata(self, zf):
|
||||
name_ver = '%s-%s' % (self.name, self.version)
|
||||
info_dir = '%s.dist-info' % name_ver
|
||||
metadata_filename = posixpath.join(info_dir, 'WHEEL')
|
||||
with zf.open(metadata_filename) as bf:
|
||||
wf = codecs.getreader('utf-8')(bf)
|
||||
message = message_from_file(wf)
|
||||
return dict(message)
|
||||
|
||||
@cached_property
|
||||
def info(self):
|
||||
pathname = os.path.join(self.dirname, self.filename)
|
||||
name_ver = '%s-%s' % (self.name, self.version)
|
||||
info_dir = '%s.dist-info' % name_ver
|
||||
metadata_filename = posixpath.join(info_dir, 'WHEEL')
|
||||
wrapper = codecs.getreader('utf-8')
|
||||
with ZipFile(pathname, 'r') as zf:
|
||||
with zf.open(metadata_filename) as bf:
|
||||
wf = wrapper(bf)
|
||||
message = message_from_file(wf)
|
||||
result = dict(message)
|
||||
result = self.get_wheel_metadata(zf)
|
||||
return result
|
||||
|
||||
def process_shebang(self, data):
|
||||
|
@ -255,6 +283,28 @@ class Wheel(object):
|
|||
p = to_posix(os.path.relpath(record_path, base))
|
||||
writer.writerow((p, '', ''))
|
||||
|
||||
def write_records(self, info, libdir, archive_paths):
|
||||
records = []
|
||||
distinfo, info_dir = info
|
||||
hasher = getattr(hashlib, self.hash_kind)
|
||||
for ap, p in archive_paths:
|
||||
with open(p, 'rb') as f:
|
||||
data = f.read()
|
||||
digest = '%s=%s' % self.get_hash(data)
|
||||
size = os.path.getsize(p)
|
||||
records.append((ap, digest, size))
|
||||
|
||||
p = os.path.join(distinfo, 'RECORD')
|
||||
self.write_record(records, p, libdir)
|
||||
ap = to_posix(os.path.join(info_dir, 'RECORD'))
|
||||
archive_paths.append((ap, p))
|
||||
|
||||
def build_zip(self, pathname, archive_paths):
|
||||
with ZipFile(pathname, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
for ap, p in archive_paths:
|
||||
logger.debug('Wrote %s to %s in wheel', p, ap)
|
||||
zf.write(p, ap)
|
||||
|
||||
def build(self, paths, tags=None, wheel_version=None):
|
||||
"""
|
||||
Build a wheel from files in specified paths, and use any specified tags
|
||||
|
@ -353,25 +403,10 @@ class Wheel(object):
|
|||
|
||||
# Now, at last, RECORD.
|
||||
# Paths in here are archive paths - nothing else makes sense.
|
||||
records = []
|
||||
hasher = getattr(hashlib, self.hash_kind)
|
||||
for ap, p in archive_paths:
|
||||
with open(p, 'rb') as f:
|
||||
data = f.read()
|
||||
digest = '%s=%s' % self.get_hash(data)
|
||||
size = os.path.getsize(p)
|
||||
records.append((ap, digest, size))
|
||||
|
||||
p = os.path.join(distinfo, 'RECORD')
|
||||
self.write_record(records, p, libdir)
|
||||
ap = to_posix(os.path.join(info_dir, 'RECORD'))
|
||||
archive_paths.append((ap, p))
|
||||
self.write_records((distinfo, info_dir), libdir, archive_paths)
|
||||
# Now, ready to build the zip file
|
||||
pathname = os.path.join(self.dirname, self.filename)
|
||||
with ZipFile(pathname, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
for ap, p in archive_paths:
|
||||
logger.debug('Wrote %s to %s in wheel', p, ap)
|
||||
zf.write(p, ap)
|
||||
self.build_zip(pathname, archive_paths)
|
||||
return pathname
|
||||
|
||||
def install(self, paths, maker, **kwargs):
|
||||
|
@ -601,11 +636,13 @@ class Wheel(object):
|
|||
shutil.rmtree(workdir)
|
||||
|
||||
def _get_dylib_cache(self):
|
||||
# Use native string to avoid issues on 2.x: see Python #20140.
|
||||
result = os.path.join(get_cache_base(), str('dylib-cache'), sys.version[:3])
|
||||
if not os.path.isdir(result):
|
||||
os.makedirs(result)
|
||||
return result
|
||||
global cache
|
||||
if cache is None:
|
||||
# Use native string to avoid issues on 2.x: see Python #20140.
|
||||
base = os.path.join(get_cache_base(), str('dylib-cache'),
|
||||
sys.version[:3])
|
||||
cache = Cache(base)
|
||||
return cache
|
||||
|
||||
def _get_extensions(self):
|
||||
pathname = os.path.join(self.dirname, self.filename)
|
||||
|
@ -619,7 +656,11 @@ class Wheel(object):
|
|||
with zf.open(arcname) as bf:
|
||||
wf = wrapper(bf)
|
||||
extensions = json.load(wf)
|
||||
cache_base = self._get_dylib_cache()
|
||||
cache = self._get_dylib_cache()
|
||||
prefix = cache.prefix_to_dir(pathname)
|
||||
cache_base = os.path.join(cache.base, prefix)
|
||||
if not os.path.isdir(cache_base):
|
||||
os.makedirs(cache_base)
|
||||
for name, relpath in extensions.items():
|
||||
dest = os.path.join(cache_base, convert_path(relpath))
|
||||
if not os.path.exists(dest):
|
||||
|
@ -637,10 +678,25 @@ class Wheel(object):
|
|||
pass
|
||||
return result
|
||||
|
||||
def is_compatible(self):
|
||||
"""
|
||||
Determine if a wheel is compatible with the running system.
|
||||
"""
|
||||
return is_compatible(self)
|
||||
|
||||
def is_mountable(self):
|
||||
"""
|
||||
Determine if a wheel is asserted as mountable by its metadata.
|
||||
"""
|
||||
return True # for now - metadata details TBD
|
||||
|
||||
def mount(self, append=False):
|
||||
pathname = os.path.abspath(os.path.join(self.dirname, self.filename))
|
||||
if not is_compatible(self):
|
||||
msg = 'Wheel %s not mountable in this Python.' % pathname
|
||||
if not self.is_compatible():
|
||||
msg = 'Wheel %s not compatible with this Python.' % pathname
|
||||
raise DistlibException(msg)
|
||||
if not self.is_mountable():
|
||||
msg = 'Wheel %s is marked as not mountable.' % pathname
|
||||
raise DistlibException(msg)
|
||||
if pathname in sys.path:
|
||||
logger.debug('%s already in path', pathname)
|
||||
|
@ -667,6 +723,160 @@ class Wheel(object):
|
|||
if _hook in sys.meta_path:
|
||||
sys.meta_path.remove(_hook)
|
||||
|
||||
def verify(self):
|
||||
pathname = os.path.join(self.dirname, self.filename)
|
||||
name_ver = '%s-%s' % (self.name, self.version)
|
||||
data_dir = '%s.data' % name_ver
|
||||
info_dir = '%s.dist-info' % name_ver
|
||||
|
||||
metadata_name = posixpath.join(info_dir, METADATA_FILENAME)
|
||||
wheel_metadata_name = posixpath.join(info_dir, 'WHEEL')
|
||||
record_name = posixpath.join(info_dir, 'RECORD')
|
||||
|
||||
wrapper = codecs.getreader('utf-8')
|
||||
|
||||
with ZipFile(pathname, 'r') as zf:
|
||||
with zf.open(wheel_metadata_name) as bwf:
|
||||
wf = wrapper(bwf)
|
||||
message = message_from_file(wf)
|
||||
wv = message['Wheel-Version'].split('.', 1)
|
||||
file_version = tuple([int(i) for i in wv])
|
||||
# TODO version verification
|
||||
|
||||
records = {}
|
||||
with zf.open(record_name) as bf:
|
||||
with CSVReader(stream=bf) as reader:
|
||||
for row in reader:
|
||||
p = row[0]
|
||||
records[p] = row
|
||||
|
||||
for zinfo in zf.infolist():
|
||||
arcname = zinfo.filename
|
||||
if isinstance(arcname, text_type):
|
||||
u_arcname = arcname
|
||||
else:
|
||||
u_arcname = arcname.decode('utf-8')
|
||||
if '..' in u_arcname:
|
||||
raise DistlibException('invalid entry in '
|
||||
'wheel: %r' % u_arcname)
|
||||
|
||||
# The signature file won't be in RECORD,
|
||||
# and we don't currently don't do anything with it
|
||||
if u_arcname.endswith('/RECORD.jws'):
|
||||
continue
|
||||
row = records[u_arcname]
|
||||
if row[2] and str(zinfo.file_size) != row[2]:
|
||||
raise DistlibException('size mismatch for '
|
||||
'%s' % u_arcname)
|
||||
if row[1]:
|
||||
kind, value = row[1].split('=', 1)
|
||||
with zf.open(arcname) as bf:
|
||||
data = bf.read()
|
||||
_, digest = self.get_hash(data, kind)
|
||||
if digest != value:
|
||||
raise DistlibException('digest mismatch for '
|
||||
'%s' % arcname)
|
||||
|
||||
def update(self, modifier, dest_dir=None, **kwargs):
|
||||
"""
|
||||
Update the contents of a wheel in a generic way. The modifier should
|
||||
be a callable which expects a dictionary argument: its keys are
|
||||
archive-entry paths, and its values are absolute filesystem paths
|
||||
where the contents the corresponding archive entries can be found. The
|
||||
modifier is free to change the contents of the files pointed to, add
|
||||
new entries and remove entries, before returning. This method will
|
||||
extract the entire contents of the wheel to a temporary location, call
|
||||
the modifier, and then use the passed (and possibly updated)
|
||||
dictionary to write a new wheel. If ``dest_dir`` is specified, the new
|
||||
wheel is written there -- otherwise, the original wheel is overwritten.
|
||||
|
||||
The modifier should return True if it updated the wheel, else False.
|
||||
This method returns the same value the modifier returns.
|
||||
"""
|
||||
|
||||
def get_version(path_map, info_dir):
|
||||
version = path = None
|
||||
key = '%s/%s' % (info_dir, METADATA_FILENAME)
|
||||
if key not in path_map:
|
||||
key = '%s/PKG-INFO' % info_dir
|
||||
if key in path_map:
|
||||
path = path_map[key]
|
||||
version = Metadata(path=path).version
|
||||
return version, path
|
||||
|
||||
def update_version(version, path):
|
||||
updated = None
|
||||
try:
|
||||
v = NormalizedVersion(version)
|
||||
i = version.find('-')
|
||||
if i < 0:
|
||||
updated = '%s-1' % version
|
||||
else:
|
||||
parts = [int(s) for s in version[i + 1:].split('.')]
|
||||
parts[-1] += 1
|
||||
updated = '%s-%s' % (version[:i],
|
||||
'.'.join(str(i) for i in parts))
|
||||
except UnsupportedVersionError:
|
||||
logger.debug('Cannot update non-compliant (PEP-440) '
|
||||
'version %r', version)
|
||||
if updated:
|
||||
md = Metadata(path=path)
|
||||
md.version = updated
|
||||
legacy = not path.endswith(METADATA_FILENAME)
|
||||
md.write(path=path, legacy=legacy)
|
||||
logger.debug('Version updated from %r to %r', version,
|
||||
updated)
|
||||
|
||||
pathname = os.path.join(self.dirname, self.filename)
|
||||
name_ver = '%s-%s' % (self.name, self.version)
|
||||
info_dir = '%s.dist-info' % name_ver
|
||||
record_name = posixpath.join(info_dir, 'RECORD')
|
||||
with tempdir() as workdir:
|
||||
with ZipFile(pathname, 'r') as zf:
|
||||
path_map = {}
|
||||
for zinfo in zf.infolist():
|
||||
arcname = zinfo.filename
|
||||
if isinstance(arcname, text_type):
|
||||
u_arcname = arcname
|
||||
else:
|
||||
u_arcname = arcname.decode('utf-8')
|
||||
if u_arcname == record_name:
|
||||
continue
|
||||
if '..' in u_arcname:
|
||||
raise DistlibException('invalid entry in '
|
||||
'wheel: %r' % u_arcname)
|
||||
zf.extract(zinfo, workdir)
|
||||
path = os.path.join(workdir, convert_path(u_arcname))
|
||||
path_map[u_arcname] = path
|
||||
|
||||
# Remember the version.
|
||||
original_version, _ = get_version(path_map, info_dir)
|
||||
# Files extracted. Call the modifier.
|
||||
modified = modifier(path_map, **kwargs)
|
||||
if modified:
|
||||
# Something changed - need to build a new wheel.
|
||||
current_version, path = get_version(path_map, info_dir)
|
||||
if current_version and (current_version == original_version):
|
||||
# Add or update local version to signify changes.
|
||||
update_version(current_version, path)
|
||||
# Decide where the new wheel goes.
|
||||
if dest_dir is None:
|
||||
fd, newpath = tempfile.mkstemp(suffix='.whl',
|
||||
prefix='wheel-update-',
|
||||
dir=workdir)
|
||||
os.close(fd)
|
||||
else:
|
||||
if not os.path.isdir(dest_dir):
|
||||
raise DistlibException('Not a directory: %r' % dest_dir)
|
||||
newpath = os.path.join(dest_dir, self.filename)
|
||||
archive_paths = list(path_map.items())
|
||||
distinfo = os.path.join(workdir, info_dir)
|
||||
info = distinfo, info_dir
|
||||
self.write_records(info, workdir, archive_paths)
|
||||
self.build_zip(newpath, archive_paths)
|
||||
if dest_dir is None:
|
||||
shutil.copyfile(newpath, pathname)
|
||||
return modified
|
||||
|
||||
def compatible_tags():
|
||||
"""
|
||||
|
@ -687,9 +897,34 @@ def compatible_tags():
|
|||
abis.append('none')
|
||||
result = []
|
||||
|
||||
arches = [ARCH]
|
||||
if sys.platform == 'darwin':
|
||||
m = re.match('(\w+)_(\d+)_(\d+)_(\w+)$', ARCH)
|
||||
if m:
|
||||
name, major, minor, arch = m.groups()
|
||||
minor = int(minor)
|
||||
matches = [arch]
|
||||
if arch in ('i386', 'ppc'):
|
||||
matches.append('fat')
|
||||
if arch in ('i386', 'ppc', 'x86_64'):
|
||||
matches.append('fat3')
|
||||
if arch in ('ppc64', 'x86_64'):
|
||||
matches.append('fat64')
|
||||
if arch in ('i386', 'x86_64'):
|
||||
matches.append('intel')
|
||||
if arch in ('i386', 'x86_64', 'intel', 'ppc', 'ppc64'):
|
||||
matches.append('universal')
|
||||
while minor >= 0:
|
||||
for match in matches:
|
||||
s = '%s_%s_%s_%s' % (name, major, minor, match)
|
||||
if s != ARCH: # already there
|
||||
arches.append(s)
|
||||
minor -= 1
|
||||
|
||||
# Most specific - our Python version, ABI and arch
|
||||
for abi in abis:
|
||||
result.append((''.join((IMP_PREFIX, versions[0])), abi, ARCH))
|
||||
for arch in arches:
|
||||
result.append((''.join((IMP_PREFIX, versions[0])), abi, arch))
|
||||
|
||||
# where no ABI / arch dependency, but IMP_PREFIX dependency
|
||||
for i, version in enumerate(versions):
|
||||
|
@ -702,7 +937,7 @@ def compatible_tags():
|
|||
result.append((''.join(('py', version)), 'none', 'any'))
|
||||
if i == 0:
|
||||
result.append((''.join(('py', version[0])), 'none', 'any'))
|
||||
return result
|
||||
return set(result)
|
||||
|
||||
|
||||
COMPATIBLE_TAGS = compatible_tags()
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
Copyright (c) 2006-2013 James Graham and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -20,4 +20,4 @@ from .serializer import serialize
|
|||
|
||||
__all__ = ["HTMLParser", "parse", "parseFragment", "getTreeBuilder",
|
||||
"getTreeWalker", "serialize"]
|
||||
__version__ = "1.0b1"
|
||||
__version__ = "1.0b3"
|
||||
|
|
|
@ -433,6 +433,24 @@ mathmlTextIntegrationPointElements = frozenset((
|
|||
(namespaces["mathml"], "mtext")
|
||||
))
|
||||
|
||||
adjustForeignAttributes = {
|
||||
"xlink:actuate": ("xlink", "actuate", namespaces["xlink"]),
|
||||
"xlink:arcrole": ("xlink", "arcrole", namespaces["xlink"]),
|
||||
"xlink:href": ("xlink", "href", namespaces["xlink"]),
|
||||
"xlink:role": ("xlink", "role", namespaces["xlink"]),
|
||||
"xlink:show": ("xlink", "show", namespaces["xlink"]),
|
||||
"xlink:title": ("xlink", "title", namespaces["xlink"]),
|
||||
"xlink:type": ("xlink", "type", namespaces["xlink"]),
|
||||
"xml:base": ("xml", "base", namespaces["xml"]),
|
||||
"xml:lang": ("xml", "lang", namespaces["xml"]),
|
||||
"xml:space": ("xml", "space", namespaces["xml"]),
|
||||
"xmlns": (None, "xmlns", namespaces["xmlns"]),
|
||||
"xmlns:xlink": ("xmlns", "xlink", namespaces["xmlns"])
|
||||
}
|
||||
|
||||
unadjustForeignAttributes = dict([((ns, local), qname) for qname, (prefix, local, ns) in
|
||||
adjustForeignAttributes.items()])
|
||||
|
||||
spaceCharacters = frozenset((
|
||||
"\t",
|
||||
"\n",
|
||||
|
|
|
@ -23,24 +23,24 @@ class Filter(_base.Filter):
|
|||
if type in ("StartTag", "EmptyTag"):
|
||||
name = token["name"]
|
||||
if contentModelFlag != "PCDATA":
|
||||
raise LintError(_("StartTag not in PCDATA content model flag: %s") % name)
|
||||
raise LintError(_("StartTag not in PCDATA content model flag: %(tag)s") % {"tag": name})
|
||||
if not isinstance(name, str):
|
||||
raise LintError(_("Tag name is not a string: %r") % name)
|
||||
raise LintError(_("Tag name is not a string: %(tag)r") % {"tag": name})
|
||||
if not name:
|
||||
raise LintError(_("Empty tag name"))
|
||||
if type == "StartTag" and name in voidElements:
|
||||
raise LintError(_("Void element reported as StartTag token: %s") % name)
|
||||
raise LintError(_("Void element reported as StartTag token: %(tag)s") % {"tag": name})
|
||||
elif type == "EmptyTag" and name not in voidElements:
|
||||
raise LintError(_("Non-void element reported as EmptyTag token: %s") % token["name"])
|
||||
raise LintError(_("Non-void element reported as EmptyTag token: %(tag)s") % {"tag": token["name"]})
|
||||
if type == "StartTag":
|
||||
open_elements.append(name)
|
||||
for name, value in token["data"]:
|
||||
if not isinstance(name, str):
|
||||
raise LintError(_("Attribute name is not a string: %r") % name)
|
||||
raise LintError(_("Attribute name is not a string: %(name)r") % {"name": name})
|
||||
if not name:
|
||||
raise LintError(_("Empty attribute name"))
|
||||
if not isinstance(value, str):
|
||||
raise LintError(_("Attribute value is not a string: %r") % value)
|
||||
raise LintError(_("Attribute value is not a string: %(value)r") % {"value": value})
|
||||
if name in cdataElements:
|
||||
contentModelFlag = "CDATA"
|
||||
elif name in rcdataElements:
|
||||
|
@ -51,14 +51,14 @@ class Filter(_base.Filter):
|
|||
elif type == "EndTag":
|
||||
name = token["name"]
|
||||
if not isinstance(name, str):
|
||||
raise LintError(_("Tag name is not a string: %r") % name)
|
||||
raise LintError(_("Tag name is not a string: %(tag)r") % {"tag": name})
|
||||
if not name:
|
||||
raise LintError(_("Empty tag name"))
|
||||
if name in voidElements:
|
||||
raise LintError(_("Void element reported as EndTag token: %s") % name)
|
||||
raise LintError(_("Void element reported as EndTag token: %(tag)s") % {"tag": name})
|
||||
start_name = open_elements.pop()
|
||||
if start_name != name:
|
||||
raise LintError(_("EndTag (%s) does not match StartTag (%s)") % (name, start_name))
|
||||
raise LintError(_("EndTag (%(end)s) does not match StartTag (%(start)s)") % {"end": name, "start": start_name})
|
||||
contentModelFlag = "PCDATA"
|
||||
|
||||
elif type == "Comment":
|
||||
|
@ -68,26 +68,26 @@ class Filter(_base.Filter):
|
|||
elif type in ("Characters", "SpaceCharacters"):
|
||||
data = token["data"]
|
||||
if not isinstance(data, str):
|
||||
raise LintError(_("Attribute name is not a string: %r") % data)
|
||||
raise LintError(_("Attribute name is not a string: %(name)r") % {"name": data})
|
||||
if not data:
|
||||
raise LintError(_("%s token with empty data") % type)
|
||||
raise LintError(_("%(type)s token with empty data") % {"type": type})
|
||||
if type == "SpaceCharacters":
|
||||
data = data.strip(spaceCharacters)
|
||||
if data:
|
||||
raise LintError(_("Non-space character(s) found in SpaceCharacters token: ") % data)
|
||||
raise LintError(_("Non-space character(s) found in SpaceCharacters token: %(token)r") % {"token": data})
|
||||
|
||||
elif type == "Doctype":
|
||||
name = token["name"]
|
||||
if contentModelFlag != "PCDATA":
|
||||
raise LintError(_("Doctype not in PCDATA content model flag: %s") % name)
|
||||
raise LintError(_("Doctype not in PCDATA content model flag: %(name)s") % {"name": name})
|
||||
if not isinstance(name, str):
|
||||
raise LintError(_("Tag name is not a string: %r") % name)
|
||||
raise LintError(_("Tag name is not a string: %(tag)r") % {"tag": name})
|
||||
# XXX: what to do with token["data"] ?
|
||||
|
||||
elif type in ("ParseError", "SerializeError"):
|
||||
pass
|
||||
|
||||
else:
|
||||
raise LintError(_("Unknown token type: %s") % type)
|
||||
raise LintError(_("Unknown token type: %(type)s") % {"type": type})
|
||||
|
||||
yield token
|
||||
|
|
|
@ -17,6 +17,7 @@ from .constants import headingElements
|
|||
from .constants import cdataElements, rcdataElements
|
||||
from .constants import tokenTypes, ReparseException, namespaces
|
||||
from .constants import htmlIntegrationPointElements, mathmlTextIntegrationPointElements
|
||||
from .constants import adjustForeignAttributes as adjustForeignAttributesMap
|
||||
|
||||
|
||||
def parse(doc, treebuilder="etree", encoding=None,
|
||||
|
@ -168,7 +169,7 @@ class HTMLParser(object):
|
|||
(self.isMathMLTextIntegrationPoint(currentNode) and
|
||||
((type == StartTagToken and
|
||||
token["name"] not in frozenset(["mglyph", "malignmark"])) or
|
||||
type in (CharactersToken, SpaceCharactersToken))) or
|
||||
type in (CharactersToken, SpaceCharactersToken))) or
|
||||
(currentNodeNamespace == namespaces["mathml"] and
|
||||
currentNodeName == "annotation-xml" and
|
||||
token["name"] == "svg") or
|
||||
|
@ -333,20 +334,7 @@ class HTMLParser(object):
|
|||
del token["data"][originalName]
|
||||
|
||||
def adjustForeignAttributes(self, token):
|
||||
replacements = {
|
||||
"xlink:actuate": ("xlink", "actuate", namespaces["xlink"]),
|
||||
"xlink:arcrole": ("xlink", "arcrole", namespaces["xlink"]),
|
||||
"xlink:href": ("xlink", "href", namespaces["xlink"]),
|
||||
"xlink:role": ("xlink", "role", namespaces["xlink"]),
|
||||
"xlink:show": ("xlink", "show", namespaces["xlink"]),
|
||||
"xlink:title": ("xlink", "title", namespaces["xlink"]),
|
||||
"xlink:type": ("xlink", "type", namespaces["xlink"]),
|
||||
"xml:base": ("xml", "base", namespaces["xml"]),
|
||||
"xml:lang": ("xml", "lang", namespaces["xml"]),
|
||||
"xml:space": ("xml", "space", namespaces["xml"]),
|
||||
"xmlns": (None, "xmlns", namespaces["xmlns"]),
|
||||
"xmlns:xlink": ("xmlns", "xlink", namespaces["xmlns"])
|
||||
}
|
||||
replacements = adjustForeignAttributesMap
|
||||
|
||||
for originalName in token["data"].keys():
|
||||
if originalName in replacements:
|
||||
|
@ -519,61 +507,61 @@ def getPhases(debug):
|
|||
|
||||
if (not correct or token["name"] != "html"
|
||||
or publicId.startswith(
|
||||
("+//silmaril//dtd html pro v0r11 19970101//",
|
||||
"-//advasoft ltd//dtd html 3.0 aswedit + extensions//",
|
||||
"-//as//dtd html 3.0 aswedit + extensions//",
|
||||
"-//ietf//dtd html 2.0 level 1//",
|
||||
"-//ietf//dtd html 2.0 level 2//",
|
||||
"-//ietf//dtd html 2.0 strict level 1//",
|
||||
"-//ietf//dtd html 2.0 strict level 2//",
|
||||
"-//ietf//dtd html 2.0 strict//",
|
||||
"-//ietf//dtd html 2.0//",
|
||||
"-//ietf//dtd html 2.1e//",
|
||||
"-//ietf//dtd html 3.0//",
|
||||
"-//ietf//dtd html 3.2 final//",
|
||||
"-//ietf//dtd html 3.2//",
|
||||
"-//ietf//dtd html 3//",
|
||||
"-//ietf//dtd html level 0//",
|
||||
"-//ietf//dtd html level 1//",
|
||||
"-//ietf//dtd html level 2//",
|
||||
"-//ietf//dtd html level 3//",
|
||||
"-//ietf//dtd html strict level 0//",
|
||||
"-//ietf//dtd html strict level 1//",
|
||||
"-//ietf//dtd html strict level 2//",
|
||||
"-//ietf//dtd html strict level 3//",
|
||||
"-//ietf//dtd html strict//",
|
||||
"-//ietf//dtd html//",
|
||||
"-//metrius//dtd metrius presentational//",
|
||||
"-//microsoft//dtd internet explorer 2.0 html strict//",
|
||||
"-//microsoft//dtd internet explorer 2.0 html//",
|
||||
"-//microsoft//dtd internet explorer 2.0 tables//",
|
||||
"-//microsoft//dtd internet explorer 3.0 html strict//",
|
||||
"-//microsoft//dtd internet explorer 3.0 html//",
|
||||
"-//microsoft//dtd internet explorer 3.0 tables//",
|
||||
"-//netscape comm. corp.//dtd html//",
|
||||
"-//netscape comm. corp.//dtd strict html//",
|
||||
"-//o'reilly and associates//dtd html 2.0//",
|
||||
"-//o'reilly and associates//dtd html extended 1.0//",
|
||||
"-//o'reilly and associates//dtd html extended relaxed 1.0//",
|
||||
"-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//",
|
||||
"-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//",
|
||||
"-//spyglass//dtd html 2.0 extended//",
|
||||
"-//sq//dtd html 2.0 hotmetal + extensions//",
|
||||
"-//sun microsystems corp.//dtd hotjava html//",
|
||||
"-//sun microsystems corp.//dtd hotjava strict html//",
|
||||
"-//w3c//dtd html 3 1995-03-24//",
|
||||
"-//w3c//dtd html 3.2 draft//",
|
||||
"-//w3c//dtd html 3.2 final//",
|
||||
"-//w3c//dtd html 3.2//",
|
||||
"-//w3c//dtd html 3.2s draft//",
|
||||
"-//w3c//dtd html 4.0 frameset//",
|
||||
"-//w3c//dtd html 4.0 transitional//",
|
||||
"-//w3c//dtd html experimental 19960712//",
|
||||
"-//w3c//dtd html experimental 970421//",
|
||||
"-//w3c//dtd w3 html//",
|
||||
"-//w3o//dtd w3 html 3.0//",
|
||||
"-//webtechs//dtd mozilla html 2.0//",
|
||||
"-//webtechs//dtd mozilla html//"))
|
||||
("+//silmaril//dtd html pro v0r11 19970101//",
|
||||
"-//advasoft ltd//dtd html 3.0 aswedit + extensions//",
|
||||
"-//as//dtd html 3.0 aswedit + extensions//",
|
||||
"-//ietf//dtd html 2.0 level 1//",
|
||||
"-//ietf//dtd html 2.0 level 2//",
|
||||
"-//ietf//dtd html 2.0 strict level 1//",
|
||||
"-//ietf//dtd html 2.0 strict level 2//",
|
||||
"-//ietf//dtd html 2.0 strict//",
|
||||
"-//ietf//dtd html 2.0//",
|
||||
"-//ietf//dtd html 2.1e//",
|
||||
"-//ietf//dtd html 3.0//",
|
||||
"-//ietf//dtd html 3.2 final//",
|
||||
"-//ietf//dtd html 3.2//",
|
||||
"-//ietf//dtd html 3//",
|
||||
"-//ietf//dtd html level 0//",
|
||||
"-//ietf//dtd html level 1//",
|
||||
"-//ietf//dtd html level 2//",
|
||||
"-//ietf//dtd html level 3//",
|
||||
"-//ietf//dtd html strict level 0//",
|
||||
"-//ietf//dtd html strict level 1//",
|
||||
"-//ietf//dtd html strict level 2//",
|
||||
"-//ietf//dtd html strict level 3//",
|
||||
"-//ietf//dtd html strict//",
|
||||
"-//ietf//dtd html//",
|
||||
"-//metrius//dtd metrius presentational//",
|
||||
"-//microsoft//dtd internet explorer 2.0 html strict//",
|
||||
"-//microsoft//dtd internet explorer 2.0 html//",
|
||||
"-//microsoft//dtd internet explorer 2.0 tables//",
|
||||
"-//microsoft//dtd internet explorer 3.0 html strict//",
|
||||
"-//microsoft//dtd internet explorer 3.0 html//",
|
||||
"-//microsoft//dtd internet explorer 3.0 tables//",
|
||||
"-//netscape comm. corp.//dtd html//",
|
||||
"-//netscape comm. corp.//dtd strict html//",
|
||||
"-//o'reilly and associates//dtd html 2.0//",
|
||||
"-//o'reilly and associates//dtd html extended 1.0//",
|
||||
"-//o'reilly and associates//dtd html extended relaxed 1.0//",
|
||||
"-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//",
|
||||
"-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//",
|
||||
"-//spyglass//dtd html 2.0 extended//",
|
||||
"-//sq//dtd html 2.0 hotmetal + extensions//",
|
||||
"-//sun microsystems corp.//dtd hotjava html//",
|
||||
"-//sun microsystems corp.//dtd hotjava strict html//",
|
||||
"-//w3c//dtd html 3 1995-03-24//",
|
||||
"-//w3c//dtd html 3.2 draft//",
|
||||
"-//w3c//dtd html 3.2 final//",
|
||||
"-//w3c//dtd html 3.2//",
|
||||
"-//w3c//dtd html 3.2s draft//",
|
||||
"-//w3c//dtd html 4.0 frameset//",
|
||||
"-//w3c//dtd html 4.0 transitional//",
|
||||
"-//w3c//dtd html experimental 19960712//",
|
||||
"-//w3c//dtd html experimental 970421//",
|
||||
"-//w3c//dtd w3 html//",
|
||||
"-//w3o//dtd w3 html 3.0//",
|
||||
"-//webtechs//dtd mozilla html 2.0//",
|
||||
"-//webtechs//dtd mozilla html//"))
|
||||
or publicId in
|
||||
("-//w3o//dtd w3 html strict 3.0//en//",
|
||||
"-/w3c/dtd html 4.0 transitional/en",
|
||||
|
|
|
@ -43,7 +43,7 @@ ascii_punctuation_re = re.compile("[\u0009-\u000D\u0020-\u002F\u003A-\u0040\u005
|
|||
charsUntilRegEx = {}
|
||||
|
||||
|
||||
class BufferedStream:
|
||||
class BufferedStream(object):
|
||||
"""Buffering for streams that do not have buffering of their own
|
||||
|
||||
The buffer is implemented as a list of chunks on the assumption that
|
||||
|
@ -63,11 +63,11 @@ class BufferedStream:
|
|||
return pos
|
||||
|
||||
def seek(self, pos):
|
||||
assert pos < self._bufferedBytes()
|
||||
assert pos <= self._bufferedBytes()
|
||||
offset = pos
|
||||
i = 0
|
||||
while len(self.buffer[i]) < offset:
|
||||
offset -= pos
|
||||
offset -= len(self.buffer[i])
|
||||
i += 1
|
||||
self.position = [i, offset]
|
||||
|
||||
|
@ -114,7 +114,7 @@ class BufferedStream:
|
|||
if remainingBytes:
|
||||
rv.append(self._readStream(remainingBytes))
|
||||
|
||||
return "".join(rv)
|
||||
return b"".join(rv)
|
||||
|
||||
|
||||
def HTMLInputStream(source, encoding=None, parseMeta=True, chardet=True):
|
||||
|
@ -132,7 +132,7 @@ def HTMLInputStream(source, encoding=None, parseMeta=True, chardet=True):
|
|||
return HTMLBinaryInputStream(source, encoding, parseMeta, chardet)
|
||||
|
||||
|
||||
class HTMLUnicodeInputStream:
|
||||
class HTMLUnicodeInputStream(object):
|
||||
"""Provides a unicode stream of characters to the HTMLTokenizer.
|
||||
|
||||
This class takes care of character encoding and removing or replacing
|
||||
|
|
|
@ -92,15 +92,17 @@ class HTMLSerializer(object):
|
|||
resolve_entities = True
|
||||
|
||||
# miscellaneous options
|
||||
alphabetical_attributes = False
|
||||
inject_meta_charset = True
|
||||
strip_whitespace = False
|
||||
sanitize = False
|
||||
|
||||
options = ("quote_attr_values", "quote_char", "use_best_quote_char",
|
||||
"minimize_boolean_attributes", "use_trailing_solidus",
|
||||
"space_before_trailing_solidus", "omit_optional_tags",
|
||||
"strip_whitespace", "inject_meta_charset", "escape_lt_in_attrs",
|
||||
"escape_rcdata", "resolve_entities", "sanitize")
|
||||
"omit_optional_tags", "minimize_boolean_attributes",
|
||||
"use_trailing_solidus", "space_before_trailing_solidus",
|
||||
"escape_lt_in_attrs", "escape_rcdata", "resolve_entities",
|
||||
"alphabetical_attributes", "inject_meta_charset",
|
||||
"strip_whitespace", "sanitize")
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Initialize HTMLSerializer.
|
||||
|
@ -143,6 +145,8 @@ class HTMLSerializer(object):
|
|||
See `html5lib user documentation`_
|
||||
omit_optional_tags=True|False
|
||||
Omit start/end tags that are optional.
|
||||
alphabetical_attributes=False|True
|
||||
Reorder attributes to be in alphabetical order.
|
||||
|
||||
.. _html5lib user documentation: http://code.google.com/p/html5lib/wiki/UserDocumentation
|
||||
"""
|
||||
|
@ -171,10 +175,11 @@ class HTMLSerializer(object):
|
|||
self.encoding = encoding
|
||||
in_cdata = False
|
||||
self.errors = []
|
||||
|
||||
if encoding and self.inject_meta_charset:
|
||||
from ..filters.inject_meta_charset import Filter
|
||||
treewalker = Filter(treewalker, encoding)
|
||||
# XXX: WhitespaceFilter should be used before OptionalTagFilter
|
||||
# WhitespaceFilter should be used before OptionalTagFilter
|
||||
# for maximum efficiently of this latter filter
|
||||
if self.strip_whitespace:
|
||||
from ..filters.whitespace import Filter
|
||||
|
@ -185,6 +190,12 @@ class HTMLSerializer(object):
|
|||
if self.omit_optional_tags:
|
||||
from ..filters.optionaltags import Filter
|
||||
treewalker = Filter(treewalker)
|
||||
# Alphabetical attributes must be last, as other filters
|
||||
# could add attributes and alter the order
|
||||
if self.alphabetical_attributes:
|
||||
from ..filters.alphabeticalattributes import Filter
|
||||
treewalker = Filter(treewalker)
|
||||
|
||||
for token in treewalker:
|
||||
type = token["type"]
|
||||
if type == "Doctype":
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
from __future__ import absolute_import, division, unicode_literals
|
||||
|
||||
from xml.sax.xmlreader import AttributesNSImpl
|
||||
|
||||
from ..constants import adjustForeignAttributes, unadjustForeignAttributes
|
||||
|
||||
prefix_mapping = {}
|
||||
for prefix, localName, namespace in adjustForeignAttributes.values():
|
||||
if prefix is not None:
|
||||
prefix_mapping[prefix] = namespace
|
||||
|
||||
|
||||
def to_sax(walker, handler):
|
||||
"""Call SAX-like content handler based on treewalker walker"""
|
||||
handler.startDocument()
|
||||
for prefix, namespace in prefix_mapping.items():
|
||||
handler.startPrefixMapping(prefix, namespace)
|
||||
|
||||
for token in walker:
|
||||
type = token["type"]
|
||||
if type == "Doctype":
|
||||
continue
|
||||
elif type in ("StartTag", "EmptyTag"):
|
||||
attrs = AttributesNSImpl(token["data"],
|
||||
unadjustForeignAttributes)
|
||||
handler.startElementNS((token["namespace"], token["name"]),
|
||||
token["name"],
|
||||
attrs)
|
||||
if type == "EmptyTag":
|
||||
handler.endElementNS((token["namespace"], token["name"]),
|
||||
token["name"])
|
||||
elif type == "EndTag":
|
||||
handler.endElementNS((token["namespace"], token["name"]),
|
||||
token["name"])
|
||||
elif type in ("Characters", "SpaceCharacters"):
|
||||
handler.characters(token["data"])
|
||||
elif type == "Comment":
|
||||
pass
|
||||
else:
|
||||
assert False, "Unknown token type"
|
||||
|
||||
for prefix, namespace in prefix_mapping.items():
|
||||
handler.endPrefixMapping(prefix)
|
||||
handler.endDocument()
|
|
@ -1,7 +1,7 @@
|
|||
from __future__ import absolute_import, division, unicode_literals
|
||||
|
||||
|
||||
from xml.dom import minidom, Node, XML_NAMESPACE, XMLNS_NAMESPACE
|
||||
from xml.dom import minidom, Node
|
||||
import weakref
|
||||
|
||||
from . import _base
|
||||
|
@ -220,69 +220,6 @@ def getDomBuilder(DomImplementation):
|
|||
|
||||
return "\n".join(rv)
|
||||
|
||||
def dom2sax(node, handler, nsmap={'xml': XML_NAMESPACE}):
|
||||
if node.nodeType == Node.ELEMENT_NODE:
|
||||
if not nsmap:
|
||||
handler.startElement(node.nodeName, node.attributes)
|
||||
for child in node.childNodes:
|
||||
dom2sax(child, handler, nsmap)
|
||||
handler.endElement(node.nodeName)
|
||||
else:
|
||||
attributes = dict(node.attributes.itemsNS())
|
||||
|
||||
# gather namespace declarations
|
||||
prefixes = []
|
||||
for attrname in list(node.attributes.keys()):
|
||||
attr = node.getAttributeNode(attrname)
|
||||
if (attr.namespaceURI == XMLNS_NAMESPACE or
|
||||
(attr.namespaceURI is None and attr.nodeName.startswith('xmlns'))):
|
||||
prefix = (attr.nodeName != 'xmlns' and attr.nodeName or None)
|
||||
handler.startPrefixMapping(prefix, attr.nodeValue)
|
||||
prefixes.append(prefix)
|
||||
nsmap = nsmap.copy()
|
||||
nsmap[prefix] = attr.nodeValue
|
||||
del attributes[(attr.namespaceURI, attr.nodeName)]
|
||||
|
||||
# apply namespace declarations
|
||||
for attrname in list(node.attributes.keys()):
|
||||
attr = node.getAttributeNode(attrname)
|
||||
if attr.namespaceURI is None and ':' in attr.nodeName:
|
||||
prefix = attr.nodeName.split(':')[0]
|
||||
if prefix in nsmap:
|
||||
del attributes[(attr.namespaceURI, attr.nodeName)]
|
||||
attributes[(nsmap[prefix], attr.nodeName)] = attr.nodeValue
|
||||
|
||||
# SAX events
|
||||
ns = node.namespaceURI or nsmap.get(None, None)
|
||||
handler.startElementNS((ns, node.nodeName), node.nodeName, attributes)
|
||||
for child in node.childNodes:
|
||||
dom2sax(child, handler, nsmap)
|
||||
handler.endElementNS((ns, node.nodeName), node.nodeName)
|
||||
for prefix in prefixes:
|
||||
handler.endPrefixMapping(prefix)
|
||||
|
||||
elif node.nodeType in [Node.TEXT_NODE, Node.CDATA_SECTION_NODE]:
|
||||
handler.characters(node.nodeValue)
|
||||
|
||||
elif node.nodeType == Node.DOCUMENT_NODE:
|
||||
handler.startDocument()
|
||||
for child in node.childNodes:
|
||||
dom2sax(child, handler, nsmap)
|
||||
handler.endDocument()
|
||||
|
||||
elif node.nodeType == Node.DOCUMENT_FRAGMENT_NODE:
|
||||
for child in node.childNodes:
|
||||
dom2sax(child, handler, nsmap)
|
||||
|
||||
else:
|
||||
# ATTRIBUTE_NODE
|
||||
# ENTITY_NODE
|
||||
# PROCESSING_INSTRUCTION_NODE
|
||||
# COMMENT_NODE
|
||||
# DOCUMENT_TYPE_NODE
|
||||
# NOTATION_NODE
|
||||
pass
|
||||
|
||||
return locals()
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,41 @@
|
|||
from __future__ import absolute_import, division, unicode_literals
|
||||
from pip._vendor.six import text_type
|
||||
from pip._vendor.six import text_type, string_types
|
||||
|
||||
import gettext
|
||||
_ = gettext.gettext
|
||||
|
||||
from xml.dom import Node
|
||||
|
||||
DOCUMENT = Node.DOCUMENT_NODE
|
||||
DOCTYPE = Node.DOCUMENT_TYPE_NODE
|
||||
TEXT = Node.TEXT_NODE
|
||||
ELEMENT = Node.ELEMENT_NODE
|
||||
COMMENT = Node.COMMENT_NODE
|
||||
ENTITY = Node.ENTITY_NODE
|
||||
UNKNOWN = "<#UNKNOWN#>"
|
||||
|
||||
from ..constants import voidElements, spaceCharacters
|
||||
spaceCharacters = "".join(spaceCharacters)
|
||||
|
||||
|
||||
def to_text(s, blank_if_none=True):
|
||||
"""Wrapper around six.text_type to convert None to empty string"""
|
||||
if s is None:
|
||||
if blank_if_none:
|
||||
return ""
|
||||
else:
|
||||
return None
|
||||
elif isinstance(s, text_type):
|
||||
return s
|
||||
else:
|
||||
return text_type(s)
|
||||
|
||||
|
||||
def is_text_or_none(string):
|
||||
"""Wrapper around isinstance(string_types) or is None"""
|
||||
return string is None or isinstance(string, string_types)
|
||||
|
||||
|
||||
class TreeWalker(object):
|
||||
def __init__(self, tree):
|
||||
self.tree = tree
|
||||
|
@ -19,45 +47,47 @@ class TreeWalker(object):
|
|||
return {"type": "SerializeError", "data": msg}
|
||||
|
||||
def emptyTag(self, namespace, name, attrs, hasChildren=False):
|
||||
assert namespace is None or isinstance(namespace, text_type), type(namespace)
|
||||
assert isinstance(name, text_type), type(name)
|
||||
assert all((namespace is None or isinstance(namespace, text_type)) and
|
||||
isinstance(name, text_type) and
|
||||
isinstance(value, text_type)
|
||||
assert namespace is None or isinstance(namespace, string_types), type(namespace)
|
||||
assert isinstance(name, string_types), type(name)
|
||||
assert all((namespace is None or isinstance(namespace, string_types)) and
|
||||
isinstance(name, string_types) and
|
||||
isinstance(value, string_types)
|
||||
for (namespace, name), value in attrs.items())
|
||||
|
||||
yield {"type": "EmptyTag", "name": name,
|
||||
"namespace": namespace,
|
||||
yield {"type": "EmptyTag", "name": to_text(name, False),
|
||||
"namespace": to_text(namespace),
|
||||
"data": attrs}
|
||||
if hasChildren:
|
||||
yield self.error(_("Void element has children"))
|
||||
|
||||
def startTag(self, namespace, name, attrs):
|
||||
assert namespace is None or isinstance(namespace, text_type), type(namespace)
|
||||
assert isinstance(name, text_type), type(name)
|
||||
assert all((namespace is None or isinstance(namespace, text_type)) and
|
||||
isinstance(name, text_type) and
|
||||
isinstance(value, text_type)
|
||||
assert namespace is None or isinstance(namespace, string_types), type(namespace)
|
||||
assert isinstance(name, string_types), type(name)
|
||||
assert all((namespace is None or isinstance(namespace, string_types)) and
|
||||
isinstance(name, string_types) and
|
||||
isinstance(value, string_types)
|
||||
for (namespace, name), value in attrs.items())
|
||||
|
||||
return {"type": "StartTag",
|
||||
"name": name,
|
||||
"namespace": namespace,
|
||||
"data": attrs}
|
||||
"name": text_type(name),
|
||||
"namespace": to_text(namespace),
|
||||
"data": dict(((to_text(namespace, False), to_text(name)),
|
||||
to_text(value, False))
|
||||
for (namespace, name), value in attrs.items())}
|
||||
|
||||
def endTag(self, namespace, name):
|
||||
assert namespace is None or isinstance(namespace, text_type), type(namespace)
|
||||
assert isinstance(name, text_type), type(namespace)
|
||||
assert namespace is None or isinstance(namespace, string_types), type(namespace)
|
||||
assert isinstance(name, string_types), type(namespace)
|
||||
|
||||
return {"type": "EndTag",
|
||||
"name": name,
|
||||
"namespace": namespace,
|
||||
"name": to_text(name, False),
|
||||
"namespace": to_text(namespace),
|
||||
"data": {}}
|
||||
|
||||
def text(self, data):
|
||||
assert isinstance(data, text_type), type(data)
|
||||
assert isinstance(data, string_types), type(data)
|
||||
|
||||
data = data
|
||||
data = to_text(data)
|
||||
middle = data.lstrip(spaceCharacters)
|
||||
left = data[:len(data) - len(middle)]
|
||||
if left:
|
||||
|
@ -71,56 +101,30 @@ class TreeWalker(object):
|
|||
yield {"type": "SpaceCharacters", "data": right}
|
||||
|
||||
def comment(self, data):
|
||||
assert isinstance(data, text_type), type(data)
|
||||
assert isinstance(data, string_types), type(data)
|
||||
|
||||
return {"type": "Comment", "data": data}
|
||||
return {"type": "Comment", "data": text_type(data)}
|
||||
|
||||
def doctype(self, name, publicId=None, systemId=None, correct=True):
|
||||
assert name is None or isinstance(name, text_type), type(name)
|
||||
assert publicId is None or isinstance(publicId, text_type), type(publicId)
|
||||
assert systemId is None or isinstance(systemId, text_type), type(systemId)
|
||||
assert is_text_or_none(name), type(name)
|
||||
assert is_text_or_none(publicId), type(publicId)
|
||||
assert is_text_or_none(systemId), type(systemId)
|
||||
|
||||
return {"type": "Doctype",
|
||||
"name": name if name is not None else "",
|
||||
"publicId": publicId,
|
||||
"systemId": systemId,
|
||||
"correct": correct}
|
||||
"name": to_text(name),
|
||||
"publicId": to_text(publicId),
|
||||
"systemId": to_text(systemId),
|
||||
"correct": to_text(correct)}
|
||||
|
||||
def entity(self, name):
|
||||
assert isinstance(name, text_type), type(name)
|
||||
assert isinstance(name, string_types), type(name)
|
||||
|
||||
return {"type": "Entity", "name": name}
|
||||
return {"type": "Entity", "name": text_type(name)}
|
||||
|
||||
def unknown(self, nodeType):
|
||||
return self.error(_("Unknown node type: ") + nodeType)
|
||||
|
||||
|
||||
class RecursiveTreeWalker(TreeWalker):
|
||||
def walkChildren(self, node):
|
||||
raise NotImplementedError
|
||||
|
||||
def element(self, node, namespace, name, attrs, hasChildren):
|
||||
if name in voidElements:
|
||||
for token in self.emptyTag(namespace, name, attrs, hasChildren):
|
||||
yield token
|
||||
else:
|
||||
yield self.startTag(name, attrs)
|
||||
if hasChildren:
|
||||
for token in self.walkChildren(node):
|
||||
yield token
|
||||
yield self.endTag(name)
|
||||
|
||||
from xml.dom import Node
|
||||
|
||||
DOCUMENT = Node.DOCUMENT_NODE
|
||||
DOCTYPE = Node.DOCUMENT_TYPE_NODE
|
||||
TEXT = Node.TEXT_NODE
|
||||
ELEMENT = Node.ELEMENT_NODE
|
||||
COMMENT = Node.COMMENT_NODE
|
||||
ENTITY = Node.ENTITY_NODE
|
||||
UNKNOWN = "<#UNKNOWN#>"
|
||||
|
||||
|
||||
class NonRecursiveTreeWalker(TreeWalker):
|
||||
def getNodeDetails(self, node):
|
||||
raise NotImplementedError
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
from __future__ import absolute_import, division, unicode_literals
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
except ImportError:
|
||||
try:
|
||||
from ordereddict import OrderedDict
|
||||
except ImportError:
|
||||
OrderedDict = dict
|
||||
import gettext
|
||||
_ = gettext.gettext
|
||||
|
||||
|
@ -61,7 +68,7 @@ def getETreeBuilder(ElementTreeImplementation):
|
|||
else:
|
||||
namespace = None
|
||||
tag = node.tag
|
||||
attrs = {}
|
||||
attrs = OrderedDict()
|
||||
for name, value in list(node.attrib.items()):
|
||||
match = tag_regexp.match(name)
|
||||
if match:
|
||||
|
|
|
@ -8,6 +8,10 @@ except ImportError:
|
|||
import xml.etree.ElementTree as default_etree
|
||||
|
||||
|
||||
__all__ = ["default_etree", "MethodDispatcher", "isSurrogatePair",
|
||||
"surrogatePairToCodepoint", "moduleFactoryFactory"]
|
||||
|
||||
|
||||
class MethodDispatcher(dict):
|
||||
"""Dict with 2 special properties:
|
||||
|
||||
|
|
|
@ -90,10 +90,8 @@ def _bypass_ensure_directory(name, mode=0x1FF): # 0777
|
|||
_state_vars = {}
|
||||
|
||||
def _declare_state(vartype, **kw):
|
||||
g = globals()
|
||||
for name, val in kw.items():
|
||||
g[name] = val
|
||||
_state_vars[name] = vartype
|
||||
globals().update(kw)
|
||||
_state_vars.update(dict.fromkeys(kw, vartype))
|
||||
|
||||
def __getstate__():
|
||||
state = {}
|
||||
|
@ -429,6 +427,48 @@ class WorkingSet(object):
|
|||
for entry in entries:
|
||||
self.add_entry(entry)
|
||||
|
||||
@classmethod
|
||||
def _build_master(cls):
|
||||
"""
|
||||
Prepare the master working set.
|
||||
"""
|
||||
ws = cls()
|
||||
try:
|
||||
from __main__ import __requires__
|
||||
except ImportError:
|
||||
# The main program does not list any requirements
|
||||
return ws
|
||||
|
||||
# ensure the requirements are met
|
||||
try:
|
||||
ws.require(__requires__)
|
||||
except VersionConflict:
|
||||
return cls._build_from_requirements(__requires__)
|
||||
|
||||
return ws
|
||||
|
||||
@classmethod
|
||||
def _build_from_requirements(cls, req_spec):
|
||||
"""
|
||||
Build a working set from a requirement spec. Rewrites sys.path.
|
||||
"""
|
||||
# try it without defaults already on sys.path
|
||||
# by starting with an empty path
|
||||
ws = cls([])
|
||||
reqs = parse_requirements(req_spec)
|
||||
dists = ws.resolve(reqs, Environment())
|
||||
for dist in dists:
|
||||
ws.add(dist)
|
||||
|
||||
# add any missing entries from sys.path
|
||||
for entry in sys.path:
|
||||
if entry not in ws.entries:
|
||||
ws.add_entry(entry)
|
||||
|
||||
# then copy back to sys.path
|
||||
sys.path[:] = ws.entries
|
||||
return ws
|
||||
|
||||
def add_entry(self, entry):
|
||||
"""Add a path item to ``.entries``, finding any distributions on it
|
||||
|
||||
|
@ -504,7 +544,7 @@ class WorkingSet(object):
|
|||
seen[key]=1
|
||||
yield self.by_key[key]
|
||||
|
||||
def add(self, dist, entry=None, insert=True):
|
||||
def add(self, dist, entry=None, insert=True, replace=False):
|
||||
"""Add `dist` to working set, associated with `entry`
|
||||
|
||||
If `entry` is unspecified, it defaults to the ``.location`` of `dist`.
|
||||
|
@ -512,8 +552,9 @@ class WorkingSet(object):
|
|||
set's ``.entries`` (if it wasn't already present).
|
||||
|
||||
`dist` is only added to the working set if it's for a project that
|
||||
doesn't already have a distribution in the set. If it's added, any
|
||||
callbacks registered with the ``subscribe()`` method will be called.
|
||||
doesn't already have a distribution in the set, unless `replace=True`.
|
||||
If it's added, any callbacks registered with the ``subscribe()`` method
|
||||
will be called.
|
||||
"""
|
||||
if insert:
|
||||
dist.insert_on(self.entries, entry)
|
||||
|
@ -522,7 +563,7 @@ class WorkingSet(object):
|
|||
entry = dist.location
|
||||
keys = self.entry_keys.setdefault(entry,[])
|
||||
keys2 = self.entry_keys.setdefault(dist.location,[])
|
||||
if dist.key in self.by_key:
|
||||
if not replace and dist.key in self.by_key:
|
||||
return # ignore hidden distros
|
||||
|
||||
self.by_key[dist.key] = dist
|
||||
|
@ -532,7 +573,8 @@ class WorkingSet(object):
|
|||
keys2.append(dist.key)
|
||||
self._added_new(dist)
|
||||
|
||||
def resolve(self, requirements, env=None, installer=None):
|
||||
def resolve(self, requirements, env=None, installer=None,
|
||||
replace_conflicting=False):
|
||||
"""List all distributions needed to (recursively) meet `requirements`
|
||||
|
||||
`requirements` must be a sequence of ``Requirement`` objects. `env`,
|
||||
|
@ -542,6 +584,12 @@ class WorkingSet(object):
|
|||
will be invoked with each requirement that cannot be met by an
|
||||
already-installed distribution; it should return a ``Distribution`` or
|
||||
``None``.
|
||||
|
||||
Unless `replace_conflicting=True`, raises a VersionConflict exception if
|
||||
any requirements are found on the path that have the correct name but
|
||||
the wrong version. Otherwise, if an `installer` is supplied it will be
|
||||
invoked to obtain the correct version of the requirement and activate
|
||||
it.
|
||||
"""
|
||||
|
||||
requirements = list(requirements)[::-1] # set up the stack
|
||||
|
@ -558,10 +606,18 @@ class WorkingSet(object):
|
|||
if dist is None:
|
||||
# Find the best distribution and add it to the map
|
||||
dist = self.by_key.get(req.key)
|
||||
if dist is None:
|
||||
if dist is None or (dist not in req and replace_conflicting):
|
||||
ws = self
|
||||
if env is None:
|
||||
env = Environment(self.entries)
|
||||
dist = best[req.key] = env.best_match(req, self, installer)
|
||||
if dist is None:
|
||||
env = Environment(self.entries)
|
||||
else:
|
||||
# Use an empty environment and workingset to avoid
|
||||
# any further conflicts with the conflicting
|
||||
# distribution
|
||||
env = Environment([])
|
||||
ws = WorkingSet([])
|
||||
dist = best[req.key] = env.best_match(req, ws, installer)
|
||||
if dist is None:
|
||||
#msg = ("The '%s' distribution was not found on this "
|
||||
# "system, and is required by this application.")
|
||||
|
@ -1811,6 +1867,7 @@ def register_namespace_handler(importer_type, namespace_handler):
|
|||
|
||||
def _handle_ns(packageName, path_item):
|
||||
"""Ensure that named package includes a subpath of path_item (if needed)"""
|
||||
|
||||
importer = get_importer(path_item)
|
||||
if importer is None:
|
||||
return None
|
||||
|
@ -1825,12 +1882,14 @@ def _handle_ns(packageName, path_item):
|
|||
elif not hasattr(module,'__path__'):
|
||||
raise TypeError("Not a package:", packageName)
|
||||
handler = _find_adapter(_namespace_handlers, importer)
|
||||
subpath = handler(importer,path_item,packageName,module)
|
||||
subpath = handler(importer, path_item, packageName, module)
|
||||
if subpath is not None:
|
||||
path = module.__path__
|
||||
path.append(subpath)
|
||||
loader.load_module(packageName)
|
||||
module.__path__ = path
|
||||
for path_item in path:
|
||||
if path_item not in module.__path__:
|
||||
module.__path__.append(path_item)
|
||||
return subpath
|
||||
|
||||
def declare_namespace(packageName):
|
||||
|
@ -2252,7 +2311,9 @@ class Distribution(object):
|
|||
self.insert_on(path)
|
||||
if path is sys.path:
|
||||
fixup_namespace_packages(self.location)
|
||||
list(map(declare_namespace, self._get_metadata('namespace_packages.txt')))
|
||||
for pkg in self._get_metadata('namespace_packages.txt'):
|
||||
if pkg in sys.modules:
|
||||
declare_namespace(pkg)
|
||||
|
||||
def egg_name(self):
|
||||
"""Return what this distribution's standard .egg filename should be"""
|
||||
|
@ -2685,26 +2746,8 @@ def _initialize(g):
|
|||
_initialize(globals())
|
||||
|
||||
# Prepare the master working set and make the ``require()`` API available
|
||||
_declare_state('object', working_set = WorkingSet())
|
||||
try:
|
||||
# Does the main program list any requirements?
|
||||
from __main__ import __requires__
|
||||
except ImportError:
|
||||
pass # No: just use the default working set based on sys.path
|
||||
else:
|
||||
# Yes: ensure the requirements are met, by prefixing sys.path if necessary
|
||||
try:
|
||||
working_set.require(__requires__)
|
||||
except VersionConflict: # try it without defaults already on sys.path
|
||||
working_set = WorkingSet([]) # by starting with an empty path
|
||||
for dist in working_set.resolve(
|
||||
parse_requirements(__requires__), Environment()
|
||||
):
|
||||
working_set.add(dist)
|
||||
for entry in sys.path: # add any missing entries from sys.path
|
||||
if entry not in working_set.entries:
|
||||
working_set.add_entry(entry)
|
||||
sys.path[:] = working_set.entries # then copy back to sys.path
|
||||
working_set = WorkingSet._build_master()
|
||||
_declare_state('object', working_set=working_set)
|
||||
|
||||
require = working_set.require
|
||||
iter_entry_points = working_set.iter_entry_points
|
||||
|
|
|
@ -25,7 +25,7 @@ import sys
|
|||
import types
|
||||
|
||||
__author__ = "Benjamin Peterson <benjamin@python.org>"
|
||||
__version__ = "1.5.2"
|
||||
__version__ = "1.6.1"
|
||||
|
||||
|
||||
# Useful for very coarse version differentiation.
|
||||
|
@ -83,7 +83,11 @@ class _LazyDescr(object):
|
|||
self.name = name
|
||||
|
||||
def __get__(self, obj, tp):
|
||||
result = self._resolve()
|
||||
try:
|
||||
result = self._resolve()
|
||||
except ImportError:
|
||||
# See the nice big comment in MovedModule.__getattr__.
|
||||
raise AttributeError("%s could not be imported " % self.name)
|
||||
setattr(obj, self.name, result) # Invokes __set__.
|
||||
# This is a bit ugly, but it avoids running this again.
|
||||
delattr(obj.__class__, self.name)
|
||||
|
@ -105,15 +109,22 @@ class MovedModule(_LazyDescr):
|
|||
return _import_module(self.mod)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
# Hack around the Django autoreloader. The reloader tries to get
|
||||
# __file__ or __name__ of every module in sys.modules. This doesn't work
|
||||
# well if this MovedModule is for an module that is unavailable on this
|
||||
# machine (like winreg on Unix systems). Thus, we pretend __file__ and
|
||||
# __name__ don't exist if the module hasn't been loaded yet. See issues
|
||||
# #51 and #53.
|
||||
if attr in ("__file__", "__name__") and self.mod not in sys.modules:
|
||||
raise AttributeError
|
||||
_module = self._resolve()
|
||||
# It turns out many Python frameworks like to traverse sys.modules and
|
||||
# try to load various attributes. This causes problems if this is a
|
||||
# platform-specific module on the wrong platform, like _winreg on
|
||||
# Unixes. Therefore, we silently pretend unimportable modules do not
|
||||
# have any attributes. See issues #51, #53, #56, and #63 for the full
|
||||
# tales of woe.
|
||||
#
|
||||
# First, if possible, avoid loading the module just to look at __file__,
|
||||
# __name__, or __path__.
|
||||
if (attr in ("__file__", "__name__", "__path__") and
|
||||
self.mod not in sys.modules):
|
||||
raise AttributeError(attr)
|
||||
try:
|
||||
_module = self._resolve()
|
||||
except ImportError:
|
||||
raise AttributeError(attr)
|
||||
value = getattr(_module, attr)
|
||||
setattr(self, attr, value)
|
||||
return value
|
||||
|
@ -222,6 +233,7 @@ _moved_attributes = [
|
|||
MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
|
||||
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
|
||||
MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
|
||||
MovedModule("xmlrpc_server", "xmlrpclib", "xmlrpc.server"),
|
||||
MovedModule("winreg", "_winreg"),
|
||||
]
|
||||
for attr in _moved_attributes:
|
||||
|
@ -241,6 +253,7 @@ class Module_six_moves_urllib_parse(_LazyModule):
|
|||
|
||||
_urllib_parse_moved_attributes = [
|
||||
MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
|
||||
|
@ -254,6 +267,7 @@ _urllib_parse_moved_attributes = [
|
|||
MovedAttribute("unquote", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("urlencode", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splitquery", "urllib", "urllib.parse"),
|
||||
]
|
||||
for attr in _urllib_parse_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_parse, attr.name, attr)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
distlib==0.1.7
|
||||
html5lib==1.0b1
|
||||
six==1.5.2
|
||||
colorama==0.2.7
|
||||
distlib==0.1.8
|
||||
html5lib==1.0b3
|
||||
six==1.6.1
|
||||
colorama==0.3.1
|
||||
requests==2.2.1
|
||||
|
|
18
pip/wheel.py
18
pip/wheel.py
|
@ -134,10 +134,11 @@ def get_entrypoints(filename):
|
|||
|
||||
|
||||
def move_wheel_files(name, req, wheeldir, user=False, home=None, root=None,
|
||||
pycompile=True):
|
||||
pycompile=True, scheme=None):
|
||||
"""Install a wheel"""
|
||||
|
||||
scheme = distutils_scheme(name, user=user, home=home, root=root)
|
||||
if not scheme:
|
||||
scheme = distutils_scheme(name, user=user, home=home, root=root)
|
||||
|
||||
if root_is_purelib(name, wheeldir):
|
||||
lib_dir = scheme['purelib']
|
||||
|
@ -177,6 +178,7 @@ def move_wheel_files(name, req, wheeldir, user=False, home=None, root=None,
|
|||
|
||||
for dir, subdirs, files in os.walk(source):
|
||||
basedir = dir[len(source):].lstrip(os.path.sep)
|
||||
destdir = os.path.join(dest, basedir)
|
||||
if is_base and basedir.split(os.path.sep, 1)[0].endswith('.data'):
|
||||
continue
|
||||
for s in subdirs:
|
||||
|
@ -190,15 +192,21 @@ def move_wheel_files(name, req, wheeldir, user=False, home=None, root=None,
|
|||
and s.lower().startswith(req.project_name.replace('-', '_').lower())):
|
||||
assert not info_dir, 'Multiple .dist-info directories'
|
||||
info_dir.append(destsubdir)
|
||||
if not os.path.exists(destsubdir):
|
||||
os.makedirs(destsubdir)
|
||||
for f in files:
|
||||
# Skip unwanted files
|
||||
if filter and filter(f):
|
||||
continue
|
||||
srcfile = os.path.join(dir, f)
|
||||
destfile = os.path.join(dest, basedir, f)
|
||||
shutil.move(srcfile, destfile)
|
||||
# directory creation is lazy and after the file filtering above
|
||||
# to ensure we don't install empty dirs; empty dirs can't be
|
||||
# uninstalled.
|
||||
if not os.path.exists(destdir):
|
||||
os.makedirs(destdir)
|
||||
# use copy2 (not move) to be extra sure we're not moving
|
||||
# directories over; copy2 fails for directories. this would
|
||||
# fail tests (not during released/user execution)
|
||||
shutil.copy2(srcfile, destfile)
|
||||
changed = False
|
||||
if fixer:
|
||||
changed = fixer(destfile)
|
||||
|
|
2
setup.py
2
setup.py
|
@ -63,7 +63,7 @@ setup(name="pip",
|
|||
keywords='easy_install distutils setuptools egg virtualenv',
|
||||
author='The pip developers',
|
||||
author_email='python-virtualenv@groups.google.com',
|
||||
url='http://www.pip-installer.org',
|
||||
url='https://pip.pypa.io/',
|
||||
license='MIT',
|
||||
packages=find_packages(exclude=["contrib", "docs", "tests*"]),
|
||||
package_data={
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,8 @@
|
|||
include DESCRIPTION.rst
|
||||
|
||||
# Include the test suite (FIXME: does not work yet)
|
||||
# recursive-include tests *
|
||||
|
||||
# If using Python 2.6 or less, then have to include package data, even though
|
||||
# it's already declared in setup.py
|
||||
include sample/*.dat
|
|
@ -0,0 +1 @@
|
|||
some data
|
|
@ -0,0 +1,5 @@
|
|||
__version__ = '1.2.0'
|
||||
|
||||
def main():
|
||||
"""Entry point for the application script"""
|
||||
print("Call your main application code here")
|
|
@ -0,0 +1 @@
|
|||
some data
|
|
@ -0,0 +1,5 @@
|
|||
[bdist_wheel]
|
||||
# This flag says that the code is written to work on both Python 2 and Python
|
||||
# 3. If at all possible, it is good practice to do this. If you cannot, you
|
||||
# will need to generate wheels for each Python version that you support.
|
||||
universal=1
|
|
@ -0,0 +1,103 @@
|
|||
from setuptools import setup, find_packages
|
||||
import codecs
|
||||
import os
|
||||
import re
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
# Read the version number from a source file.
|
||||
# Why read it, and not import?
|
||||
# see https://groups.google.com/d/topic/pypa-dev/0PkjVpcxTzQ/discussion
|
||||
def find_version(*file_paths):
|
||||
# Open in Latin-1 so that we avoid encoding errors.
|
||||
# Use codecs.open for Python 2 compatibility
|
||||
with codecs.open(os.path.join(here, *file_paths), 'r', 'latin1') as f:
|
||||
version_file = f.read()
|
||||
|
||||
# The version line must have the form
|
||||
# __version__ = 'ver'
|
||||
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
|
||||
version_file, re.M)
|
||||
if version_match:
|
||||
return version_match.group(1)
|
||||
raise RuntimeError("Unable to find version string.")
|
||||
|
||||
|
||||
# Get the long description from the relevant file
|
||||
with codecs.open('DESCRIPTION.rst', encoding='utf-8') as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name="sample",
|
||||
version=find_version('sample', '__init__.py'),
|
||||
description="A sample Python project",
|
||||
long_description=long_description,
|
||||
|
||||
# The project URL.
|
||||
url='https://github.com/pypa/sampleproject',
|
||||
|
||||
# Author details
|
||||
author='The Python Packaging Authority',
|
||||
author_email='pypa-dev@googlegroups.com',
|
||||
|
||||
# Choose your license
|
||||
license='MIT',
|
||||
|
||||
classifiers=[
|
||||
# How mature is this project? Common values are
|
||||
# 3 - Alpha
|
||||
# 4 - Beta
|
||||
# 5 - Production/Stable
|
||||
'Development Status :: 3 - Alpha',
|
||||
|
||||
# Indicate who your project is intended for
|
||||
'Intended Audience :: Developers',
|
||||
'Topic :: Software Development :: Build Tools',
|
||||
|
||||
# Pick your license as you wish (should match "license" above)
|
||||
'License :: OSI Approved :: MIT License',
|
||||
|
||||
# Specify the Python versions you support here. In particular, ensure
|
||||
# that you indicate whether you support Python 2, Python 3 or both.
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.1',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
],
|
||||
|
||||
# What does your project relate to?
|
||||
keywords='sample setuptools development',
|
||||
|
||||
# You can just specify the packages manually here if your project is
|
||||
# simple. Or you can use find_packages.
|
||||
packages=find_packages(exclude=["contrib", "docs", "tests*"]),
|
||||
|
||||
# List run-time dependencies here. These will be installed by pip when your
|
||||
# project is installed.
|
||||
install_requires = ['peppercorn'],
|
||||
|
||||
# If there are data files included in your packages that need to be
|
||||
# installed, specify them here. If using Python 2.6 or less, then these
|
||||
# have to be included in MANIFEST.in as well.
|
||||
package_data={
|
||||
'sample': ['package_data.dat'],
|
||||
},
|
||||
|
||||
# Although 'package_data' is the preferred approach, in some case you may
|
||||
# need to place data files outside of your packages.
|
||||
# see http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files
|
||||
# In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
|
||||
data_files=[('my_data', ['data/data_file'])],
|
||||
|
||||
# To provide executable scripts, use entry points in preference to the
|
||||
# "scripts" keyword. Entry points provide cross-platform support and allow
|
||||
# pip to create the appropriate form of executable for the target platform.
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'sample=sample:main',
|
||||
],
|
||||
},
|
||||
)
|
|
@ -228,3 +228,14 @@ def test_uninstallpathset_non_local(mock_logger):
|
|||
uninstall_set = UninstallPathSet(test_dist)
|
||||
uninstall_set.remove() #with no files added to set; which is the case when trying to remove non-local dists
|
||||
mock_logger.notify.assert_any_call("Not uninstalling pip at %s, outside environment %s" % (nonlocal_path, sys.prefix)), mock_logger.notify.mock_calls
|
||||
|
||||
def test_uninstall_wheel(script, data):
|
||||
"""
|
||||
Test uninstalling a wheel
|
||||
"""
|
||||
package = data.packages.join("simple.dist-0.1-py2.py3-none-any.whl")
|
||||
result = script.pip('install', package, '--no-index')
|
||||
dist_info_folder = script.site_packages / 'simple.dist-0.1.dist-info'
|
||||
assert dist_info_folder in result.files_created
|
||||
result2 = script.pip('uninstall', 'simple.dist', '-y')
|
||||
assert_all_changes(result, result2, [])
|
||||
|
|
|
@ -9,7 +9,7 @@ from pip import wheel
|
|||
from pip.download import path_to_url as path_to_url_d
|
||||
from pip.locations import write_delete_marker_file
|
||||
from pip.status_codes import PREVIOUS_BUILD_DIR_ERROR
|
||||
from tests.lib import pyversion_nodot, path_to_url
|
||||
from tests.lib import pyversion, path_to_url
|
||||
|
||||
|
||||
def test_pip_wheel_fails_without_wheel(script, data):
|
||||
|
@ -26,7 +26,7 @@ def test_pip_wheel_success(script, data):
|
|||
"""
|
||||
script.pip('install', 'wheel')
|
||||
result = script.pip('wheel', '--no-index', '-f', data.find_links, 'simple==3.0')
|
||||
wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion_nodot
|
||||
wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion[0]
|
||||
wheel_file_path = script.scratch/'wheelhouse'/wheel_file_name
|
||||
assert wheel_file_path in result.files_created, result.stdout
|
||||
assert "Successfully built simple" in result.stdout, result.stdout
|
||||
|
@ -52,7 +52,7 @@ def test_pip_wheel_fail(script, data):
|
|||
"""
|
||||
script.pip('install', 'wheel')
|
||||
result = script.pip('wheel', '--no-index', '-f', data.find_links, 'wheelbroken==0.1')
|
||||
wheel_file_name = 'wheelbroken-0.1-py%s-none-any.whl' % pyversion_nodot
|
||||
wheel_file_name = 'wheelbroken-0.1-py%s-none-any.whl' % pyversion[0]
|
||||
wheel_file_path = script.scratch/'wheelhouse'/wheel_file_name
|
||||
assert wheel_file_path not in result.files_created, (wheel_file_path, result.files_created)
|
||||
assert "FakeError" in result.stdout, result.stdout
|
||||
|
@ -73,7 +73,7 @@ def test_pip_wheel_ignore_wheels_editables(script, data):
|
|||
simple
|
||||
""" % (local_wheel, local_editable)))
|
||||
result = script.pip('wheel', '--no-index', '-f', data.find_links, '-r', script.scratch_path / 'reqs.txt')
|
||||
wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion_nodot
|
||||
wheel_file_name = 'simple-3.0-py%s-none-any.whl' % pyversion[0]
|
||||
wheel_file_path = script.scratch/'wheelhouse'/wheel_file_name
|
||||
assert wheel_file_path in result.files_created, (wheel_file_path, result.files_created)
|
||||
assert "Successfully built simple" in result.stdout, result.stdout
|
||||
|
@ -102,9 +102,9 @@ def test_pip_wheel_source_deps(script, data):
|
|||
# 'requires_source' is a wheel that depends on the 'source' project
|
||||
script.pip('install', 'wheel')
|
||||
result = script.pip('wheel', '--use-wheel', '--no-index', '-f', data.find_links, 'requires_source')
|
||||
wheel_file_name = 'source-1.0-py%s-none-any.whl' % pyversion_nodot
|
||||
wheel_file_name = 'source-1.0-py%s-none-any.whl' % pyversion[0]
|
||||
wheel_file_path = script.scratch/'wheelhouse'/wheel_file_name
|
||||
assert wheel_file_path in result.files_created, result.stdout
|
||||
assert wheel_file_path in result.files_created, result.files_created
|
||||
assert "Successfully built source" in result.stdout, result.stdout
|
||||
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ class TestLocations:
|
|||
self.username = "example"
|
||||
self.patch()
|
||||
|
||||
def tearDown(self):
|
||||
def teardown(self):
|
||||
self.revert_patch()
|
||||
shutil.rmtree(self.tempdir, ignore_errors=True)
|
||||
|
||||
|
|
|
@ -2,14 +2,12 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
from mock import patch
|
||||
|
||||
import pkg_resources
|
||||
from mock import patch, Mock
|
||||
from pip._vendor import pkg_resources
|
||||
from pip import wheel
|
||||
from pip.exceptions import (
|
||||
InstallationError, InvalidWheelFilename, UnsupportedWheel,
|
||||
)
|
||||
from pip.index import PackageFinder
|
||||
from pip.exceptions import InvalidWheelFilename, UnsupportedWheel
|
||||
from pip.req import InstallRequirement
|
||||
from pip.util import unpack_file
|
||||
from tests.lib import assert_raises_regexp
|
||||
|
||||
|
@ -225,3 +223,56 @@ class TestPEP425Tags(object):
|
|||
with patch('pip.pep425tags.sysconfig.get_config_var', raises_ioerror):
|
||||
assert len(pip.pep425tags.get_supported())
|
||||
|
||||
class TestMoveWheelFiles(object):
|
||||
"""
|
||||
Tests for moving files from wheel src to scheme paths
|
||||
"""
|
||||
|
||||
def prep(self, data, tmpdir):
|
||||
self.name = 'sample'
|
||||
self.wheelpath = data.packages.join('sample-1.2.0-py2.py3-none-any.whl')
|
||||
self.req = pkg_resources.Requirement.parse('sample')
|
||||
self.src = os.path.join(tmpdir, 'src')
|
||||
self.dest = os.path.join(tmpdir, 'dest')
|
||||
unpack_file(self.wheelpath, self.src, None, None)
|
||||
self.scheme = {
|
||||
'scripts': os.path.join(self.dest, 'bin'),
|
||||
'purelib': os.path.join(self.dest, 'lib'),
|
||||
'data': os.path.join(self.dest, 'data'),
|
||||
}
|
||||
self.src_dist_info = os.path.join(
|
||||
self.src, 'sample-1.2.0.dist-info')
|
||||
self.dest_dist_info = os.path.join(
|
||||
self.scheme['purelib'], 'sample-1.2.0.dist-info')
|
||||
|
||||
def assert_installed(self):
|
||||
# lib
|
||||
assert os.path.isdir(
|
||||
os.path.join(self.scheme['purelib'], 'sample'))
|
||||
# dist-info
|
||||
metadata = os.path.join(self.dest_dist_info, 'METADATA')
|
||||
assert os.path.isfile(metadata)
|
||||
# data files
|
||||
data_file = os.path.join(self.scheme['data'], 'my_data', 'data_file')
|
||||
assert os.path.isfile(data_file)
|
||||
# package data
|
||||
pkg_data = os.path.join(self.scheme['purelib'], 'sample', 'package_data.dat')
|
||||
|
||||
def test_std_install(self, data, tmpdir):
|
||||
self.prep(data, tmpdir)
|
||||
wheel.move_wheel_files(self.name, self.req, self.src, scheme=self.scheme)
|
||||
self.assert_installed()
|
||||
|
||||
def test_dist_info_contains_empty_dir(self, data, tmpdir):
|
||||
"""
|
||||
Test that empty dirs are not installed
|
||||
"""
|
||||
# e.g. https://github.com/pypa/pip/issues/1632#issuecomment-38027275
|
||||
self.prep(data, tmpdir)
|
||||
src_empty_dir = os.path.join(self.src_dist_info, 'empty_dir', 'empty_dir')
|
||||
os.makedirs(src_empty_dir)
|
||||
assert os.path.isdir(src_empty_dir)
|
||||
wheel.move_wheel_files(self.name, self.req, self.src, scheme=self.scheme)
|
||||
self.assert_installed()
|
||||
assert not os.path.isdir(os.path.join(self.dest_dist_info, 'empty_dir'))
|
||||
|
||||
|
|
Loading…
Reference in New Issue