Merge tag 'upstream/6.2'

Upstream version 6.2
This commit is contained in:
aviau 2015-12-04 10:51:02 -05:00
commit 34ba2edbe9
69 changed files with 1313 additions and 419 deletions

56
CHANGES
View file

@ -3,6 +3,62 @@ Click Changelog
This contains all major version changes between Click releases.
Version 6.2
-----------
(bugfix release, released on November 27th 2015)
- Correct fix for hidden progress bars.
Version 6.1
-----------
(bugfix release, released on November 27th 2015)
- Resolved an issue with invisible progress bars no longer rendering.
- Disable chain commands with subcommands as they were inherently broken.
- Fix `MissingParameter` not working without parameters passed.
Version 6.0
-----------
(codename "pow pow", released on November 24th 2015)
- Optimized the progressbar rendering to not render when it did not
actually change.
- Explicitly disallow nargs=-1 with a set default.
- The context is now closed before it's popped from the stack.
- Added support for short aliases for the false flag on toggles.
- Click will now attempt to aid you with debugging locale errors
better by listing with the help of the OS what locales are
available.
- Click used to return byte strings on Python 2 in some unit-testing
situations. This has been fixed to correctly return unicode strings
now.
- For Windows users on Python 2, Click will now handle Unicode more
correctly handle Unicode coming in from the system. This also has
the disappointing side effect that filenames will now be always
unicode by default in the `Path` type which means that this can
introduce small bugs for code not aware of this.
- Added a `type` parameter to `Path` to force a specific string type
on the value.
- For users running Python on Windows the `echo`) and `prompt` functions
now work with full unicode functionality in the Python windows console
by emulating an output stream. This also applies to getting the
virtual output and input streams via `click.get_text_stream(...)`.
- Unittests now always force a certain virtual terminal width.
- Added support for allowing dashes to indicate standard streams to the
`Path` type.
- Multi commands in chain mode no longer propagate arguments left over
from parsing to the callbacks. It's also now disallowed through an
exception when optional arguments are attached to multi commands if chain
mode is enabled.
- Relaxed restriction that disallowed chained commands to have other
chained commands as child commands.
- Arguments with positive nargs can now have defaults implemented.
Previously this configuration would often result in slightly unexpected
values be returned.
Version 5.1
-----------

View file

@ -5,4 +5,7 @@ upload-docs:
$(MAKE) -C docs dirhtml
rsync -a docs/_build/dirhtml/* flow.srv.pocoo.org:/srv/websites/click.pocoo.org/static/
release:
python setup.py sdist bdist_wheel upload
.PHONY: upload-docs

View file

@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: click
Version: 5.1
Version: 6.2
Summary: A simple wrapper around optparse for powerful command line utilities.
Home-page: http://github.com/mitsuhiko/click
Author: Armin Ronacher

BIN
artwork/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: click
Version: 5.1
Version: 6.2
Summary: A simple wrapper around optparse for powerful command line utilities.
Home-page: http://github.com/mitsuhiko/click
Author: Armin Ronacher

View file

@ -5,12 +5,15 @@ Makefile
README
setup.cfg
setup.py
artwork/.DS_Store
artwork/logo.svg
click/__init__.py
click/_bashcomplete.py
click/_compat.py
click/_termui_impl.py
click/_textwrap.py
click/_unicodefun.py
click/_winconsole.py
click/core.py
click/decorators.py
click/exceptions.py
@ -51,12 +54,14 @@ docs/testing.rst
docs/upgrading.rst
docs/utils.rst
docs/why.rst
docs/wincmd.rst
docs/_static/click-small.png
docs/_static/click-small@2x.png
docs/_static/click.png
docs/_static/click@2x.png
docs/_templates/sidebarintro.html
docs/_templates/sidebarlogo.html
examples/.DS_Store
examples/README
examples/aliases/README
examples/aliases/aliases.ini
@ -72,36 +77,50 @@ examples/complex/complex/cli.py
examples/complex/complex/commands/__init__.py
examples/complex/complex/commands/cmd_init.py
examples/complex/complex/commands/cmd_status.py
examples/demo/demo.py
examples/imagepipe/.DS_Store
examples/imagepipe/.gitignore
examples/imagepipe/README
examples/imagepipe/example01.jpg
examples/imagepipe/example02.jpg
examples/imagepipe/imagepipe.py
examples/imagepipe/setup.py
examples/imagepipe/click_example_imagepipe.egg-info/PKG-INFO
examples/imagepipe/click_example_imagepipe.egg-info/SOURCES.txt
examples/imagepipe/click_example_imagepipe.egg-info/dependency_links.txt
examples/imagepipe/click_example_imagepipe.egg-info/entry_points.txt
examples/imagepipe/click_example_imagepipe.egg-info/requires.txt
examples/imagepipe/click_example_imagepipe.egg-info/top_level.txt
examples/inout/README
examples/inout/inout.py
examples/inout/setup.py
examples/naval/README
examples/naval/naval.py
examples/naval/setup.py
examples/plugins/BrokenPlugin/printer_bold.egg-info/PKG-INFO
examples/plugins/BrokenPlugin/printer_bold.egg-info/SOURCES.txt
examples/plugins/BrokenPlugin/printer_bold.egg-info/dependency_links.txt
examples/plugins/BrokenPlugin/printer_bold.egg-info/entry_points.txt
examples/plugins/BrokenPlugin/printer_bold.egg-info/top_level.txt
examples/plugins/printer.egg-info/PKG-INFO
examples/plugins/printer.egg-info/SOURCES.txt
examples/plugins/printer.egg-info/dependency_links.txt
examples/plugins/printer.egg-info/entry_points.txt
examples/plugins/printer.egg-info/top_level.txt
examples/naval/click_example_naval.egg-info/PKG-INFO
examples/naval/click_example_naval.egg-info/SOURCES.txt
examples/naval/click_example_naval.egg-info/dependency_links.txt
examples/naval/click_example_naval.egg-info/entry_points.txt
examples/naval/click_example_naval.egg-info/requires.txt
examples/naval/click_example_naval.egg-info/top_level.txt
examples/repo/README
examples/repo/repo.py
examples/repo/setup.py
examples/repo/click_example_repo.egg-info/PKG-INFO
examples/repo/click_example_repo.egg-info/SOURCES.txt
examples/repo/click_example_repo.egg-info/dependency_links.txt
examples/repo/click_example_repo.egg-info/entry_points.txt
examples/repo/click_example_repo.egg-info/requires.txt
examples/repo/click_example_repo.egg-info/top_level.txt
examples/termui/README
examples/termui/setup.py
examples/termui/termui.py
examples/termui/build/lib/termui.py
examples/termui/dist/click_example_termui-1.0-py3.4.egg
examples/termui/click_example_termui.egg-info/PKG-INFO
examples/termui/click_example_termui.egg-info/SOURCES.txt
examples/termui/click_example_termui.egg-info/dependency_links.txt
examples/termui/click_example_termui.egg-info/entry_points.txt
examples/termui/click_example_termui.egg-info/requires.txt
examples/termui/click_example_termui.egg-info/top_level.txt
examples/validation/README
examples/validation/setup.py
examples/validation/validation.py

View file

@ -32,7 +32,7 @@ from .types import ParamType, File, Path, Choice, IntRange, Tuple, \
# Utilities
from .utils import echo, get_binary_stream, get_text_stream, open_file, \
format_filename, get_app_dir
format_filename, get_app_dir, get_os_args
# Terminal functions
from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \
@ -41,7 +41,8 @@ from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \
# Exceptions
from .exceptions import ClickException, UsageError, BadParameter, \
FileError, Abort, NoSuchOption, BadOptionUsage, MissingParameter
FileError, Abort, NoSuchOption, BadOptionUsage, BadArgumentUsage, \
MissingParameter
# Formatting
from .formatting import HelpFormatter, wrap_text
@ -69,7 +70,7 @@ __all__ = [
# Utilities
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',
'format_filename', 'get_app_dir',
'format_filename', 'get_app_dir', 'get_os_args',
# Terminal functions
'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager',
@ -78,7 +79,8 @@ __all__ = [
# Exceptions
'ClickException', 'UsageError', 'BadParameter', 'FileError',
'Abort', 'NoSuchOption', 'BadOptionUsage', 'MissingParameter',
'Abort', 'NoSuchOption', 'BadOptionUsage', 'BadArgumentUsage',
'MissingParameter',
# Formatting
'HelpFormatter', 'wrap_text',
@ -93,4 +95,4 @@ __all__ = [
disable_unicode_literals_warning = False
__version__ = '5.1'
__version__ = '6.2'

View file

@ -5,8 +5,6 @@ import sys
import codecs
from weakref import WeakKeyDictionary
import click
PY2 = sys.version_info[0] == 2
WIN = sys.platform.startswith('win')
@ -16,39 +14,6 @@ DEFAULT_COLUMNS = 80
_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
def _find_unicode_literals_frame():
import __future__
frm = sys._getframe(1)
idx = 1
while frm is not None:
if frm.f_globals.get('__name__', '').startswith('click.'):
frm = frm.f_back
idx += 1
elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag:
return idx
else:
break
return 0
def _check_for_unicode_literals():
if not __debug__:
return
if not PY2 or click.disable_unicode_literals_warning:
return
bad_frame = _find_unicode_literals_frame()
if bad_frame <= 0:
return
from warnings import warn
warn(Warning('Click detected the use of the unicode_literals '
'__future__ import. This is heavily discouraged '
'because it can introduce subtle bugs in your '
'code. You should instead use explicit u"" literals '
'for your unicode strings. For more information see '
'http://click.pocoo.org/python3/'),
stacklevel=bad_frame)
def get_filesystem_encoding():
return sys.getfilesystemencoding() or sys.getdefaultencoding()
@ -192,6 +157,9 @@ if PY2:
# binary only, patch it back to the system, and then use a wrapper
# stream that converts newlines. It's not quite clear what's the
# correct option here.
#
# This code also lives in _winconsole for the fallback to the console
# emulation stream.
if WIN:
import msvcrt
def set_binary_mode(f):
@ -218,12 +186,21 @@ if PY2:
return set_binary_mode(sys.stderr)
def get_text_stdin(encoding=None, errors=None):
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
if rv is not None:
return rv
return _make_text_stream(sys.stdin, encoding, errors)
def get_text_stdout(encoding=None, errors=None):
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
if rv is not None:
return rv
return _make_text_stream(sys.stdout, encoding, errors)
def get_text_stderr(encoding=None, errors=None):
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
if rv is not None:
return rv
return _make_text_stream(sys.stderr, encoding, errors)
def filename_to_ui(value):
@ -393,12 +370,21 @@ else:
return writer
def get_text_stdin(encoding=None, errors=None):
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
if rv is not None:
return rv
return _force_correct_text_reader(sys.stdin, encoding, errors)
def get_text_stdout(encoding=None, errors=None):
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
if rv is not None:
return rv
return _force_correct_text_writer(sys.stdout, encoding, errors)
def get_text_stderr(encoding=None, errors=None):
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
if rv is not None:
return rv
return _force_correct_text_writer(sys.stderr, encoding, errors)
def filename_to_ui(value):
@ -472,7 +458,12 @@ def open_stream(filename, mode='r', encoding=None, errors='strict',
# Used in a destructor call, needs extra protection from interpreter cleanup.
_rename = os.rename
if hasattr(os, 'replace'):
_replace = os.replace
_can_replace = True
else:
_replace = os.rename
_can_replace = not WIN
class _AtomicFile(object):
@ -491,7 +482,12 @@ class _AtomicFile(object):
if self.closed:
return
self._f.close()
_rename(self._tmp_filename, self._real_filename)
if not _can_replace:
try:
os.remove(self._real_filename)
except OSError:
pass
_replace(self._tmp_filename, self._real_filename)
self.closed = True
def __getattr__(self, name):
@ -531,6 +527,21 @@ if WIN:
# Windows has a smaller terminal
DEFAULT_COLUMNS = 79
from ._winconsole import _get_windows_console_stream
def _get_argv_encoding():
import locale
return locale.getpreferredencoding()
if PY2:
def raw_input(prompt=''):
sys.stderr.flush()
if prompt:
stdout = _default_text_stdout()
stdout.write(prompt)
stdin = _default_text_stdin()
return stdin.readline().rstrip('\r\n')
try:
import colorama
except ImportError:
@ -573,6 +584,11 @@ if WIN:
win = colorama.win32.GetConsoleScreenBufferInfo(
colorama.win32.STDOUT).srWindow
return win.Right - win.Left, win.Bottom - win.Top
else:
def _get_argv_encoding():
return getattr(sys.stdin, 'encoding', None) or get_filesystem_encoding()
_get_windows_console_stream = lambda *x: None
def term_len(x):
@ -605,6 +621,8 @@ def _make_cached_stream_func(src_func, wrapper_func):
return func
_default_text_stdin = _make_cached_stream_func(
lambda: sys.stdin, get_text_stdin)
_default_text_stdout = _make_cached_stream_func(
lambda: sys.stdout, get_text_stdout)
_default_text_stderr = _make_cached_stream_func(

View file

@ -87,6 +87,7 @@ class ProgressBar(object):
self.entered = False
self.current_item = None
self.is_hidden = not isatty(self.file)
self._last_line = None
def __enter__(self):
self.entered = True
@ -128,7 +129,18 @@ class ProgressBar(object):
def format_eta(self):
if self.eta_known:
return time.strftime('%H:%M:%S', time.gmtime(self.eta + 1))
t = self.eta + 1
seconds = t % 60
t /= 60
minutes = t % 60
t /= 60
hours = t % 24
t /= 24
if t > 0:
days = t
return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds)
else:
return '%02d:%02d:%02d' % (hours, minutes, seconds)
return ''
def format_pos(self):
@ -179,37 +191,44 @@ class ProgressBar(object):
def render_progress(self):
from .termui import get_terminal_size
nl = False
if self.is_hidden:
echo(self.label, file=self.file, color=self.color)
buf = [self.label]
nl = True
else:
buf = []
# Update width in case the terminal has been resized
if self.autowidth:
old_width = self.width
self.width = 0
clutter_length = term_len(self.format_progress_line())
new_width = max(0, get_terminal_size()[0] - clutter_length)
if new_width < old_width:
buf.append(BEFORE_BAR)
buf.append(' ' * self.max_width)
self.max_width = new_width
self.width = new_width
clear_width = self.width
if self.max_width is not None:
clear_width = self.max_width
buf.append(BEFORE_BAR)
line = self.format_progress_line()
line_len = term_len(line)
if self.max_width is None or self.max_width < line_len:
self.max_width = line_len
buf.append(line)
buf.append(' ' * (clear_width - line_len))
line = ''.join(buf)
# Render the line only if it changed.
if line != self._last_line:
self._last_line = line
echo(line, file=self.file, color=self.color, nl=nl)
self.file.flush()
return
# Update width in case the terminal has been resized
if self.autowidth:
old_width = self.width
self.width = 0
clutter_length = term_len(self.format_progress_line())
new_width = max(0, get_terminal_size()[0] - clutter_length)
if new_width < old_width:
self.file.write(BEFORE_BAR)
self.file.write(' ' * self.max_width)
self.max_width = new_width
self.width = new_width
clear_width = self.width
if self.max_width is not None:
clear_width = self.max_width
self.file.write(BEFORE_BAR)
line = self.format_progress_line()
line_len = term_len(line)
if self.max_width is None or self.max_width < line_len:
self.max_width = line_len
# Use echo here so that we get colorama support.
echo(line, file=self.file, nl=False, color=self.color)
self.file.write(' ' * (clear_width - line_len))
self.file.flush()
def make_step(self, n_steps):
self.pos += n_steps

114
click/_unicodefun.py Normal file
View file

@ -0,0 +1,114 @@
import os
import sys
import codecs
from ._compat import PY2
# If someone wants to vendor click, we want to ensure the
# correct package is discovered. Ideally we could use a
# relative import here but unfortunately Python does not
# support that.
click = sys.modules[__name__.rsplit('.', 1)[0]]
def _find_unicode_literals_frame():
import __future__
frm = sys._getframe(1)
idx = 1
while frm is not None:
if frm.f_globals.get('__name__', '').startswith('click.'):
frm = frm.f_back
idx += 1
elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag:
return idx
else:
break
return 0
def _check_for_unicode_literals():
if not __debug__:
return
if not PY2 or click.disable_unicode_literals_warning:
return
bad_frame = _find_unicode_literals_frame()
if bad_frame <= 0:
return
from warnings import warn
warn(Warning('Click detected the use of the unicode_literals '
'__future__ import. This is heavily discouraged '
'because it can introduce subtle bugs in your '
'code. You should instead use explicit u"" literals '
'for your unicode strings. For more information see '
'http://click.pocoo.org/python3/'),
stacklevel=bad_frame)
def _verify_python3_env():
"""Ensures that the environment is good for unicode on Python 3."""
if PY2:
return
try:
import locale
fs_enc = codecs.lookup(locale.getpreferredencoding()).name
except Exception:
fs_enc = 'ascii'
if fs_enc != 'ascii':
return
extra = ''
if os.name == 'posix':
import subprocess
rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()[0]
good_locales = set()
has_c_utf8 = False
for line in rv.splitlines():
locale = line.strip()
if locale.lower().endswith(('.utf-8', '.utf8')):
good_locales.add(locale)
if locale.lower() in ('c.utf8', 'c.utf-8'):
has_c_utf8 = True
extra += '\n\n'
if not good_locales:
extra += (
'Additional information: on this system no suitable UTF-8\n'
'locales were discovered. This most likely requires resolving\n'
'by reconfiguring the locale system.'
)
elif has_c_utf8:
extra += (
'This system supports the C.UTF-8 locale which is recommended.\n'
'You might be able to resolve your issue by exporting the\n'
'following environment variables:\n\n'
' export LC_ALL=C.UTF-8\n'
' export LANG=C.UTF-8'
)
else:
extra += (
'This system lists a couple of UTF-8 supporting locales that\n'
'you can pick from. The following suitable locales where\n'
'discovered: %s'
) % ', '.join(sorted(good_locales))
bad_locale = None
for locale in os.environ.get('LC_ALL'), os.environ.get('LANG'):
if locale and locale.lower().endswith(('.utf-8', '.utf8')):
bad_locale = locale
if locale is not None:
break
if bad_locale is not None:
extra += (
'\n\nClick discovered that you exported a UTF-8 locale\n'
'but the locale system could not pick up from it because\n'
'it does not exist. The exported locale is "%s" but it\n'
'is not supported'
) % bad_locale
raise RuntimeError('Click will abort further execution because Python 3 '
'was configured to use ASCII as encoding for the '
'environment. Either run this under Python 2 or '
'consult http://click.pocoo.org/python3/ for '
'mitigation steps.' + extra)

264
click/_winconsole.py Normal file
View file

@ -0,0 +1,264 @@
# -*- coding: utf-8 -*-
# This module is based on the excellent work by Adam Bartoš who
# provided a lot of what went into the implementation here in
# the discussion to issue1602 in the Python bug tracker.
#
# There are some general differences in regards to how this works
# compared to the original patches as we do not need to patch
# the entire interpreter but just work in our little world of
# echo and prmopt.
import io
import os
import sys
import zlib
import time
import ctypes
import msvcrt
from click._compat import _NonClosingTextIOWrapper, text_type, PY2
from ctypes import byref, POINTER, pythonapi, c_int, c_char, c_char_p, \
c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE
from ctypes.wintypes import LPWSTR, LPCWSTR
c_ssize_p = POINTER(c_ssize_t)
PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
PyBuffer_Release = pythonapi.PyBuffer_Release
kernel32 = windll.kernel32
GetStdHandle = kernel32.GetStdHandle
ReadConsoleW = kernel32.ReadConsoleW
WriteConsoleW = kernel32.WriteConsoleW
GetLastError = kernel32.GetLastError
GetCommandLineW = WINFUNCTYPE(LPWSTR)(
('GetCommandLineW', windll.kernel32))
CommandLineToArgvW = WINFUNCTYPE(
POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
('CommandLineToArgvW', windll.shell32))
STDIN_HANDLE = GetStdHandle(-10)
STDOUT_HANDLE = GetStdHandle(-11)
STDERR_HANDLE = GetStdHandle(-12)
PyBUF_SIMPLE = 0
PyBUF_WRITABLE = 1
ERROR_SUCCESS = 0
ERROR_NOT_ENOUGH_MEMORY = 8
ERROR_OPERATION_ABORTED = 995
STDIN_FILENO = 0
STDOUT_FILENO = 1
STDERR_FILENO = 2
EOF = b'\x1a'
MAX_BYTES_WRITTEN = 32767
class Py_buffer(ctypes.Structure):
_fields_ = [
('buf', c_void_p),
('obj', py_object),
('len', c_ssize_t),
('itemsize', c_ssize_t),
('readonly', c_int),
('ndim', c_int),
('format', c_char_p),
('shape', c_ssize_p),
('strides', c_ssize_p),
('suboffsets', c_ssize_p),
('internal', c_void_p)
]
if PY2:
_fields_.insert(-1, ('smalltable', c_ssize_t * 2))
def get_buffer(obj, writable=False):
buf = Py_buffer()
flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE
PyObject_GetBuffer(py_object(obj), byref(buf), flags)
try:
buffer_type = c_char * buf.len
return buffer_type.from_address(buf.buf)
finally:
PyBuffer_Release(byref(buf))
class _WindowsConsoleRawIOBase(io.RawIOBase):
def __init__(self, handle):
self.handle = handle
def isatty(self):
io.RawIOBase.isatty(self)
return True
class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
def readable(self):
return True
def readinto(self, b):
bytes_to_be_read = len(b)
if not bytes_to_be_read:
return 0
elif bytes_to_be_read % 2:
raise ValueError('cannot read odd number of bytes from '
'UTF-16-LE encoded console')
buffer = get_buffer(b, writable=True)
code_units_to_be_read = bytes_to_be_read // 2
code_units_read = c_ulong()
rv = ReadConsoleW(self.handle, buffer, code_units_to_be_read,
byref(code_units_read), None)
if GetLastError() == ERROR_OPERATION_ABORTED:
# wait for KeyboardInterrupt
time.sleep(0.1)
if not rv:
raise OSError('Windows error: %s' % GetLastError())
if buffer[0] == EOF:
return 0
return 2 * code_units_read.value
class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
def writable(self):
return True
@staticmethod
def _get_error_message(errno):
if errno == ERROR_SUCCESS:
return 'ERROR_SUCCESS'
elif errno == ERROR_NOT_ENOUGH_MEMORY:
return 'ERROR_NOT_ENOUGH_MEMORY'
return 'Windows error %s' % errno
def write(self, b):
bytes_to_be_written = len(b)
buf = get_buffer(b)
code_units_to_be_written = min(bytes_to_be_written,
MAX_BYTES_WRITTEN) // 2
code_units_written = c_ulong()
WriteConsoleW(self.handle, buf, code_units_to_be_written,
byref(code_units_written), None)
bytes_written = 2 * code_units_written.value
if bytes_written == 0 and bytes_to_be_written > 0:
raise OSError(self._get_error_message(GetLastError()))
return bytes_written
class ConsoleStream(object):
def __init__(self, text_stream, byte_stream):
self._text_stream = text_stream
self.buffer = byte_stream
@property
def name(self):
return self.buffer.name
def write(self, x):
if isinstance(x, text_type):
return self._text_stream.write(x)
try:
self.flush()
except Exception:
pass
return self.buffer.write(x)
def writelines(self, lines):
for line in lines:
self.write(line)
def __getattr__(self, name):
return getattr(self._text_stream, name)
def isatty(self):
return self.buffer.isatty()
def __repr__(self):
return '<ConsoleStream name=%r encoding=%r>' % (
self.name,
self.encoding,
)
def _get_text_stdin(buffer_stream):
text_stream = _NonClosingTextIOWrapper(
io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
'utf-16-le', 'strict', line_buffering=True)
return ConsoleStream(text_stream, buffer_stream)
def _get_text_stdout(buffer_stream):
text_stream = _NonClosingTextIOWrapper(
_WindowsConsoleWriter(STDOUT_HANDLE),
'utf-16-le', 'strict', line_buffering=True)
return ConsoleStream(text_stream, buffer_stream)
def _get_text_stderr(buffer_stream):
text_stream = _NonClosingTextIOWrapper(
_WindowsConsoleWriter(STDERR_HANDLE),
'utf-16-le', 'strict', line_buffering=True)
return ConsoleStream(text_stream, buffer_stream)
if PY2:
def _hash_py_argv():
return zlib.crc32('\x00'.join(sys.argv[1:]))
_initial_argv_hash = _hash_py_argv()
def _get_windows_argv():
argc = c_int(0)
argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc))
argv = [argv_unicode[i] for i in range(0, argc.value)]
if not hasattr(sys, 'frozen'):
argv = argv[1:]
while len(argv) > 0:
arg = argv[0]
if not arg.startswith('-') or arg == '-':
break
argv = argv[1:]
if arg in ('-c', '-m'):
break
return argv[1:]
_stream_factories = {
0: _get_text_stdin,
1: _get_text_stdout,
2: _get_text_stderr,
}
def _get_windows_console_stream(f, encoding, errors):
if encoding in ('utf-16-le', None) \
and errors in ('strict', None) and \
hasattr(f, 'isatty') and f.isatty():
func = _stream_factories.get(f.fileno())
if func is not None:
if not PY2:
f = getattr(f, 'buffer')
if f is None:
return None
else:
# If we are on Python 2 we need to set the stream that we
# deal with to binary mode as otherwise the exercise if a
# bit moot. The same problems apply as for
# get_binary_stdin and friends from _compat.
msvcrt.setmode(f.fileno(), os.O_BINARY)
return func(f)

View file

@ -1,12 +1,11 @@
import os
import sys
import codecs
from contextlib import contextmanager
from itertools import repeat
from functools import update_wrapper
from .types import convert_type, IntRange, BOOL
from .utils import make_str, make_default_short_help, echo
from .utils import make_str, make_default_short_help, echo, get_os_args
from .exceptions import ClickException, UsageError, BadParameter, Abort, \
MissingParameter
from .termui import prompt, confirm
@ -14,7 +13,8 @@ from .formatting import HelpFormatter, join_options
from .parser import OptionParser, split_opt
from .globals import push_context, pop_context
from ._compat import PY2, isidentifier, iteritems, _check_for_unicode_literals
from ._compat import PY2, isidentifier, iteritems
from ._unicodefun import _check_for_unicode_literals, _verify_python3_env
_missing = object()
@ -37,6 +37,27 @@ def _bashcomplete(cmd, prog_name, complete_var=None):
sys.exit(1)
def _check_multicommand(base_command, cmd_name, cmd, register=False):
if not base_command.chain or not isinstance(cmd, MultiCommand):
return
if register:
hint = 'It is not possible to add multi commands as children to ' \
'another multi command that is in chain mode'
else:
hint = 'Found a multi command as subcommand to a multi command ' \
'that is in chain mode. This is not supported'
raise RuntimeError('%s. Command "%s" is set to chain and "%s" was '
'added as subcommand but it in itself is a '
'multi command. ("%s" is a %s within a chained '
'%s named "%s"). This restriction was supposed to '
'be lifted in 6.0 but the fix was flawed. This '
'will be fixed in Click 7.0' % (
hint, base_command.name, cmd_name,
cmd_name, cmd.__class__.__name__,
base_command.__class__.__name__,
base_command.name))
def batch(iterable, batch_size):
return list(zip(*repeat(iter(iterable), batch_size)))
@ -187,6 +208,11 @@ class Context(object):
self.params = {}
#: the leftover arguments.
self.args = []
#: protected arguments. These are arguments that are prepended
#: to `args` when certain parsing scenarios are encountered but
#: must be never propagated to another arguments. This is used
#: to implement nested parsing.
self.protected_args = []
if obj is None and parent is not None:
obj = parent.obj
#: the user object stored.
@ -299,10 +325,10 @@ class Context(object):
return self
def __exit__(self, exc_type, exc_value, tb):
pop_context()
self._depth -= 1
if self._depth == 0:
self.close()
pop_context()
@contextmanager
def scope(self, cleanup=True):
@ -646,25 +672,15 @@ class BaseCommand(object):
# sane at this point of reject further execution to avoid a
# broken script.
if not PY2:
try:
import locale
fs_enc = codecs.lookup(locale.getpreferredencoding()).name
except Exception:
fs_enc = 'ascii'
if fs_enc == 'ascii':
raise RuntimeError('Click will abort further execution '
'because Python 3 was configured to use '
'ASCII as encoding for the environment. '
'Either switch to Python 2 or consult '
'http://click.pocoo.org/python3/ '
'for mitigation steps.')
_verify_python3_env()
else:
_check_for_unicode_literals()
if args is None:
args = sys.argv[1:]
args = get_os_args()
else:
args = list(args)
if prog_name is None:
prog_name = make_str(os.path.basename(
sys.argv and sys.argv[0] or __file__))
@ -918,6 +934,12 @@ class MultiCommand(Command):
#: overridden with the :func:`resultcallback` decorator.
self.result_callback = result_callback
if self.chain:
for param in self.params:
if isinstance(param, Argument) and not param.required:
raise RuntimeError('Multi commands in chain mode cannot '
'have optional arguments.')
def collect_usage_pieces(self, ctx):
rv = Command.collect_usage_pieces(self, ctx)
rv.append(self.subcommand_metavar)
@ -986,7 +1008,15 @@ class MultiCommand(Command):
if not args and self.no_args_is_help and not ctx.resilient_parsing:
echo(ctx.get_help(), color=ctx.color)
ctx.exit()
return Command.parse_args(self, ctx, args)
rest = Command.parse_args(self, ctx, args)
if self.chain:
ctx.protected_args = rest
ctx.args = []
elif rest:
ctx.protected_args, ctx.args = rest[:1], rest[1:]
return ctx.args
def invoke(self, ctx):
def _process_result(value):
@ -995,7 +1025,7 @@ class MultiCommand(Command):
**ctx.params)
return value
if not ctx.args:
if not ctx.protected_args:
# If we are invoked without command the chain flag controls
# how this happens. If we are not in chain mode, the return
# value here is the return value of the command.
@ -1010,7 +1040,10 @@ class MultiCommand(Command):
return _process_result([])
ctx.fail('Missing command.')
args = ctx.args
# Fetch args back out
args = ctx.protected_args + ctx.args
ctx.args = []
ctx.protected_args = []
# If we're not in chain mode, we only allow the invocation of a
# single command but we also inform the current context about the
@ -1045,7 +1078,7 @@ class MultiCommand(Command):
allow_extra_args=True,
allow_interspersed_args=False)
contexts.append(sub_ctx)
args = sub_ctx.args
args, sub_ctx.args = sub_ctx.args, []
rv = []
for sub_ctx in contexts:
@ -1111,6 +1144,7 @@ class Group(MultiCommand):
name = name or cmd.name
if name is None:
raise TypeError('Command has no name.')
_check_multicommand(self, name, cmd, register=True)
self.commands[name] = cmd
def command(self, *args, **kwargs):
@ -1164,6 +1198,8 @@ class CommandCollection(MultiCommand):
for source in self.sources:
rv = source.get_command(ctx, cmd_name)
if rv is not None:
if self.chain:
_check_multicommand(self, cmd_name, rv)
return rv
def list_commands(self, ctx):
@ -1495,9 +1531,12 @@ class Option(Parameter):
if split_char in decl:
first, second = decl.split(split_char, 1)
first = first.rstrip()
possible_names.append(split_opt(first))
opts.append(first)
secondary_opts.append(second.lstrip())
if first:
possible_names.append(split_opt(first))
opts.append(first)
second = second.lstrip()
if second:
secondary_opts.append(second.lstrip())
else:
possible_names.append(split_opt(decl))
opts.append(decl)
@ -1652,6 +1691,9 @@ class Argument(Parameter):
else:
required = attrs.get('nargs', 1) > 0
Parameter.__init__(self, param_decls, required=required, **attrs)
if self.default is not None and self.nargs < 0:
raise TypeError('nargs=-1 in combination with a default value '
'is not supported.')
@property
def human_readable_name(self):

View file

@ -3,7 +3,8 @@ import inspect
from functools import update_wrapper
from ._compat import iteritems, _check_for_unicode_literals
from ._compat import iteritems
from ._unicodefun import _check_for_unicode_literals
from .utils import echo
from .globals import get_current_context

View file

@ -116,12 +116,13 @@ class MissingParameter(BadParameter):
param_type = self.param.param_type_name
msg = self.message
msg_extra = self.param.type.get_missing_message(self.param)
if msg_extra:
if msg:
msg += '. ' + msg_extra
else:
msg = msg_extra
if self.param is not None:
msg_extra = self.param.type.get_missing_message(self.param)
if msg_extra:
if msg:
msg += '. ' + msg_extra
else:
msg = msg_extra
return 'Missing %s%s%s%s' % (
param_type,
@ -165,7 +166,19 @@ class BadOptionUsage(UsageError):
.. versionadded:: 4.0
"""
def __init__(self, option_name, message, ctx=None):
def __init__(self, message, ctx=None):
UsageError.__init__(self, message, ctx)
class BadArgumentUsage(UsageError):
"""Raised if an argument is generally supplied but the use of the argument
was incorrect. This is for instance raised if the number of values
for an argument is not correct.
.. versionadded:: 6.0
"""
def __init__(self, message, ctx=None):
UsageError.__init__(self, message, ctx)

View file

@ -4,6 +4,10 @@ from .parser import split_opt
from ._compat import term_len
# Can force a width. This is used by the test system
FORCED_WIDTH = None
def measure_table(rows):
widths = {}
for row in rows:
@ -99,7 +103,9 @@ class HelpFormatter(object):
if max_width is None:
max_width = 80
if width is None:
width = max(min(get_terminal_size()[0], max_width) - 2, 50)
width = FORCED_WIDTH
if width is None:
width = max(min(get_terminal_size()[0], max_width) - 2, 50)
self.width = width
self.current_indent = 0
self.buffer = []

View file

@ -16,14 +16,66 @@
and might cause us issues.
"""
import re
from .exceptions import UsageError, NoSuchOption, BadOptionUsage
from .utils import unpack_args
from collections import deque
from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \
BadArgumentUsage
def _error_args(nargs, opt):
def _unpack_args(args, nargs_spec):
"""Given an iterable of arguments and an iterable of nargs specifications,
it returns a tuple with all the unpacked arguments at the first index
and all remaining arguments as the second.
The nargs specification is the number of arguments that should be consumed
or `-1` to indicate that this position should eat up all the remainders.
Missing items are filled with `None`.
"""
args = deque(args)
nargs_spec = deque(nargs_spec)
rv = []
spos = None
def _fetch(c):
try:
if spos is None:
return c.popleft()
else:
return c.pop()
except IndexError:
return None
while nargs_spec:
nargs = _fetch(nargs_spec)
if nargs == 1:
rv.append(_fetch(args))
elif nargs > 1:
x = [_fetch(args) for _ in range(nargs)]
# If we're reversed, we're pulling in the arguments in reverse,
# so we need to turn them around.
if spos is not None:
x.reverse()
rv.append(tuple(x))
elif nargs < 0:
if spos is not None:
raise TypeError('Cannot have two nargs < 0')
spos = len(rv)
rv.append(None)
# spos is the position of the wildcard (star). If it's not `None`,
# we fill it with the remainder.
if spos is not None:
rv[spos] = tuple(args)
args = []
rv[spos + 1:] = reversed(rv[spos + 1:])
return tuple(rv), list(args)
def _error_opt_args(nargs, opt):
if nargs == 1:
raise BadOptionUsage(opt, '%s option requires an argument' % opt)
raise BadOptionUsage(opt, '%s option requires %d arguments' % (opt, nargs))
raise BadOptionUsage('%s option requires an argument' % opt)
raise BadOptionUsage('%s option requires %d arguments' % (opt, nargs))
def split_opt(opt):
@ -116,6 +168,13 @@ class Argument(object):
self.obj = obj
def process(self, value, state):
if self.nargs > 1:
holes = sum(1 for x in value if x is None)
if holes == len(value):
value = None
elif holes != 0:
raise BadArgumentUsage('argument %s takes %d values'
% (self.dest, self.nargs))
state.opts[self.dest] = value
state.order.append(self.obj)
@ -213,8 +272,8 @@ class OptionParser(object):
return state.opts, state.largs, state.order
def _process_args_for_args(self, state):
pargs, args = unpack_args(state.largs + state.rargs,
[x.nargs for x in self._args])
pargs, args = _unpack_args(state.largs + state.rargs,
[x.nargs for x in self._args])
for idx, arg in enumerate(self._args):
arg.process(pargs[idx], state)
@ -275,7 +334,7 @@ class OptionParser(object):
nargs = option.nargs
if len(state.rargs) < nargs:
_error_args(nargs, opt)
_error_opt_args(nargs, opt)
elif nargs == 1:
value = state.rargs.pop(0)
else:
@ -283,7 +342,7 @@ class OptionParser(object):
del state.rargs[:nargs]
elif explicit_value is not None:
raise BadOptionUsage(opt, '%s option does not take a value' % opt)
raise BadOptionUsage('%s option does not take a value' % opt)
else:
value = None
@ -315,7 +374,7 @@ class OptionParser(object):
nargs = option.nargs
if len(state.rargs) < nargs:
_error_args(nargs, opt)
_error_opt_args(nargs, opt)
elif nargs == 1:
value = state.rargs.pop(0)
else:

View file

@ -41,6 +41,9 @@ def prompt(text, default=None, hide_input=False,
If the user aborts the input by sending a interrupt signal, this
function will catch it and raise a :exc:`Abort` exception.
.. versionadded:: 6.0
Added unicode support for cmd.exe on Windows.
.. versionadded:: 4.0
Added the `err` parameter.

View file

@ -157,6 +157,8 @@ class CliRunner(object):
old_stdin = sys.stdin
old_stdout = sys.stdout
old_stderr = sys.stderr
old_forced_width = clickpkg.formatting.FORCED_WIDTH
clickpkg.formatting.FORCED_WIDTH = 80
env = self.make_env(env)
@ -236,6 +238,7 @@ class CliRunner(object):
clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func
clickpkg.termui._getchar = old__getchar_func
clickpkg.utils.should_strip_ansi = old_should_strip_ansi
clickpkg.formatting.FORCED_WIDTH = old_forced_width
def invoke(self, cli, args=None, input=None, env=None,
catch_exceptions=True, color=False, **extra):

View file

@ -1,9 +1,8 @@
import os
import sys
import stat
from ._compat import open_stream, text_type, filename_to_ui, \
get_filesystem_encoding, get_streerror
get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2
from .exceptions import BadParameter
from .utils import safecall, LazyFile
@ -109,15 +108,16 @@ class StringParamType(ParamType):
def convert(self, value, param, ctx):
if isinstance(value, bytes):
enc = _get_argv_encoding()
try:
enc = getattr(sys.stdin, 'encoding', None)
if enc is not None:
value = value.decode(enc)
value = value.decode(enc)
except UnicodeError:
try:
value = value.decode(get_filesystem_encoding())
except UnicodeError:
value = value.decode('utf-8', 'replace')
fs_enc = get_filesystem_encoding()
if fs_enc != enc:
try:
value = value.decode(fs_enc)
except UnicodeError:
value = value.decode('utf-8', 'replace')
return value
return value
@ -168,7 +168,7 @@ class IntParamType(ParamType):
def convert(self, value, param, ctx):
try:
return int(value)
except ValueError:
except (ValueError, UnicodeError):
self.fail('%s is not a valid integer' % value, param, ctx)
def __repr__(self):
@ -237,7 +237,7 @@ class FloatParamType(ParamType):
def convert(self, value, param, ctx):
try:
return float(value)
except ValueError:
except (UnicodeError, ValueError):
self.fail('%s is not a valid floating point value' %
value, param, ctx)
@ -251,8 +251,10 @@ class UUIDParameterType(ParamType):
def convert(self, value, param, ctx):
import uuid
try:
if PY2 and isinstance(value, text_type):
value = value.encode('ascii')
return uuid.UUID(value)
except ValueError:
except (UnicodeError, ValueError):
self.fail('%s is not a valid UUID value' % value, param, ctx)
def __repr__(self):
@ -343,6 +345,9 @@ class Path(ParamType):
handle it returns just the filename. Secondly, it can perform various
basic checks about what the file or directory should be.
.. versionchanged:: 6.0
`allow_dash` was added.
:param exists: if set to true, the file or directory needs to exist for
this value to be valid. If this is not required and a
file does indeed not exist, then all further checks are
@ -354,17 +359,27 @@ class Path(ParamType):
:param resolve_path: if this is true, then the path is fully resolved
before the value is passed onwards. This means
that it's absolute and symlinks are resolved.
:param allow_dash: If this is set to `True`, a single dash to indicate
standard streams is permitted.
:param type: optionally a string type that should be used to
represent the path. The default is `None` which
means the return value will be either bytes or
unicode depending on what makes most sense given the
input data Click deals with.
"""
envvar_list_splitter = os.path.pathsep
def __init__(self, exists=False, file_okay=True, dir_okay=True,
writable=False, readable=True, resolve_path=False):
writable=False, readable=True, resolve_path=False,
allow_dash=False, path_type=None):
self.exists = exists
self.file_okay = file_okay
self.dir_okay = dir_okay
self.writable = writable
self.readable = readable
self.resolve_path = resolve_path
self.allow_dash = allow_dash
self.type = path_type
if self.file_okay and not self.dir_okay:
self.name = 'file'
@ -376,20 +391,32 @@ class Path(ParamType):
self.name = 'path'
self.path_type = 'Path'
def coerce_path_result(self, rv):
if self.type is not None and not isinstance(rv, self.type):
if self.type is text_type:
rv = rv.decode(get_filesystem_encoding())
else:
rv = rv.encode(get_filesystem_encoding())
return rv
def convert(self, value, param, ctx):
rv = value
if self.resolve_path:
rv = os.path.realpath(rv)
try:
st = os.stat(rv)
except OSError:
if not self.exists:
return rv
self.fail('%s "%s" does not exist.' % (
self.path_type,
filename_to_ui(value)
), param, ctx)
is_dash = self.file_okay and self.allow_dash and rv in (b'-', '-')
if not is_dash:
if self.resolve_path:
rv = os.path.realpath(rv)
try:
st = os.stat(rv)
except OSError:
if not self.exists:
return self.coerce_path_result(rv)
self.fail('%s "%s" does not exist.' % (
self.path_type,
filename_to_ui(value)
), param, ctx)
if not self.file_okay and stat.S_ISREG(st.st_mode):
self.fail('%s "%s" is a file.' % (
@ -412,7 +439,7 @@ class Path(ParamType):
filename_to_ui(value)
), param, ctx)
return rv
return self.coerce_path_result(rv)
class Tuple(CompositeParamType):

View file

@ -1,6 +1,5 @@
import os
import sys
from collections import deque
from .globals import resolve_color_default
@ -11,6 +10,9 @@ from ._compat import text_type, open_stream, get_filesystem_encoding, \
if not PY2:
from ._compat import _find_binary_writer
elif WIN:
from ._winconsole import _get_windows_argv, \
_hash_py_argv, _initial_argv_hash
echo_native_types = string_types + (bytes, bytearray)
@ -20,70 +22,6 @@ def _posixify(name):
return '-'.join(name.split()).lower()
def unpack_args(args, nargs_spec):
"""Given an iterable of arguments and an iterable of nargs specifications,
it returns a tuple with all the unpacked arguments at the first index
and all remaining arguments as the second.
The nargs specification is the number of arguments that should be consumed
or `-1` to indicate that this position should eat up all the remainders.
Missing items are filled with `None`.
Examples:
>>> unpack_args(range(6), [1, 2, 1, -1])
((0, (1, 2), 3, (4, 5)), [])
>>> unpack_args(range(6), [1, 2, 1])
((0, (1, 2), 3), [4, 5])
>>> unpack_args(range(6), [-1])
(((0, 1, 2, 3, 4, 5),), [])
>>> unpack_args(range(6), [1, 1])
((0, 1), [2, 3, 4, 5])
>>> unpack_args(range(6), [-1,1,1,1,1])
(((0, 1), 2, 3, 4, 5), [])
"""
args = deque(args)
nargs_spec = deque(nargs_spec)
rv = []
spos = None
def _fetch(c):
try:
if spos is None:
return c.popleft()
else:
return c.pop()
except IndexError:
return None
while nargs_spec:
nargs = _fetch(nargs_spec)
if nargs == 1:
rv.append(_fetch(args))
elif nargs > 1:
x = [_fetch(args) for _ in range(nargs)]
# If we're reversed, we're pulling in the arguments in reverse,
# so we need to turn them around.
if spos is not None:
x.reverse()
rv.append(tuple(x))
elif nargs < 0:
if spos is not None:
raise TypeError('Cannot have two nargs < 0')
spos = len(rv)
rv.append(None)
# spos is the position of the wildcard (star). If it's not `None`,
# we fill it with the remainder.
if spos is not None:
rv[spos] = tuple(args)
args = []
rv[spos + 1:] = reversed(rv[spos + 1:])
return tuple(rv), list(args)
def safecall(func):
"""Wraps a function so that it swallows exceptions."""
def wrapper(*args, **kwargs):
@ -234,7 +172,8 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
Primarily it means that you can print binary data as well as Unicode
data on both 2.x and 3.x to the given file in the most appropriate way
possible. This is a very carefree function as in that it will try its
best to not fail.
best to not fail. As of Click 6.0 this includes support for unicode
output on the Windows console.
In addition to that, if `colorama`_ is installed, the echo function will
also support clever handling of ANSI codes. Essentially it will then
@ -246,6 +185,12 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
.. _colorama: http://pypi.python.org/pypi/colorama
.. versionchanged:: 6.0
As of Click 6.0 the echo function will properly support unicode
output on the windows console. Not that click does not modify
the interpreter in any way which means that `sys.stdout` or the
print statement or function will still not provide unicode support.
.. versionchanged:: 2.0
Starting with version 2.0 of Click, the echo function will work
with colorama if it's installed.
@ -381,6 +326,27 @@ def open_file(filename, mode='r', encoding=None, errors='strict',
return f
def get_os_args():
"""This returns the argument part of sys.argv in the most appropriate
form for processing. What this means is that this return value is in
a format that works for Click to process but does not necessarily
correspond well to what's actually standard for the interpreter.
On most environments the return value is ``sys.argv[:1]`` unchanged.
However if you are on Windows and running Python 2 the return value
will actually be a list of unicode strings instead because the
default behavior on that platform otherwise will not be able to
carry all possible values that sys.argv can have.
.. versionadded:: 6.0
"""
# We can only extract the unicode argv if sys.argv has not been
# changed since the startup of the application.
if PY2 and WIN and _initial_argv_hash == _hash_py_argv():
return _get_windows_argv()
return sys.argv[1:]
def format_filename(filename, shorten=False):
"""Formats a filename for user display. The main purpose of this
function is to ensure that the filename can be displayed at all. This

View file

@ -153,6 +153,8 @@ Exceptions
.. autoexception:: BadOptionUsage
.. autoexception:: BadArgumentUsage
Formatting
----------

View file

@ -310,7 +310,8 @@ Now you can invoke it like this:
invoke(cli, prog_name='setup.py', args=['sdist', 'bdist_wheel'])
When using multi command chaining you can only have one command (the last)
use ``nargs=-1`` on an argument. Other than that there are no
use ``nargs=-1`` on an argument. It is also not possible to nest multi
commands below chained multicommands. Other than that there are no
restrictions on how they work. They can accept options and arguments as
normal.
@ -320,6 +321,11 @@ one command is invoked. This is necessary because the handling of
subcommands happens one after another so the exact subcommands that will
be handled are not yet available when the callback fires.
.. note::
It is currently not possible for chain commands to be nested. This
will be fixed in future versions of Click.
Multi Command Pipelines
-----------------------

View file

@ -75,6 +75,7 @@ usage patterns.
bashcomplete
exceptions
python3
wincmd
API Reference
-------------

View file

@ -201,6 +201,29 @@ can alternatively split the parameters through ``;`` instead:
if __name__ == '__main__':
log()
.. versionchanged:: 6.0
If you want to define an alias for the second option only, then you will
need to use leading whitespace to disambiguate the format string:
Example:
.. click:example::
import sys
@click.command()
@click.option('--shout/--no-shout', ' /-S', default=False)
def info(shout):
rv = sys.platform
if shout:
rv = rv.upper() + '!!!!111'
click.echo(rv)
.. click:run::
invoke(info, args=['--help'])
Feature Switches
----------------
@ -445,7 +468,7 @@ replaced with the :func:`confirmation_option` decorator:
.. click:example::
@click.command()
@click.confirmation_option(help='Are you sure you want to drop the db?')
@click.confirmation_option(prompt='Are you sure you want to drop the db?')
def dropdb():
click.echo('Dropped all tables!')

View file

@ -139,6 +139,12 @@ some newer Linux systems, you could also try ``C.UTF-8`` as the locale::
export LC_ALL=C.UTF-8
export LANG=C.UTF-8
On some systems it was reported that `UTF-8` has to be written as `UTF8`
and vice versa. To see which locales are supported you can invoke
``locale -a``::
locale -a
You need to do this before you invoke your Python script. If you are
curious about the reasons for this, you can join the discussions in the
Python 3 bug tracker:

View file

@ -30,6 +30,19 @@ suppressed by passing ``nl=False``::
click.echo(b'\xe2\x98\x83', nl=False)
Last but not least :func:`echo` uses click's intelligent internal output
streams to stdout and stderr which support unicode output on the Windows
console. This means for as long as you are using `click.echo` you can
output unicode character (there are some limitations on the default font
with regards to which characters can be displayed). This functionality is
new in Click 6.0.
.. versionadded:: 6.0
Click now emulates output streams on Windows to support unicode to the
Windows console through separate APIs. For more information see
`wincmd`_.
.. versionadded:: 3.0
Starting with Click 3.0 you can also easily print to standard error by
@ -266,6 +279,12 @@ Example::
stdin_text = click.get_text_stream('stdin')
stdout_binary = click.get_binary_stream('stdout')
.. versionadded:: 6.0
Click now emulates output streams on Windows to support unicode to the
Windows console through separate APIs. For more information see
`wincmd`_.
Intelligent File Opening
------------------------

73
docs/wincmd.rst Normal file
View file

@ -0,0 +1,73 @@
Windows Console Notes
=====================
.. versionadded:: 6.0
Until Click 6.0 there are various bugs and limitations with using Click on
a Windows console. Most notably the decoding of command line arguments
was performed with the wrong encoding on Python 2 and on all versions of
Python output of unicode characters was impossible. Starting with Click
6.0 we now emulate output streams on Windows to support unicode to the
Windows console through separate APIs and we perform different decoding of
parameters.
Here is a brief overview of how this works and what it means to you.
Unicode Arguments
-----------------
Click internally is generally based on the concept that any argument can
come in as either byte string or unicode string and conversion is
performed to the type expected value as late as possible. This has some
advantages as it allows us to accept the data in the most appropriate form
for the operating system and Python version.
For instance paths are left as bytes on Python 2 unless you explicitly
tell it otherwise.
This caused some problems on Windows where initially the wrong encoding
was used and garbage ended up in your input data. We not only fixed the
encoding part, but we also now extract unicode parameters from `sys.argv`.
This means that on Python 2 under Windows, the arguments processed will
*most likely* be of unicode nature and not bytes. This was something that
previously did not really happen unless you explicitly passed in unicode
parameters so your custom types need to be aware of this.
There is also another limitation with this: if `sys.argv` was modified
prior to invoking a click handler, we have to fall back to the regular
byte input in which case not all unicode values are available but only a
subset of the codepage used for parameters.
Unicode Output and Input
------------------------
Unicode output and input on Windows is implemented through the concept of
a dispatching text stream. What this means is that when click first needs
a text output (or input) stream on windows it goes through a few checks to
figure out of a windows console is connected or not. If no Windows
console is present then the text output stream is returned as such and the
encoding for that stream is set to ``utf-8`` like on all platforms.
However if a console is connected the stream will instead be emulated and
use the cmd.exe unicode APIs to output text information. In this case the
stream will also use ``utf-16-le`` as internal encoding. However there is
some hackery going on that the underlying raw IO buffer is still bypassing
the unicode APIs and byte output through an indirection is still possible.
This hackery is used on both Python 2 and Python 3 as neither version of
Python has native support for cmd.exe with unicode characters. There are
some limitations you need to be aware of:
* this unicode support is limited to ``click.echo``, ``click.prompt`` as
well as ``click.get_text_stream``.
* depending on if unicode values or byte strings are passed the control
flow goes completely different places internally which can have some
odd artifacts if data partially ends up being buffered. Click
attempts to protect against that by manually always flushing but if
you are mixing and matching different string types to ``stdout`` or
``stderr`` you will need to manually flush.
Another important thing to note is that the Windows console's default
fonts do not support a lot of characters which means that you are mostly
limited to international letters but no emojis or special characters.

BIN
examples/.DS_Store vendored Normal file

Binary file not shown.

47
examples/demo/demo.py Normal file
View file

@ -0,0 +1,47 @@
import click
class VcsContext(object):
def __init__(self):
self.debug = False
class FancyContext(object):
def __init__(self):
pass
pass_vcs = click.make_pass_decorator(VcsContext, ensure=True)
pass_fancy = click.make_pass_decorator(FancyContext, ensure=True)
@click.group()
@pass_vcs
def cli(obj):
"""Write stuff here."""
print obj
@click.group()
@pass_vcs
def commit(obj):
"""This commits some stuff."""
print obj
@click.command()
@pass_fancy
def fancy_thing(obj):
ctx = click.get_current_context()
print obj
print 'find vcs context', ctx.find_object(VcsContext)
cli.add_command(commit)
commit.add_command(fancy_thing)
if __name__ == '__main__':
cli()

BIN
examples/imagepipe/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,10 @@
Metadata-Version: 1.0
Name: click-example-imagepipe
Version: 1.0
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
Author-email: UNKNOWN
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN

View file

@ -0,0 +1,8 @@
README
imagepipe.py
click_example_imagepipe.egg-info/PKG-INFO
click_example_imagepipe.egg-info/SOURCES.txt
click_example_imagepipe.egg-info/dependency_links.txt
click_example_imagepipe.egg-info/entry_points.txt
click_example_imagepipe.egg-info/requires.txt
click_example_imagepipe.egg-info/top_level.txt

View file

@ -0,0 +1,4 @@
[console_scripts]
imagepipe=imagepipe:cli

View file

@ -0,0 +1 @@
Click

View file

@ -0,0 +1 @@
imagepipe

View file

@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: printer
Version: 0.1dev0
Name: click-example-naval
Version: 2.0
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN

View file

@ -0,0 +1,8 @@
README
naval.py
click_example_naval.egg-info/PKG-INFO
click_example_naval.egg-info/SOURCES.txt
click_example_naval.egg-info/dependency_links.txt
click_example_naval.egg-info/entry_points.txt
click_example_naval.egg-info/requires.txt
click_example_naval.egg-info/top_level.txt

View file

@ -0,0 +1,4 @@
[console_scripts]
naval=naval:cli

View file

@ -0,0 +1 @@
click

View file

@ -0,0 +1 @@
naval

View file

@ -1,8 +0,0 @@
README.rst
printer_bold/__init__.py
printer_bold/core.py
printer_bold.egg-info/PKG-INFO
printer_bold.egg-info/SOURCES.txt
printer_bold.egg-info/dependency_links.txt
printer_bold.egg-info/entry_points.txt
printer_bold.egg-info/top_level.txt

View file

@ -1,4 +0,0 @@
[printer.plugins]
bold=printer_bold.core:bolddddddddddd

View file

@ -1,8 +0,0 @@
README.rst
printer/__init__.py
printer/cli.py
printer.egg-info/PKG-INFO
printer.egg-info/SOURCES.txt
printer.egg-info/dependency_links.txt
printer.egg-info/entry_points.txt
printer.egg-info/top_level.txt

View file

@ -1,4 +0,0 @@
[console_scripts]
printer=printer.cli:cli

View file

@ -1 +0,0 @@
printer

View file

@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: printer-bold
Version: 0.1dev0
Name: click-example-repo
Version: 0.1
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN

View file

@ -0,0 +1,8 @@
README
repo.py
click_example_repo.egg-info/PKG-INFO
click_example_repo.egg-info/SOURCES.txt
click_example_repo.egg-info/dependency_links.txt
click_example_repo.egg-info/entry_points.txt
click_example_repo.egg-info/requires.txt
click_example_repo.egg-info/top_level.txt

View file

@ -0,0 +1,4 @@
[console_scripts]
repo=repo:cli

View file

@ -0,0 +1 @@
click

View file

@ -0,0 +1 @@
repo

View file

@ -1,145 +0,0 @@
# coding: utf-8
import click
import time
import random
try:
range_type = xrange
except NameError:
range_type = range
@click.group()
def cli():
"""This script showcases different terminal UI helpers in Click."""
pass
@cli.command()
def colordemo():
"""Demonstrates ANSI color support."""
for color in 'red', 'green', 'blue':
click.echo(click.style('I am colored %s' % color, fg=color))
click.echo(click.style('I am background colored %s' % color, bg=color))
@cli.command()
def pager():
"""Demonstrates using the pager."""
lines = []
for x in range_type(200):
lines.append('%s. Hello World!' % click.style(str(x), fg='green'))
click.echo_via_pager('\n'.join(lines))
@cli.command()
@click.option('--count', default=8000, type=click.IntRange(1, 100000),
help='The number of items to process.')
def progress(count):
"""Demonstrates the progress bar."""
items = range_type(count)
def process_slowly(item):
time.sleep(0.002 * random.random())
def filter(items):
for item in items:
if random.random() > 0.3:
yield item
with click.progressbar(items, label='Processing accounts',
fill_char=click.style('#', fg='green')) as bar:
for item in bar:
process_slowly(item)
def show_item(item):
if item is not None:
return 'Item #%d' % item
with click.progressbar(filter(items), label='Committing transaction',
fill_char=click.style('#', fg='yellow'),
item_show_func=show_item) as bar:
for item in bar:
process_slowly(item)
with click.progressbar(length=count, label='Counting',
bar_template='%(label)s %(bar)s | %(info)s',
fill_char=click.style(u'', fg='cyan'),
empty_char=' ') as bar:
for item in bar:
process_slowly(item)
with click.progressbar(length=count, width=0, show_percent=False,
show_eta=False,
fill_char=click.style('#', fg='magenta')) as bar:
for item in bar:
process_slowly(item)
@cli.command()
@click.argument('url')
def open(url):
"""Opens a file or URL In the default application."""
click.launch(url)
@cli.command()
@click.argument('url')
def locate(url):
"""Opens a file or URL In the default application."""
click.launch(url, locate=True)
@cli.command()
def edit():
"""Opens an editor with some text in it."""
MARKER = '# Everything below is ignored\n'
message = click.edit('\n\n' + MARKER)
if message is not None:
msg = message.split(MARKER, 1)[0].rstrip('\n')
if not msg:
click.echo('Empty message!')
else:
click.echo('Message:\n' + msg)
else:
click.echo('You did not enter anything!')
@cli.command()
def clear():
"""Clears the entire screen."""
click.clear()
@cli.command()
def pause():
"""Waits for the user to press a button."""
click.pause()
@cli.command()
def menu():
"""Shows a simple menu."""
menu = 'main'
while 1:
if menu == 'main':
click.echo('Main menu:')
click.echo(' d: debug menu')
click.echo(' q: quit')
char = click.getchar()
if char == 'd':
menu = 'debug'
elif char == 'q':
menu = 'quit'
else:
click.echo('Invalid input')
elif menu == 'debug':
click.echo('Debug menu')
click.echo(' b: back')
char = click.getchar()
if char == 'b':
menu = 'main'
else:
click.echo('Invalid input')
elif menu == 'quit':
return

View file

@ -0,0 +1,10 @@
Metadata-Version: 1.0
Name: click-example-termui
Version: 1.0
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
Author-email: UNKNOWN
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN

View file

@ -0,0 +1,8 @@
README
termui.py
click_example_termui.egg-info/PKG-INFO
click_example_termui.egg-info/SOURCES.txt
click_example_termui.egg-info/dependency_links.txt
click_example_termui.egg-info/entry_points.txt
click_example_termui.egg-info/requires.txt
click_example_termui.egg-info/top_level.txt

View file

@ -0,0 +1,4 @@
[console_scripts]
termui=termui:cli

View file

@ -0,0 +1,2 @@
click
colorama

View file

@ -0,0 +1 @@
termui

View file

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import click
from click._compat import PY2
def test_nargs_star(runner):
@ -18,6 +19,18 @@ def test_nargs_star(runner):
]
def test_nargs_default(runner):
try:
@click.command()
@click.argument('src', nargs=-1, default=42)
def copy(src):
pass
except TypeError as e:
assert 'nargs=-1' in str(e)
else:
assert False
def test_nargs_tup(runner):
@click.command()
@click.argument('name', nargs=1)
@ -106,7 +119,8 @@ def test_file_atomics(runner):
with runner.isolated_filesystem():
with open('foo.txt', 'wb') as f:
f.write(b'OLD\n')
result = runner.invoke(inout, ['foo.txt'], input='Hey!')
result = runner.invoke(inout, ['foo.txt'], input='Hey!',
catch_exceptions=False)
assert result.output == ''
assert result.exit_code == 0
with open('foo.txt', 'rb') as f:
@ -223,7 +237,7 @@ def test_nargs_star_ordering(runner):
result = runner.invoke(cmd, ['a', 'b', 'c'])
assert result.output.splitlines() == [
"('a',)",
PY2 and "(u'a',)" or "('a',)",
'b',
'c',
]
@ -240,7 +254,25 @@ def test_nargs_specified_plus_star_ordering(runner):
result = runner.invoke(cmd, ['a', 'b', 'c', 'd', 'e', 'f'])
assert result.output.splitlines() == [
"('a', 'b', 'c')",
PY2 and "(u'a', u'b', u'c')" or "('a', 'b', 'c')",
'd',
"('e', 'f')",
PY2 and "(u'e', u'f')" or "('e', 'f')",
]
def test_defaults_for_nargs(runner):
@click.command()
@click.argument('a', nargs=2, type=int, default=(1, 2))
def cmd(a):
x, y = a
click.echo(x + y)
result = runner.invoke(cmd, [])
assert result.output.strip() == '3'
result = runner.invoke(cmd, ['3', '4'])
assert result.output.strip() == '7'
result = runner.invoke(cmd, ['3'])
assert result.exception is not None
assert 'argument a takes 2 values' in result.output

View file

@ -1,4 +1,13 @@
import sys
import click
import pytest
def debug():
click.echo('%s=%s' % (
sys._getframe(1).f_code.co_name,
'|'.join(click.get_current_context().args),
))
def test_basic_chaining(runner):
@ -152,3 +161,92 @@ def test_pipeline(runner):
'FOO',
'BAR',
]
def test_args_and_chain(runner):
@click.group(chain=True)
def cli():
debug()
@cli.command()
def a():
debug()
@cli.command()
def b():
debug()
@cli.command()
def c():
debug()
result = runner.invoke(cli, ['a', 'b', 'c'])
assert not result.exception
assert result.output.splitlines() == [
'cli=',
'a=',
'b=',
'c=',
]
def test_multicommand_arg_behavior(runner):
with pytest.raises(RuntimeError):
@click.group(chain=True)
@click.argument('forbidden', required=False)
def bad_cli():
pass
with pytest.raises(RuntimeError):
@click.group(chain=True)
@click.argument('forbidden', nargs=-1)
def bad_cli2():
pass
@click.group(chain=True)
@click.argument('arg')
def cli(arg):
click.echo('cli:%s' % arg)
@cli.command()
def a():
click.echo('a')
result = runner.invoke(cli, ['foo', 'a'])
assert not result.exception
assert result.output.splitlines() == [
'cli:foo',
'a',
]
@pytest.mark.xfail
def test_multicommand_chaining(runner):
@click.group(chain=True)
def cli():
debug()
@cli.group()
def l1a():
debug()
@l1a.command()
def l2a():
debug()
@l1a.command()
def l2b():
debug()
@cli.command()
def l1b():
debug()
result = runner.invoke(cli, ['l1a', 'l2a', 'l1b'])
assert not result.exception
assert result.output.splitlines() == [
'cli=',
'l1a=',
'l2a=',
'l1b=',
]

View file

@ -184,3 +184,22 @@ def test_pass_obj(runner):
result = runner.invoke(cli, ['test'])
assert not result.exception
assert result.output == 'test\n'
def test_close_before_pop(runner):
called = []
@click.command()
@click.pass_context
def cli(ctx):
ctx.obj = 'test'
@ctx.call_on_close
def foo():
assert click.get_current_context().obj == 'test'
called.append(True)
click.echo('aha!')
result = runner.invoke(cli, [])
assert not result.exception
assert result.output == 'aha!\n'
assert called == [True]

View file

@ -2,6 +2,8 @@ import sys
import json
import subprocess
from click._compat import WIN
IMPORT_TEST = b'''\
try:
@ -30,9 +32,13 @@ click.echo(json.dumps(rv))
ALLOWED_IMPORTS = set([
'weakref', 'os', 'struct', 'collections', 'sys', 'contextlib',
'functools', 'stat', 're', 'codecs', 'inspect', 'itertools', 'io',
'threading'
'threading', 'colorama'
])
if WIN:
ALLOWED_IMPORTS.update(['ctypes', 'ctypes.wintypes', 'msvcrt', 'time',
'zlib'])
def test_light_imports():
c = subprocess.Popen([sys.executable, '-'], stdin=subprocess.PIPE,

View file

@ -4,6 +4,8 @@ import os
import click
import pytest
from click._compat import text_type
def test_prefixes(runner):
@click.command()
@ -144,32 +146,35 @@ def test_multiple_envvar(runner):
def test_multiple_default_help(runner):
@click.command()
@click.option("--arg1", multiple=True, default=('foo', 'bar'),
@click.option('--arg1', multiple=True, default=('foo', 'bar'),
show_default=True)
@click.option("--arg2", multiple=True, default=(1, 2), type=int,
@click.option('--arg2', multiple=True, default=(1, 2), type=int,
show_default=True)
def cmd(arg, arg2):
pass
result = runner.invoke(cmd, ['--help'])
assert not result.exception
assert "foo, bar" in result.output
assert "1, 2" in result.output
assert 'foo, bar' in result.output
assert '1, 2' in result.output
def test_multiple_default_type(runner):
@click.command()
@click.option("--arg1", multiple=True, default=('foo', 'bar'))
@click.option("--arg2", multiple=True, default=(1, "a"))
@click.option('--arg1', multiple=True, default=('foo', 'bar'))
@click.option('--arg2', multiple=True, default=(1, 'a'))
def cmd(arg1, arg2):
assert all(isinstance(e[0],str) for e in arg1)
assert all(isinstance(e[1],str) for e in arg1)
assert all(isinstance(e[0], text_type) for e in arg1)
assert all(isinstance(e[1], text_type) for e in arg1)
assert all(isinstance(e[0],int) for e in arg2)
assert all(isinstance(e[1],str) for e in arg2)
assert all(isinstance(e[0], int) for e in arg2)
assert all(isinstance(e[1], text_type) for e in arg2)
result = runner.invoke(cmd, "--arg1 a b --arg1 test 1 --arg2 2 two --arg2 4 four".split())
result = runner.invoke(cmd, '--arg1 a b --arg1 test 1 --arg2 2 '
'two --arg2 4 four'.split())
assert not result.exception
def test_nargs_envvar(runner):
@click.command()
@click.option('--arg', nargs=2)
@ -304,3 +309,29 @@ def test_option_custom_class(runner):
result = runner.invoke(cmd, ['--help'])
assert 'I am a help text' in result.output
assert 'you wont see me' not in result.output
def test_aliases_for_flags(runner):
@click.command()
@click.option('--warnings/--no-warnings', ' /-W', default=True)
def cli(warnings):
click.echo(warnings)
result = runner.invoke(cli, ['--warnings'])
assert result.output == 'True\n'
result = runner.invoke(cli, ['--no-warnings'])
assert result.output == 'False\n'
result = runner.invoke(cli, ['-W'])
assert result.output == 'False\n'
@click.command()
@click.option('--warnings/--no-warnings', '-w', default=True)
def cli_alt(warnings):
click.echo(warnings)
result = runner.invoke(cli_alt, ['--warnings'])
assert result.output == 'True\n'
result = runner.invoke(cli_alt, ['--no-warnings'])
assert result.output == 'False\n'
result = runner.invoke(cli_alt, ['-w'])
assert result.output == 'True\n'

View file

@ -5,7 +5,7 @@ import click
from click.testing import CliRunner
from click._compat import PY2
from click._compat import PY2, WIN
# Use the most reasonable io that users would use for the python version.
if PY2:
@ -117,6 +117,7 @@ def test_catch_exceptions():
assert result.exit_code == 1
@pytest.mark.skipif(WIN, reason='Test does not make sense on Windows.')
def test_with_color():
@click.command()
def cli():
@ -182,4 +183,3 @@ def test_exit_code_and_output_from_sys_exit():
result = runner.invoke(cli_no_error)
assert result.exit_code == 0
assert result.output == 'hello world\n'

View file

@ -6,6 +6,7 @@ import pytest
import click
import click.utils
import click._termui_impl
from click._compat import WIN, PY2
def test_echo(runner):
@ -15,7 +16,7 @@ def test_echo(runner):
click.echo(42, nl=False)
click.echo(b'a', nl=False)
click.echo('\x1b[31mx\x1b[39m', nl=False)
bytes = out.getvalue()
bytes = out.getvalue().replace(b'\r\n', b'\n')
assert bytes == b'\xe2\x98\x83\nDD\n42ax'
# If we are in Python 2, we expect that writing bytes into a string io
@ -82,8 +83,11 @@ def test_filename_formatting():
assert click.format_filename(b'/x/foo.txt') == '/x/foo.txt'
assert click.format_filename(u'/x/foo.txt') == '/x/foo.txt'
assert click.format_filename(u'/x/foo.txt', shorten=True) == 'foo.txt'
assert click.format_filename(b'/x/foo\xff.txt', shorten=True) \
== u'foo\ufffd.txt'
# filesystem encoding on windows permits this.
if not WIN:
assert click.format_filename(b'/x/foo\xff.txt', shorten=True) \
== u'foo\ufffd.txt'
def test_prompts(runner):
@ -126,6 +130,7 @@ def test_prompts(runner):
assert result.output == 'Foo [Y/n]: n\nno :(\n'
@pytest.mark.skipif(WIN, reason='Different behavior on windows.')
def test_prompts_abort(monkeypatch, capsys):
def f(_):
raise KeyboardInterrupt()
@ -141,6 +146,7 @@ def test_prompts_abort(monkeypatch, capsys):
assert out == 'Password: \nScrew you.\n'
@pytest.mark.skipif(WIN, reason='Different behavior on windows.')
@pytest.mark.parametrize('cat', ['cat', 'cat ', 'cat '])
def test_echo_via_pager(monkeypatch, capfd, cat):
monkeypatch.setitem(os.environ, 'PAGER', cat)
@ -150,6 +156,7 @@ def test_echo_via_pager(monkeypatch, capfd, cat):
assert out == 'haha\n'
@pytest.mark.skipif(WIN, reason='Test does not make sense on Windows.')
def test_echo_color_flag(monkeypatch, capfd):
isatty = True
monkeypatch.setattr(click._compat, 'isatty', lambda x: isatty)
@ -177,6 +184,7 @@ def test_echo_color_flag(monkeypatch, capfd):
assert out == text + '\n'
@pytest.mark.skipif(WIN, reason='Test too complex to make work windows.')
def test_echo_writing_to_standard_error(capfd, monkeypatch):
def emulate_input(text):
"""Emulate keyboard input."""
@ -255,8 +263,8 @@ def test_open_file(runner):
assert result.output == 'foobar\nmeep\n'
@pytest.mark.xfail(WIN and not PY2, reason='God knows ...')
def test_iter_keepopenfile(tmpdir):
expected = list(map(str, range(10)))
p = tmpdir.mkdir('testdir').join('testfile')
p.write(os.linesep.join(expected))
@ -265,8 +273,8 @@ def test_iter_keepopenfile(tmpdir):
assert e_line == a_line.strip()
@pytest.mark.xfail(WIN and not PY2, reason='God knows ...')
def test_iter_lazyfile(tmpdir):
expected = list(map(str, range(10)))
p = tmpdir.mkdir('testdir').join('testfile')
p.write(os.linesep.join(expected))