mirror of
https://github.com/kennethreitz/pipenv.git
synced 2026-06-05 22:50:18 +00:00
remove ALL the things!
This commit is contained in:
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
from pkgutil import extend_path
|
||||
__path__ = extend_path(__path__, __name__)
|
||||
@@ -1,11 +0,0 @@
|
||||
"""A backport of the get_terminal_size function from Python 3.3's shutil."""
|
||||
|
||||
__title__ = "backports.shutil_get_terminal_size"
|
||||
__version__ = "1.0.0"
|
||||
__license__ = "MIT"
|
||||
__author__ = "Christopher Rosell"
|
||||
__copyright__ = "Copyright 2014 Christopher Rosell"
|
||||
|
||||
__all__ = ["get_terminal_size"]
|
||||
|
||||
from .get_terminal_size import get_terminal_size
|
||||
@@ -1,101 +0,0 @@
|
||||
"""This is a backport of shutil.get_terminal_size from Python 3.3.
|
||||
|
||||
The original implementation is in C, but here we use the ctypes and
|
||||
fcntl modules to create a pure Python version of os.get_terminal_size.
|
||||
"""
|
||||
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
__all__ = ["get_terminal_size"]
|
||||
|
||||
|
||||
terminal_size = namedtuple("terminal_size", "columns lines")
|
||||
|
||||
try:
|
||||
from ctypes import windll, create_string_buffer
|
||||
|
||||
_handles = {
|
||||
0: windll.kernel32.GetStdHandle(-10),
|
||||
1: windll.kernel32.GetStdHandle(-11),
|
||||
2: windll.kernel32.GetStdHandle(-12),
|
||||
}
|
||||
|
||||
def _get_terminal_size(fd):
|
||||
columns = lines = 0
|
||||
|
||||
try:
|
||||
handle = _handles[fd]
|
||||
csbi = create_string_buffer(22)
|
||||
res = windll.kernel32.GetConsoleScreenBufferInfo(handle, csbi)
|
||||
if res:
|
||||
res = struct.unpack("hhhhHhhhhhh", csbi.raw)
|
||||
left, top, right, bottom = res[5:9]
|
||||
columns = right - left + 1
|
||||
lines = bottom - top + 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return terminal_size(columns, lines)
|
||||
|
||||
except ImportError:
|
||||
import fcntl
|
||||
import termios
|
||||
|
||||
def _get_terminal_size(fd):
|
||||
try:
|
||||
res = fcntl.ioctl(fd, termios.TIOCGWINSZ, b"\x00" * 4)
|
||||
lines, columns = struct.unpack("hh", res)
|
||||
except Exception:
|
||||
columns = lines = 0
|
||||
|
||||
return terminal_size(columns, lines)
|
||||
|
||||
|
||||
def get_terminal_size(fallback=(80, 24)):
|
||||
"""Get the size of the terminal window.
|
||||
|
||||
For each of the two dimensions, the environment variable, COLUMNS
|
||||
and LINES respectively, is checked. If the variable is defined and
|
||||
the value is a positive integer, it is used.
|
||||
|
||||
When COLUMNS or LINES is not defined, which is the common case,
|
||||
the terminal connected to sys.__stdout__ is queried
|
||||
by invoking os.get_terminal_size.
|
||||
|
||||
If the terminal size cannot be successfully queried, either because
|
||||
the system doesn't support querying, or because we are not
|
||||
connected to a terminal, the value given in fallback parameter
|
||||
is used. Fallback defaults to (80, 24) which is the default
|
||||
size used by many terminal emulators.
|
||||
|
||||
The value returned is a named tuple of type os.terminal_size.
|
||||
"""
|
||||
# Try the environment first
|
||||
try:
|
||||
columns = int(os.environ["COLUMNS"])
|
||||
except (KeyError, ValueError):
|
||||
columns = 0
|
||||
|
||||
try:
|
||||
lines = int(os.environ["LINES"])
|
||||
except (KeyError, ValueError):
|
||||
lines = 0
|
||||
|
||||
# Only query if necessary
|
||||
if columns <= 0 or lines <= 0:
|
||||
try:
|
||||
size = _get_terminal_size(sys.__stdout__.fileno())
|
||||
except (NameError, OSError):
|
||||
size = terminal_size(*fallback)
|
||||
|
||||
if columns <= 0:
|
||||
columns = size.columns
|
||||
if lines <= 0:
|
||||
lines = size.lines
|
||||
|
||||
return terminal_size(columns, lines)
|
||||
|
||||
Vendored
-73
@@ -1,73 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import itertools
|
||||
|
||||
|
||||
class Spinner(object):
|
||||
spinner_cycle = itertools.cycle(u'⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏')
|
||||
|
||||
def __init__(self, beep=False, force=False):
|
||||
self.beep = beep
|
||||
self.force = force
|
||||
self.stop_running = None
|
||||
self.spin_thread = None
|
||||
|
||||
def start(self):
|
||||
if sys.stdout.isatty() or self.force:
|
||||
self.stop_running = threading.Event()
|
||||
self.spin_thread = threading.Thread(target=self.init_spin)
|
||||
self.spin_thread.start()
|
||||
|
||||
def stop(self):
|
||||
if self.spin_thread:
|
||||
self.stop_running.set()
|
||||
self.spin_thread.join()
|
||||
|
||||
def init_spin(self):
|
||||
while not self.stop_running.is_set():
|
||||
next_val = next(self.spinner_cycle)
|
||||
if sys.version_info[0] == 2:
|
||||
next_val = next_val.encode('utf-8')
|
||||
sys.stdout.write(next_val)
|
||||
sys.stdout.flush()
|
||||
time.sleep(0.07)
|
||||
sys.stdout.write('\b')
|
||||
|
||||
def __enter__(self):
|
||||
self.start()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.stop()
|
||||
if self.beep:
|
||||
sys.stdout.write('\7')
|
||||
sys.stdout.flush()
|
||||
return False
|
||||
|
||||
|
||||
def spinner(beep=False, force=False):
|
||||
"""This function creates a context manager that is used to display a
|
||||
spinner on stdout as long as the context has not exited.
|
||||
|
||||
The spinner is created only if stdout is not redirected, or if the spinner
|
||||
is forced using the `force` parameter.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
beep : bool
|
||||
Beep when spinner finishes.
|
||||
force : bool
|
||||
Force creation of spinner even when stdout is redirected.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
with spinner():
|
||||
do_something()
|
||||
do_something_else()
|
||||
|
||||
"""
|
||||
return Spinner(beep, force)
|
||||
Vendored
-98
@@ -1,98 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
click
|
||||
~~~~~
|
||||
|
||||
Click is a simple Python module that wraps the stdlib's optparse to make
|
||||
writing command line scripts fun. Unlike other modules, it's based around
|
||||
a simple API that does not come with too much magic and is composable.
|
||||
|
||||
In case optparse ever gets removed from the stdlib, it will be shipped by
|
||||
this module.
|
||||
|
||||
:copyright: (c) 2014 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
# Core classes
|
||||
from .core import Context, BaseCommand, Command, MultiCommand, Group, \
|
||||
CommandCollection, Parameter, Option, Argument
|
||||
|
||||
# Globals
|
||||
from .globals import get_current_context
|
||||
|
||||
# Decorators
|
||||
from .decorators import pass_context, pass_obj, make_pass_decorator, \
|
||||
command, group, argument, option, confirmation_option, \
|
||||
password_option, version_option, help_option
|
||||
|
||||
# Types
|
||||
from .types import ParamType, File, Path, Choice, IntRange, Tuple, \
|
||||
STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED
|
||||
|
||||
# Utilities
|
||||
from .utils import echo, get_binary_stream, get_text_stream, open_file, \
|
||||
format_filename, get_app_dir, get_os_args
|
||||
|
||||
# Terminal functions
|
||||
from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \
|
||||
progressbar, clear, style, unstyle, secho, edit, launch, getchar, \
|
||||
pause
|
||||
|
||||
# Exceptions
|
||||
from .exceptions import ClickException, UsageError, BadParameter, \
|
||||
FileError, Abort, NoSuchOption, BadOptionUsage, BadArgumentUsage, \
|
||||
MissingParameter
|
||||
|
||||
# Formatting
|
||||
from .formatting import HelpFormatter, wrap_text
|
||||
|
||||
# Parsing
|
||||
from .parser import OptionParser
|
||||
|
||||
|
||||
__all__ = [
|
||||
# Core classes
|
||||
'Context', 'BaseCommand', 'Command', 'MultiCommand', 'Group',
|
||||
'CommandCollection', 'Parameter', 'Option', 'Argument',
|
||||
|
||||
# Globals
|
||||
'get_current_context',
|
||||
|
||||
# Decorators
|
||||
'pass_context', 'pass_obj', 'make_pass_decorator', 'command', 'group',
|
||||
'argument', 'option', 'confirmation_option', 'password_option',
|
||||
'version_option', 'help_option',
|
||||
|
||||
# Types
|
||||
'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', 'STRING',
|
||||
'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED',
|
||||
|
||||
# Utilities
|
||||
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',
|
||||
'format_filename', 'get_app_dir', 'get_os_args',
|
||||
|
||||
# Terminal functions
|
||||
'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager',
|
||||
'progressbar', 'clear', 'style', 'unstyle', 'secho', 'edit', 'launch',
|
||||
'getchar', 'pause',
|
||||
|
||||
# Exceptions
|
||||
'ClickException', 'UsageError', 'BadParameter', 'FileError',
|
||||
'Abort', 'NoSuchOption', 'BadOptionUsage', 'BadArgumentUsage',
|
||||
'MissingParameter',
|
||||
|
||||
# Formatting
|
||||
'HelpFormatter', 'wrap_text',
|
||||
|
||||
# Parsing
|
||||
'OptionParser',
|
||||
]
|
||||
|
||||
|
||||
# Controls if click should emit the warning about the use of unicode
|
||||
# literals.
|
||||
disable_unicode_literals_warning = False
|
||||
|
||||
|
||||
__version__ = '6.7'
|
||||
Vendored
-83
@@ -1,83 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
from .utils import echo
|
||||
from .parser import split_arg_string
|
||||
from .core import MultiCommand, Option
|
||||
|
||||
|
||||
COMPLETION_SCRIPT = '''
|
||||
%(complete_func)s() {
|
||||
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||
COMP_CWORD=$COMP_CWORD \\
|
||||
%(autocomplete_var)s=complete $1 ) )
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F %(complete_func)s -o default %(script_names)s
|
||||
'''
|
||||
|
||||
_invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]')
|
||||
|
||||
|
||||
def get_completion_script(prog_name, complete_var):
|
||||
cf_name = _invalid_ident_char_re.sub('', prog_name.replace('-', '_'))
|
||||
return (COMPLETION_SCRIPT % {
|
||||
'complete_func': '_%s_completion' % cf_name,
|
||||
'script_names': prog_name,
|
||||
'autocomplete_var': complete_var,
|
||||
}).strip() + ';'
|
||||
|
||||
|
||||
def resolve_ctx(cli, prog_name, args):
|
||||
ctx = cli.make_context(prog_name, args, resilient_parsing=True)
|
||||
while ctx.protected_args + ctx.args and isinstance(ctx.command, MultiCommand):
|
||||
a = ctx.protected_args + ctx.args
|
||||
cmd = ctx.command.get_command(ctx, a[0])
|
||||
if cmd is None:
|
||||
return None
|
||||
ctx = cmd.make_context(a[0], a[1:], parent=ctx, resilient_parsing=True)
|
||||
return ctx
|
||||
|
||||
|
||||
def get_choices(cli, prog_name, args, incomplete):
|
||||
ctx = resolve_ctx(cli, prog_name, args)
|
||||
if ctx is None:
|
||||
return
|
||||
|
||||
choices = []
|
||||
if incomplete and not incomplete[:1].isalnum():
|
||||
for param in ctx.command.params:
|
||||
if not isinstance(param, Option):
|
||||
continue
|
||||
choices.extend(param.opts)
|
||||
choices.extend(param.secondary_opts)
|
||||
elif isinstance(ctx.command, MultiCommand):
|
||||
choices.extend(ctx.command.list_commands(ctx))
|
||||
|
||||
for item in choices:
|
||||
if item.startswith(incomplete):
|
||||
yield item
|
||||
|
||||
|
||||
def do_complete(cli, prog_name):
|
||||
cwords = split_arg_string(os.environ['COMP_WORDS'])
|
||||
cword = int(os.environ['COMP_CWORD'])
|
||||
args = cwords[1:cword]
|
||||
try:
|
||||
incomplete = cwords[cword]
|
||||
except IndexError:
|
||||
incomplete = ''
|
||||
|
||||
for item in get_choices(cli, prog_name, args, incomplete):
|
||||
echo(item)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def bashcomplete(cli, prog_name, complete_var, complete_instr):
|
||||
if complete_instr == 'source':
|
||||
echo(get_completion_script(prog_name, complete_var))
|
||||
return True
|
||||
elif complete_instr == 'complete':
|
||||
return do_complete(cli, prog_name)
|
||||
return False
|
||||
Vendored
-648
@@ -1,648 +0,0 @@
|
||||
import re
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import codecs
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
WIN = sys.platform.startswith('win')
|
||||
DEFAULT_COLUMNS = 80
|
||||
|
||||
|
||||
_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
|
||||
|
||||
|
||||
def get_filesystem_encoding():
|
||||
return sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
|
||||
|
||||
def _make_text_stream(stream, encoding, errors):
|
||||
if encoding is None:
|
||||
encoding = get_best_encoding(stream)
|
||||
if errors is None:
|
||||
errors = 'replace'
|
||||
return _NonClosingTextIOWrapper(stream, encoding, errors,
|
||||
line_buffering=True)
|
||||
|
||||
|
||||
def is_ascii_encoding(encoding):
|
||||
"""Checks if a given encoding is ascii."""
|
||||
try:
|
||||
return codecs.lookup(encoding).name == 'ascii'
|
||||
except LookupError:
|
||||
return False
|
||||
|
||||
|
||||
def get_best_encoding(stream):
|
||||
"""Returns the default stream encoding if not found."""
|
||||
rv = getattr(stream, 'encoding', None) or sys.getdefaultencoding()
|
||||
if is_ascii_encoding(rv):
|
||||
return 'utf-8'
|
||||
return rv
|
||||
|
||||
|
||||
class _NonClosingTextIOWrapper(io.TextIOWrapper):
|
||||
|
||||
def __init__(self, stream, encoding, errors, **extra):
|
||||
self._stream = stream = _FixupStream(stream)
|
||||
io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra)
|
||||
|
||||
# The io module is a place where the Python 3 text behavior
|
||||
# was forced upon Python 2, so we need to unbreak
|
||||
# it to look like Python 2.
|
||||
if PY2:
|
||||
def write(self, x):
|
||||
if isinstance(x, str) or is_bytes(x):
|
||||
try:
|
||||
self.flush()
|
||||
except Exception:
|
||||
pass
|
||||
return self.buffer.write(str(x))
|
||||
return io.TextIOWrapper.write(self, x)
|
||||
|
||||
def writelines(self, lines):
|
||||
for line in lines:
|
||||
self.write(line)
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.detach()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def isatty(self):
|
||||
# https://bitbucket.org/pypy/pypy/issue/1803
|
||||
return self._stream.isatty()
|
||||
|
||||
|
||||
class _FixupStream(object):
|
||||
"""The new io interface needs more from streams than streams
|
||||
traditionally implement. As such, this fix-up code is necessary in
|
||||
some circumstances.
|
||||
"""
|
||||
|
||||
def __init__(self, stream):
|
||||
self._stream = stream
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._stream, name)
|
||||
|
||||
def read1(self, size):
|
||||
f = getattr(self._stream, 'read1', None)
|
||||
if f is not None:
|
||||
return f(size)
|
||||
# We only dispatch to readline instead of read in Python 2 as we
|
||||
# do not want cause problems with the different implementation
|
||||
# of line buffering.
|
||||
if PY2:
|
||||
return self._stream.readline(size)
|
||||
return self._stream.read(size)
|
||||
|
||||
def readable(self):
|
||||
x = getattr(self._stream, 'readable', None)
|
||||
if x is not None:
|
||||
return x()
|
||||
try:
|
||||
self._stream.read(0)
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def writable(self):
|
||||
x = getattr(self._stream, 'writable', None)
|
||||
if x is not None:
|
||||
return x()
|
||||
try:
|
||||
self._stream.write('')
|
||||
except Exception:
|
||||
try:
|
||||
self._stream.write(b'')
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def seekable(self):
|
||||
x = getattr(self._stream, 'seekable', None)
|
||||
if x is not None:
|
||||
return x()
|
||||
try:
|
||||
self._stream.seek(self._stream.tell())
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if PY2:
|
||||
text_type = unicode
|
||||
bytes = str
|
||||
raw_input = raw_input
|
||||
string_types = (str, unicode)
|
||||
iteritems = lambda x: x.iteritems()
|
||||
range_type = xrange
|
||||
|
||||
def is_bytes(x):
|
||||
return isinstance(x, (buffer, bytearray))
|
||||
|
||||
_identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$')
|
||||
|
||||
# For Windows, we need to force stdout/stdin/stderr to binary if it's
|
||||
# fetched for that. This obviously is not the most correct way to do
|
||||
# it as it changes global state. Unfortunately, there does not seem to
|
||||
# be a clear better way to do it as just reopening the file in binary
|
||||
# mode does not change anything.
|
||||
#
|
||||
# An option would be to do what Python 3 does and to open the file as
|
||||
# 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.
|
||||
#
|
||||
# There are also Windows environments where the `msvcrt` module is not
|
||||
# available (which is why we use try-catch instead of the WIN variable
|
||||
# here), such as the Google App Engine development server on Windows. In
|
||||
# those cases there is just nothing we can do.
|
||||
try:
|
||||
import msvcrt
|
||||
except ImportError:
|
||||
set_binary_mode = lambda x: x
|
||||
else:
|
||||
def set_binary_mode(f):
|
||||
try:
|
||||
fileno = f.fileno()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
msvcrt.setmode(fileno, os.O_BINARY)
|
||||
return f
|
||||
|
||||
def isidentifier(x):
|
||||
return _identifier_re.search(x) is not None
|
||||
|
||||
def get_binary_stdin():
|
||||
return set_binary_mode(sys.stdin)
|
||||
|
||||
def get_binary_stdout():
|
||||
return set_binary_mode(sys.stdout)
|
||||
|
||||
def get_binary_stderr():
|
||||
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):
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode(get_filesystem_encoding(), 'replace')
|
||||
return value
|
||||
else:
|
||||
import io
|
||||
text_type = str
|
||||
raw_input = input
|
||||
string_types = (str,)
|
||||
range_type = range
|
||||
isidentifier = lambda x: x.isidentifier()
|
||||
iteritems = lambda x: iter(x.items())
|
||||
|
||||
def is_bytes(x):
|
||||
return isinstance(x, (bytes, memoryview, bytearray))
|
||||
|
||||
def _is_binary_reader(stream, default=False):
|
||||
try:
|
||||
return isinstance(stream.read(0), bytes)
|
||||
except Exception:
|
||||
return default
|
||||
# This happens in some cases where the stream was already
|
||||
# closed. In this case, we assume the default.
|
||||
|
||||
def _is_binary_writer(stream, default=False):
|
||||
try:
|
||||
stream.write(b'')
|
||||
except Exception:
|
||||
try:
|
||||
stream.write('')
|
||||
return False
|
||||
except Exception:
|
||||
pass
|
||||
return default
|
||||
return True
|
||||
|
||||
def _find_binary_reader(stream):
|
||||
# We need to figure out if the given stream is already binary.
|
||||
# This can happen because the official docs recommend detaching
|
||||
# the streams to get binary streams. Some code might do this, so
|
||||
# we need to deal with this case explicitly.
|
||||
if _is_binary_reader(stream, False):
|
||||
return stream
|
||||
|
||||
buf = getattr(stream, 'buffer', None)
|
||||
|
||||
# Same situation here; this time we assume that the buffer is
|
||||
# actually binary in case it's closed.
|
||||
if buf is not None and _is_binary_reader(buf, True):
|
||||
return buf
|
||||
|
||||
def _find_binary_writer(stream):
|
||||
# We need to figure out if the given stream is already binary.
|
||||
# This can happen because the official docs recommend detatching
|
||||
# the streams to get binary streams. Some code might do this, so
|
||||
# we need to deal with this case explicitly.
|
||||
if _is_binary_writer(stream, False):
|
||||
return stream
|
||||
|
||||
buf = getattr(stream, 'buffer', None)
|
||||
|
||||
# Same situation here; this time we assume that the buffer is
|
||||
# actually binary in case it's closed.
|
||||
if buf is not None and _is_binary_writer(buf, True):
|
||||
return buf
|
||||
|
||||
def _stream_is_misconfigured(stream):
|
||||
"""A stream is misconfigured if its encoding is ASCII."""
|
||||
# If the stream does not have an encoding set, we assume it's set
|
||||
# to ASCII. This appears to happen in certain unittest
|
||||
# environments. It's not quite clear what the correct behavior is
|
||||
# but this at least will force Click to recover somehow.
|
||||
return is_ascii_encoding(getattr(stream, 'encoding', None) or 'ascii')
|
||||
|
||||
def _is_compatible_text_stream(stream, encoding, errors):
|
||||
stream_encoding = getattr(stream, 'encoding', None)
|
||||
stream_errors = getattr(stream, 'errors', None)
|
||||
|
||||
# Perfect match.
|
||||
if stream_encoding == encoding and stream_errors == errors:
|
||||
return True
|
||||
|
||||
# Otherwise, it's only a compatible stream if we did not ask for
|
||||
# an encoding.
|
||||
if encoding is None:
|
||||
return stream_encoding is not None
|
||||
|
||||
return False
|
||||
|
||||
def _force_correct_text_reader(text_reader, encoding, errors):
|
||||
if _is_binary_reader(text_reader, False):
|
||||
binary_reader = text_reader
|
||||
else:
|
||||
# If there is no target encoding set, we need to verify that the
|
||||
# reader is not actually misconfigured.
|
||||
if encoding is None and not _stream_is_misconfigured(text_reader):
|
||||
return text_reader
|
||||
|
||||
if _is_compatible_text_stream(text_reader, encoding, errors):
|
||||
return text_reader
|
||||
|
||||
# If the reader has no encoding, we try to find the underlying
|
||||
# binary reader for it. If that fails because the environment is
|
||||
# misconfigured, we silently go with the same reader because this
|
||||
# is too common to happen. In that case, mojibake is better than
|
||||
# exceptions.
|
||||
binary_reader = _find_binary_reader(text_reader)
|
||||
if binary_reader is None:
|
||||
return text_reader
|
||||
|
||||
# At this point, we default the errors to replace instead of strict
|
||||
# because nobody handles those errors anyways and at this point
|
||||
# we're so fundamentally fucked that nothing can repair it.
|
||||
if errors is None:
|
||||
errors = 'replace'
|
||||
return _make_text_stream(binary_reader, encoding, errors)
|
||||
|
||||
def _force_correct_text_writer(text_writer, encoding, errors):
|
||||
if _is_binary_writer(text_writer, False):
|
||||
binary_writer = text_writer
|
||||
else:
|
||||
# If there is no target encoding set, we need to verify that the
|
||||
# writer is not actually misconfigured.
|
||||
if encoding is None and not _stream_is_misconfigured(text_writer):
|
||||
return text_writer
|
||||
|
||||
if _is_compatible_text_stream(text_writer, encoding, errors):
|
||||
return text_writer
|
||||
|
||||
# If the writer has no encoding, we try to find the underlying
|
||||
# binary writer for it. If that fails because the environment is
|
||||
# misconfigured, we silently go with the same writer because this
|
||||
# is too common to happen. In that case, mojibake is better than
|
||||
# exceptions.
|
||||
binary_writer = _find_binary_writer(text_writer)
|
||||
if binary_writer is None:
|
||||
return text_writer
|
||||
|
||||
# At this point, we default the errors to replace instead of strict
|
||||
# because nobody handles those errors anyways and at this point
|
||||
# we're so fundamentally fucked that nothing can repair it.
|
||||
if errors is None:
|
||||
errors = 'replace'
|
||||
return _make_text_stream(binary_writer, encoding, errors)
|
||||
|
||||
def get_binary_stdin():
|
||||
reader = _find_binary_reader(sys.stdin)
|
||||
if reader is None:
|
||||
raise RuntimeError('Was not able to determine binary '
|
||||
'stream for sys.stdin.')
|
||||
return reader
|
||||
|
||||
def get_binary_stdout():
|
||||
writer = _find_binary_writer(sys.stdout)
|
||||
if writer is None:
|
||||
raise RuntimeError('Was not able to determine binary '
|
||||
'stream for sys.stdout.')
|
||||
return writer
|
||||
|
||||
def get_binary_stderr():
|
||||
writer = _find_binary_writer(sys.stderr)
|
||||
if writer is None:
|
||||
raise RuntimeError('Was not able to determine binary '
|
||||
'stream for sys.stderr.')
|
||||
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):
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode(get_filesystem_encoding(), 'replace')
|
||||
else:
|
||||
value = value.encode('utf-8', 'surrogateescape') \
|
||||
.decode('utf-8', 'replace')
|
||||
return value
|
||||
|
||||
|
||||
def get_streerror(e, default=None):
|
||||
if hasattr(e, 'strerror'):
|
||||
msg = e.strerror
|
||||
else:
|
||||
if default is not None:
|
||||
msg = default
|
||||
else:
|
||||
msg = str(e)
|
||||
if isinstance(msg, bytes):
|
||||
msg = msg.decode('utf-8', 'replace')
|
||||
return msg
|
||||
|
||||
|
||||
def open_stream(filename, mode='r', encoding=None, errors='strict',
|
||||
atomic=False):
|
||||
# Standard streams first. These are simple because they don't need
|
||||
# special handling for the atomic flag. It's entirely ignored.
|
||||
if filename == '-':
|
||||
if 'w' in mode:
|
||||
if 'b' in mode:
|
||||
return get_binary_stdout(), False
|
||||
return get_text_stdout(encoding=encoding, errors=errors), False
|
||||
if 'b' in mode:
|
||||
return get_binary_stdin(), False
|
||||
return get_text_stdin(encoding=encoding, errors=errors), False
|
||||
|
||||
# Non-atomic writes directly go out through the regular open functions.
|
||||
if not atomic:
|
||||
if encoding is None:
|
||||
return open(filename, mode), True
|
||||
return io.open(filename, mode, encoding=encoding, errors=errors), True
|
||||
|
||||
# Some usability stuff for atomic writes
|
||||
if 'a' in mode:
|
||||
raise ValueError(
|
||||
'Appending to an existing file is not supported, because that '
|
||||
'would involve an expensive `copy`-operation to a temporary '
|
||||
'file. Open the file in normal `w`-mode and copy explicitly '
|
||||
'if that\'s what you\'re after.'
|
||||
)
|
||||
if 'x' in mode:
|
||||
raise ValueError('Use the `overwrite`-parameter instead.')
|
||||
if 'w' not in mode:
|
||||
raise ValueError('Atomic writes only make sense with `w`-mode.')
|
||||
|
||||
# Atomic writes are more complicated. They work by opening a file
|
||||
# as a proxy in the same folder and then using the fdopen
|
||||
# functionality to wrap it in a Python file. Then we wrap it in an
|
||||
# atomic file that moves the file over on close.
|
||||
import tempfile
|
||||
fd, tmp_filename = tempfile.mkstemp(dir=os.path.dirname(filename),
|
||||
prefix='.__atomic-write')
|
||||
|
||||
if encoding is not None:
|
||||
f = io.open(fd, mode, encoding=encoding, errors=errors)
|
||||
else:
|
||||
f = os.fdopen(fd, mode)
|
||||
|
||||
return _AtomicFile(f, tmp_filename, filename), True
|
||||
|
||||
|
||||
# Used in a destructor call, needs extra protection from interpreter cleanup.
|
||||
if hasattr(os, 'replace'):
|
||||
_replace = os.replace
|
||||
_can_replace = True
|
||||
else:
|
||||
_replace = os.rename
|
||||
_can_replace = not WIN
|
||||
|
||||
|
||||
class _AtomicFile(object):
|
||||
|
||||
def __init__(self, f, tmp_filename, real_filename):
|
||||
self._f = f
|
||||
self._tmp_filename = tmp_filename
|
||||
self._real_filename = real_filename
|
||||
self.closed = False
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._real_filename
|
||||
|
||||
def close(self, delete=False):
|
||||
if self.closed:
|
||||
return
|
||||
self._f.close()
|
||||
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):
|
||||
return getattr(self._f, name)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.close(delete=exc_type is not None)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._f)
|
||||
|
||||
|
||||
auto_wrap_for_ansi = None
|
||||
colorama = None
|
||||
get_winterm_size = None
|
||||
|
||||
|
||||
def strip_ansi(value):
|
||||
return _ansi_re.sub('', value)
|
||||
|
||||
|
||||
def should_strip_ansi(stream=None, color=None):
|
||||
if color is None:
|
||||
if stream is None:
|
||||
stream = sys.stdin
|
||||
return not isatty(stream)
|
||||
return not color
|
||||
|
||||
|
||||
# If we're on Windows, we provide transparent integration through
|
||||
# colorama. This will make ANSI colors through the echo function
|
||||
# work automatically.
|
||||
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:
|
||||
pass
|
||||
else:
|
||||
_ansi_stream_wrappers = WeakKeyDictionary()
|
||||
|
||||
def auto_wrap_for_ansi(stream, color=None):
|
||||
"""This function wraps a stream so that calls through colorama
|
||||
are issued to the win32 console API to recolor on demand. It
|
||||
also ensures to reset the colors if a write call is interrupted
|
||||
to not destroy the console afterwards.
|
||||
"""
|
||||
try:
|
||||
cached = _ansi_stream_wrappers.get(stream)
|
||||
except Exception:
|
||||
cached = None
|
||||
if cached is not None:
|
||||
return cached
|
||||
strip = should_strip_ansi(stream, color)
|
||||
ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
|
||||
rv = ansi_wrapper.stream
|
||||
_write = rv.write
|
||||
|
||||
def _safe_write(s):
|
||||
try:
|
||||
return _write(s)
|
||||
except:
|
||||
ansi_wrapper.reset_all()
|
||||
raise
|
||||
|
||||
rv.write = _safe_write
|
||||
try:
|
||||
_ansi_stream_wrappers[stream] = rv
|
||||
except Exception:
|
||||
pass
|
||||
return rv
|
||||
|
||||
def get_winterm_size():
|
||||
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):
|
||||
return len(strip_ansi(x))
|
||||
|
||||
|
||||
def isatty(stream):
|
||||
try:
|
||||
return stream.isatty()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _make_cached_stream_func(src_func, wrapper_func):
|
||||
cache = WeakKeyDictionary()
|
||||
def func():
|
||||
stream = src_func()
|
||||
try:
|
||||
rv = cache.get(stream)
|
||||
except Exception:
|
||||
rv = None
|
||||
if rv is not None:
|
||||
return rv
|
||||
rv = wrapper_func()
|
||||
try:
|
||||
cache[stream] = rv
|
||||
except Exception:
|
||||
pass
|
||||
return rv
|
||||
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(
|
||||
lambda: sys.stderr, get_text_stderr)
|
||||
|
||||
|
||||
binary_streams = {
|
||||
'stdin': get_binary_stdin,
|
||||
'stdout': get_binary_stdout,
|
||||
'stderr': get_binary_stderr,
|
||||
}
|
||||
|
||||
text_streams = {
|
||||
'stdin': get_text_stdin,
|
||||
'stdout': get_text_stdout,
|
||||
'stderr': get_text_stderr,
|
||||
}
|
||||
Vendored
-547
@@ -1,547 +0,0 @@
|
||||
"""
|
||||
click._termui_impl
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module contains implementations for the termui module. To keep the
|
||||
import time of Click down, some infrequently used functionality is placed
|
||||
in this module and only imported as needed.
|
||||
|
||||
:copyright: (c) 2014 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import math
|
||||
from ._compat import _default_text_stdout, range_type, PY2, isatty, \
|
||||
open_stream, strip_ansi, term_len, get_best_encoding, WIN
|
||||
from .utils import echo
|
||||
from .exceptions import ClickException
|
||||
|
||||
|
||||
if os.name == 'nt':
|
||||
BEFORE_BAR = '\r'
|
||||
AFTER_BAR = '\n'
|
||||
else:
|
||||
BEFORE_BAR = '\r\033[?25l'
|
||||
AFTER_BAR = '\033[?25h\n'
|
||||
|
||||
|
||||
def _length_hint(obj):
|
||||
"""Returns the length hint of an object."""
|
||||
try:
|
||||
return len(obj)
|
||||
except (AttributeError, TypeError):
|
||||
try:
|
||||
get_hint = type(obj).__length_hint__
|
||||
except AttributeError:
|
||||
return None
|
||||
try:
|
||||
hint = get_hint(obj)
|
||||
except TypeError:
|
||||
return None
|
||||
if hint is NotImplemented or \
|
||||
not isinstance(hint, (int, long)) or \
|
||||
hint < 0:
|
||||
return None
|
||||
return hint
|
||||
|
||||
|
||||
class ProgressBar(object):
|
||||
|
||||
def __init__(self, iterable, length=None, fill_char='#', empty_char=' ',
|
||||
bar_template='%(bar)s', info_sep=' ', show_eta=True,
|
||||
show_percent=None, show_pos=False, item_show_func=None,
|
||||
label=None, file=None, color=None, width=30):
|
||||
self.fill_char = fill_char
|
||||
self.empty_char = empty_char
|
||||
self.bar_template = bar_template
|
||||
self.info_sep = info_sep
|
||||
self.show_eta = show_eta
|
||||
self.show_percent = show_percent
|
||||
self.show_pos = show_pos
|
||||
self.item_show_func = item_show_func
|
||||
self.label = label or ''
|
||||
if file is None:
|
||||
file = _default_text_stdout()
|
||||
self.file = file
|
||||
self.color = color
|
||||
self.width = width
|
||||
self.autowidth = width == 0
|
||||
|
||||
if length is None:
|
||||
length = _length_hint(iterable)
|
||||
if iterable is None:
|
||||
if length is None:
|
||||
raise TypeError('iterable or length is required')
|
||||
iterable = range_type(length)
|
||||
self.iter = iter(iterable)
|
||||
self.length = length
|
||||
self.length_known = length is not None
|
||||
self.pos = 0
|
||||
self.avg = []
|
||||
self.start = self.last_eta = time.time()
|
||||
self.eta_known = False
|
||||
self.finished = False
|
||||
self.max_width = None
|
||||
self.entered = False
|
||||
self.current_item = None
|
||||
self.is_hidden = not isatty(self.file)
|
||||
self._last_line = None
|
||||
|
||||
def __enter__(self):
|
||||
self.entered = True
|
||||
self.render_progress()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.render_finish()
|
||||
|
||||
def __iter__(self):
|
||||
if not self.entered:
|
||||
raise RuntimeError('You need to use progress bars in a with block.')
|
||||
self.render_progress()
|
||||
return self
|
||||
|
||||
def render_finish(self):
|
||||
if self.is_hidden:
|
||||
return
|
||||
self.file.write(AFTER_BAR)
|
||||
self.file.flush()
|
||||
|
||||
@property
|
||||
def pct(self):
|
||||
if self.finished:
|
||||
return 1.0
|
||||
return min(self.pos / (float(self.length) or 1), 1.0)
|
||||
|
||||
@property
|
||||
def time_per_iteration(self):
|
||||
if not self.avg:
|
||||
return 0.0
|
||||
return sum(self.avg) / float(len(self.avg))
|
||||
|
||||
@property
|
||||
def eta(self):
|
||||
if self.length_known and not self.finished:
|
||||
return self.time_per_iteration * (self.length - self.pos)
|
||||
return 0.0
|
||||
|
||||
def format_eta(self):
|
||||
if self.eta_known:
|
||||
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):
|
||||
pos = str(self.pos)
|
||||
if self.length_known:
|
||||
pos += '/%s' % self.length
|
||||
return pos
|
||||
|
||||
def format_pct(self):
|
||||
return ('% 4d%%' % int(self.pct * 100))[1:]
|
||||
|
||||
def format_progress_line(self):
|
||||
show_percent = self.show_percent
|
||||
|
||||
info_bits = []
|
||||
if self.length_known:
|
||||
bar_length = int(self.pct * self.width)
|
||||
bar = self.fill_char * bar_length
|
||||
bar += self.empty_char * (self.width - bar_length)
|
||||
if show_percent is None:
|
||||
show_percent = not self.show_pos
|
||||
else:
|
||||
if self.finished:
|
||||
bar = self.fill_char * self.width
|
||||
else:
|
||||
bar = list(self.empty_char * (self.width or 1))
|
||||
if self.time_per_iteration != 0:
|
||||
bar[int((math.cos(self.pos * self.time_per_iteration)
|
||||
/ 2.0 + 0.5) * self.width)] = self.fill_char
|
||||
bar = ''.join(bar)
|
||||
|
||||
if self.show_pos:
|
||||
info_bits.append(self.format_pos())
|
||||
if show_percent:
|
||||
info_bits.append(self.format_pct())
|
||||
if self.show_eta and self.eta_known and not self.finished:
|
||||
info_bits.append(self.format_eta())
|
||||
if self.item_show_func is not None:
|
||||
item_info = self.item_show_func(self.current_item)
|
||||
if item_info is not None:
|
||||
info_bits.append(item_info)
|
||||
|
||||
return (self.bar_template % {
|
||||
'label': self.label,
|
||||
'bar': bar,
|
||||
'info': self.info_sep.join(info_bits)
|
||||
}).rstrip()
|
||||
|
||||
def render_progress(self):
|
||||
from .termui import get_terminal_size
|
||||
nl = False
|
||||
|
||||
if self.is_hidden:
|
||||
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()
|
||||
|
||||
def make_step(self, n_steps):
|
||||
self.pos += n_steps
|
||||
if self.length_known and self.pos >= self.length:
|
||||
self.finished = True
|
||||
|
||||
if (time.time() - self.last_eta) < 1.0:
|
||||
return
|
||||
|
||||
self.last_eta = time.time()
|
||||
self.avg = self.avg[-6:] + [-(self.start - time.time()) / (self.pos)]
|
||||
|
||||
self.eta_known = self.length_known
|
||||
|
||||
def update(self, n_steps):
|
||||
self.make_step(n_steps)
|
||||
self.render_progress()
|
||||
|
||||
def finish(self):
|
||||
self.eta_known = 0
|
||||
self.current_item = None
|
||||
self.finished = True
|
||||
|
||||
def next(self):
|
||||
if self.is_hidden:
|
||||
return next(self.iter)
|
||||
try:
|
||||
rv = next(self.iter)
|
||||
self.current_item = rv
|
||||
except StopIteration:
|
||||
self.finish()
|
||||
self.render_progress()
|
||||
raise StopIteration()
|
||||
else:
|
||||
self.update(1)
|
||||
return rv
|
||||
|
||||
if not PY2:
|
||||
__next__ = next
|
||||
del next
|
||||
|
||||
|
||||
def pager(text, color=None):
|
||||
"""Decide what method to use for paging through text."""
|
||||
stdout = _default_text_stdout()
|
||||
if not isatty(sys.stdin) or not isatty(stdout):
|
||||
return _nullpager(stdout, text, color)
|
||||
pager_cmd = (os.environ.get('PAGER', None) or '').strip()
|
||||
if pager_cmd:
|
||||
if WIN:
|
||||
return _tempfilepager(text, pager_cmd, color)
|
||||
return _pipepager(text, pager_cmd, color)
|
||||
if os.environ.get('TERM') in ('dumb', 'emacs'):
|
||||
return _nullpager(stdout, text, color)
|
||||
if WIN or sys.platform.startswith('os2'):
|
||||
return _tempfilepager(text, 'more <', color)
|
||||
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
|
||||
return _pipepager(text, 'less', color)
|
||||
|
||||
import tempfile
|
||||
fd, filename = tempfile.mkstemp()
|
||||
os.close(fd)
|
||||
try:
|
||||
if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
|
||||
return _pipepager(text, 'more', color)
|
||||
return _nullpager(stdout, text, color)
|
||||
finally:
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def _pipepager(text, cmd, color):
|
||||
"""Page through text by feeding it to another program. Invoking a
|
||||
pager through this might support colors.
|
||||
"""
|
||||
import subprocess
|
||||
env = dict(os.environ)
|
||||
|
||||
# If we're piping to less we might support colors under the
|
||||
# condition that
|
||||
cmd_detail = cmd.rsplit('/', 1)[-1].split()
|
||||
if color is None and cmd_detail[0] == 'less':
|
||||
less_flags = os.environ.get('LESS', '') + ' '.join(cmd_detail[1:])
|
||||
if not less_flags:
|
||||
env['LESS'] = '-R'
|
||||
color = True
|
||||
elif 'r' in less_flags or 'R' in less_flags:
|
||||
color = True
|
||||
|
||||
if not color:
|
||||
text = strip_ansi(text)
|
||||
|
||||
c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
|
||||
env=env)
|
||||
encoding = get_best_encoding(c.stdin)
|
||||
try:
|
||||
c.stdin.write(text.encode(encoding, 'replace'))
|
||||
c.stdin.close()
|
||||
except (IOError, KeyboardInterrupt):
|
||||
pass
|
||||
|
||||
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting
|
||||
# search or other commands inside less).
|
||||
#
|
||||
# That means when the user hits ^C, the parent process (click) terminates,
|
||||
# but less is still alive, paging the output and messing up the terminal.
|
||||
#
|
||||
# If the user wants to make the pager exit on ^C, they should set
|
||||
# `LESS='-K'`. It's not our decision to make.
|
||||
while True:
|
||||
try:
|
||||
c.wait()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def _tempfilepager(text, cmd, color):
|
||||
"""Page through text by invoking a program on a temporary file."""
|
||||
import tempfile
|
||||
filename = tempfile.mktemp()
|
||||
if not color:
|
||||
text = strip_ansi(text)
|
||||
encoding = get_best_encoding(sys.stdout)
|
||||
with open_stream(filename, 'wb')[0] as f:
|
||||
f.write(text.encode(encoding))
|
||||
try:
|
||||
os.system(cmd + ' "' + filename + '"')
|
||||
finally:
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def _nullpager(stream, text, color):
|
||||
"""Simply print unformatted text. This is the ultimate fallback."""
|
||||
if not color:
|
||||
text = strip_ansi(text)
|
||||
stream.write(text)
|
||||
|
||||
|
||||
class Editor(object):
|
||||
|
||||
def __init__(self, editor=None, env=None, require_save=True,
|
||||
extension='.txt'):
|
||||
self.editor = editor
|
||||
self.env = env
|
||||
self.require_save = require_save
|
||||
self.extension = extension
|
||||
|
||||
def get_editor(self):
|
||||
if self.editor is not None:
|
||||
return self.editor
|
||||
for key in 'VISUAL', 'EDITOR':
|
||||
rv = os.environ.get(key)
|
||||
if rv:
|
||||
return rv
|
||||
if WIN:
|
||||
return 'notepad'
|
||||
for editor in 'vim', 'nano':
|
||||
if os.system('which %s >/dev/null 2>&1' % editor) == 0:
|
||||
return editor
|
||||
return 'vi'
|
||||
|
||||
def edit_file(self, filename):
|
||||
import subprocess
|
||||
editor = self.get_editor()
|
||||
if self.env:
|
||||
environ = os.environ.copy()
|
||||
environ.update(self.env)
|
||||
else:
|
||||
environ = None
|
||||
try:
|
||||
c = subprocess.Popen('%s "%s"' % (editor, filename),
|
||||
env=environ, shell=True)
|
||||
exit_code = c.wait()
|
||||
if exit_code != 0:
|
||||
raise ClickException('%s: Editing failed!' % editor)
|
||||
except OSError as e:
|
||||
raise ClickException('%s: Editing failed: %s' % (editor, e))
|
||||
|
||||
def edit(self, text):
|
||||
import tempfile
|
||||
|
||||
text = text or ''
|
||||
if text and not text.endswith('\n'):
|
||||
text += '\n'
|
||||
|
||||
fd, name = tempfile.mkstemp(prefix='editor-', suffix=self.extension)
|
||||
try:
|
||||
if WIN:
|
||||
encoding = 'utf-8-sig'
|
||||
text = text.replace('\n', '\r\n')
|
||||
else:
|
||||
encoding = 'utf-8'
|
||||
text = text.encode(encoding)
|
||||
|
||||
f = os.fdopen(fd, 'wb')
|
||||
f.write(text)
|
||||
f.close()
|
||||
timestamp = os.path.getmtime(name)
|
||||
|
||||
self.edit_file(name)
|
||||
|
||||
if self.require_save \
|
||||
and os.path.getmtime(name) == timestamp:
|
||||
return None
|
||||
|
||||
f = open(name, 'rb')
|
||||
try:
|
||||
rv = f.read()
|
||||
finally:
|
||||
f.close()
|
||||
return rv.decode('utf-8-sig').replace('\r\n', '\n')
|
||||
finally:
|
||||
os.unlink(name)
|
||||
|
||||
|
||||
def open_url(url, wait=False, locate=False):
|
||||
import subprocess
|
||||
|
||||
def _unquote_file(url):
|
||||
try:
|
||||
import urllib
|
||||
except ImportError:
|
||||
import urllib
|
||||
if url.startswith('file://'):
|
||||
url = urllib.unquote(url[7:])
|
||||
return url
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
args = ['open']
|
||||
if wait:
|
||||
args.append('-W')
|
||||
if locate:
|
||||
args.append('-R')
|
||||
args.append(_unquote_file(url))
|
||||
null = open('/dev/null', 'w')
|
||||
try:
|
||||
return subprocess.Popen(args, stderr=null).wait()
|
||||
finally:
|
||||
null.close()
|
||||
elif WIN:
|
||||
if locate:
|
||||
url = _unquote_file(url)
|
||||
args = 'explorer /select,"%s"' % _unquote_file(
|
||||
url.replace('"', ''))
|
||||
else:
|
||||
args = 'start %s "" "%s"' % (
|
||||
wait and '/WAIT' or '', url.replace('"', ''))
|
||||
return os.system(args)
|
||||
|
||||
try:
|
||||
if locate:
|
||||
url = os.path.dirname(_unquote_file(url)) or '.'
|
||||
else:
|
||||
url = _unquote_file(url)
|
||||
c = subprocess.Popen(['xdg-open', url])
|
||||
if wait:
|
||||
return c.wait()
|
||||
return 0
|
||||
except OSError:
|
||||
if url.startswith(('http://', 'https://')) and not locate and not wait:
|
||||
import webbrowser
|
||||
webbrowser.open(url)
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
def _translate_ch_to_exc(ch):
|
||||
if ch == '\x03':
|
||||
raise KeyboardInterrupt()
|
||||
if ch == '\x04':
|
||||
raise EOFError()
|
||||
|
||||
|
||||
if WIN:
|
||||
import msvcrt
|
||||
|
||||
def getchar(echo):
|
||||
rv = msvcrt.getch()
|
||||
if echo:
|
||||
msvcrt.putchar(rv)
|
||||
_translate_ch_to_exc(rv)
|
||||
if PY2:
|
||||
enc = getattr(sys.stdin, 'encoding', None)
|
||||
if enc is not None:
|
||||
rv = rv.decode(enc, 'replace')
|
||||
else:
|
||||
rv = rv.decode('cp1252', 'replace')
|
||||
return rv
|
||||
else:
|
||||
import tty
|
||||
import termios
|
||||
|
||||
def getchar(echo):
|
||||
if not isatty(sys.stdin):
|
||||
f = open('/dev/tty')
|
||||
fd = f.fileno()
|
||||
else:
|
||||
fd = sys.stdin.fileno()
|
||||
f = None
|
||||
try:
|
||||
old_settings = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
ch = os.read(fd, 32)
|
||||
if echo and isatty(sys.stdout):
|
||||
sys.stdout.write(ch)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
sys.stdout.flush()
|
||||
if f is not None:
|
||||
f.close()
|
||||
except termios.error:
|
||||
pass
|
||||
_translate_ch_to_exc(ch)
|
||||
return ch.decode(get_best_encoding(sys.stdin), 'replace')
|
||||
Vendored
-38
@@ -1,38 +0,0 @@
|
||||
import textwrap
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
class TextWrapper(textwrap.TextWrapper):
|
||||
|
||||
def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
|
||||
space_left = max(width - cur_len, 1)
|
||||
|
||||
if self.break_long_words:
|
||||
last = reversed_chunks[-1]
|
||||
cut = last[:space_left]
|
||||
res = last[space_left:]
|
||||
cur_line.append(cut)
|
||||
reversed_chunks[-1] = res
|
||||
elif not cur_line:
|
||||
cur_line.append(reversed_chunks.pop())
|
||||
|
||||
@contextmanager
|
||||
def extra_indent(self, indent):
|
||||
old_initial_indent = self.initial_indent
|
||||
old_subsequent_indent = self.subsequent_indent
|
||||
self.initial_indent += indent
|
||||
self.subsequent_indent += indent
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.initial_indent = old_initial_indent
|
||||
self.subsequent_indent = old_subsequent_indent
|
||||
|
||||
def indent_only(self, text):
|
||||
rv = []
|
||||
for idx, line in enumerate(text.splitlines()):
|
||||
indent = self.initial_indent
|
||||
if idx > 0:
|
||||
indent = self.subsequent_indent
|
||||
rv.append(indent + line)
|
||||
return '\n'.join(rv)
|
||||
Vendored
-118
@@ -1,118 +0,0 @@
|
||||
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
|
||||
|
||||
# Make sure we're operating on text here.
|
||||
if isinstance(rv, bytes):
|
||||
rv = rv.decode('ascii', 'replace')
|
||||
|
||||
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. Consult http://click.pocoo.org/python3/'
|
||||
'for mitigation steps.' + extra)
|
||||
Vendored
-273
@@ -1,273 +0,0 @@
|
||||
# -*- 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, c_int, c_char, c_char_p, \
|
||||
c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE
|
||||
try:
|
||||
from ctypes import pythonapi
|
||||
PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
|
||||
PyBuffer_Release = pythonapi.PyBuffer_Release
|
||||
except ImportError:
|
||||
pythonapi = None
|
||||
from ctypes.wintypes import LPWSTR, LPCWSTR
|
||||
|
||||
|
||||
c_ssize_p = POINTER(c_ssize_t)
|
||||
|
||||
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))
|
||||
|
||||
|
||||
# On PyPy we cannot get buffers so our ability to operate here is
|
||||
# serverly limited.
|
||||
if pythonapi is None:
|
||||
get_buffer = None
|
||||
else:
|
||||
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.startswith(('-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 get_buffer is not None and \
|
||||
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)
|
||||
Vendored
-1744
File diff suppressed because it is too large
Load Diff
Vendored
-304
@@ -1,304 +0,0 @@
|
||||
import sys
|
||||
import inspect
|
||||
|
||||
from functools import update_wrapper
|
||||
|
||||
from ._compat import iteritems
|
||||
from ._unicodefun import _check_for_unicode_literals
|
||||
from .utils import echo
|
||||
from .globals import get_current_context
|
||||
|
||||
|
||||
def pass_context(f):
|
||||
"""Marks a callback as wanting to receive the current context
|
||||
object as first argument.
|
||||
"""
|
||||
def new_func(*args, **kwargs):
|
||||
return f(get_current_context(), *args, **kwargs)
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
|
||||
def pass_obj(f):
|
||||
"""Similar to :func:`pass_context`, but only pass the object on the
|
||||
context onwards (:attr:`Context.obj`). This is useful if that object
|
||||
represents the state of a nested system.
|
||||
"""
|
||||
def new_func(*args, **kwargs):
|
||||
return f(get_current_context().obj, *args, **kwargs)
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
|
||||
def make_pass_decorator(object_type, ensure=False):
|
||||
"""Given an object type this creates a decorator that will work
|
||||
similar to :func:`pass_obj` but instead of passing the object of the
|
||||
current context, it will find the innermost context of type
|
||||
:func:`object_type`.
|
||||
|
||||
This generates a decorator that works roughly like this::
|
||||
|
||||
from functools import update_wrapper
|
||||
|
||||
def decorator(f):
|
||||
@pass_context
|
||||
def new_func(ctx, *args, **kwargs):
|
||||
obj = ctx.find_object(object_type)
|
||||
return ctx.invoke(f, obj, *args, **kwargs)
|
||||
return update_wrapper(new_func, f)
|
||||
return decorator
|
||||
|
||||
:param object_type: the type of the object to pass.
|
||||
:param ensure: if set to `True`, a new object will be created and
|
||||
remembered on the context if it's not there yet.
|
||||
"""
|
||||
def decorator(f):
|
||||
def new_func(*args, **kwargs):
|
||||
ctx = get_current_context()
|
||||
if ensure:
|
||||
obj = ctx.ensure_object(object_type)
|
||||
else:
|
||||
obj = ctx.find_object(object_type)
|
||||
if obj is None:
|
||||
raise RuntimeError('Managed to invoke callback without a '
|
||||
'context object of type %r existing'
|
||||
% object_type.__name__)
|
||||
return ctx.invoke(f, obj, *args[1:], **kwargs)
|
||||
return update_wrapper(new_func, f)
|
||||
return decorator
|
||||
|
||||
|
||||
def _make_command(f, name, attrs, cls):
|
||||
if isinstance(f, Command):
|
||||
raise TypeError('Attempted to convert a callback into a '
|
||||
'command twice.')
|
||||
try:
|
||||
params = f.__click_params__
|
||||
params.reverse()
|
||||
del f.__click_params__
|
||||
except AttributeError:
|
||||
params = []
|
||||
help = attrs.get('help')
|
||||
if help is None:
|
||||
help = inspect.getdoc(f)
|
||||
if isinstance(help, bytes):
|
||||
help = help.decode('utf-8')
|
||||
else:
|
||||
help = inspect.cleandoc(help)
|
||||
attrs['help'] = help
|
||||
_check_for_unicode_literals()
|
||||
return cls(name=name or f.__name__.lower(),
|
||||
callback=f, params=params, **attrs)
|
||||
|
||||
|
||||
def command(name=None, cls=None, **attrs):
|
||||
"""Creates a new :class:`Command` and uses the decorated function as
|
||||
callback. This will also automatically attach all decorated
|
||||
:func:`option`\s and :func:`argument`\s as parameters to the command.
|
||||
|
||||
The name of the command defaults to the name of the function. If you
|
||||
want to change that, you can pass the intended name as the first
|
||||
argument.
|
||||
|
||||
All keyword arguments are forwarded to the underlying command class.
|
||||
|
||||
Once decorated the function turns into a :class:`Command` instance
|
||||
that can be invoked as a command line utility or be attached to a
|
||||
command :class:`Group`.
|
||||
|
||||
:param name: the name of the command. This defaults to the function
|
||||
name.
|
||||
:param cls: the command class to instantiate. This defaults to
|
||||
:class:`Command`.
|
||||
"""
|
||||
if cls is None:
|
||||
cls = Command
|
||||
def decorator(f):
|
||||
cmd = _make_command(f, name, attrs, cls)
|
||||
cmd.__doc__ = f.__doc__
|
||||
return cmd
|
||||
return decorator
|
||||
|
||||
|
||||
def group(name=None, **attrs):
|
||||
"""Creates a new :class:`Group` with a function as callback. This
|
||||
works otherwise the same as :func:`command` just that the `cls`
|
||||
parameter is set to :class:`Group`.
|
||||
"""
|
||||
attrs.setdefault('cls', Group)
|
||||
return command(name, **attrs)
|
||||
|
||||
|
||||
def _param_memo(f, param):
|
||||
if isinstance(f, Command):
|
||||
f.params.append(param)
|
||||
else:
|
||||
if not hasattr(f, '__click_params__'):
|
||||
f.__click_params__ = []
|
||||
f.__click_params__.append(param)
|
||||
|
||||
|
||||
def argument(*param_decls, **attrs):
|
||||
"""Attaches an argument to the command. All positional arguments are
|
||||
passed as parameter declarations to :class:`Argument`; all keyword
|
||||
arguments are forwarded unchanged (except ``cls``).
|
||||
This is equivalent to creating an :class:`Argument` instance manually
|
||||
and attaching it to the :attr:`Command.params` list.
|
||||
|
||||
:param cls: the argument class to instantiate. This defaults to
|
||||
:class:`Argument`.
|
||||
"""
|
||||
def decorator(f):
|
||||
ArgumentClass = attrs.pop('cls', Argument)
|
||||
_param_memo(f, ArgumentClass(param_decls, **attrs))
|
||||
return f
|
||||
return decorator
|
||||
|
||||
|
||||
def option(*param_decls, **attrs):
|
||||
"""Attaches an option to the command. All positional arguments are
|
||||
passed as parameter declarations to :class:`Option`; all keyword
|
||||
arguments are forwarded unchanged (except ``cls``).
|
||||
This is equivalent to creating an :class:`Option` instance manually
|
||||
and attaching it to the :attr:`Command.params` list.
|
||||
|
||||
:param cls: the option class to instantiate. This defaults to
|
||||
:class:`Option`.
|
||||
"""
|
||||
def decorator(f):
|
||||
if 'help' in attrs:
|
||||
attrs['help'] = inspect.cleandoc(attrs['help'])
|
||||
OptionClass = attrs.pop('cls', Option)
|
||||
_param_memo(f, OptionClass(param_decls, **attrs))
|
||||
return f
|
||||
return decorator
|
||||
|
||||
|
||||
def confirmation_option(*param_decls, **attrs):
|
||||
"""Shortcut for confirmation prompts that can be ignored by passing
|
||||
``--yes`` as parameter.
|
||||
|
||||
This is equivalent to decorating a function with :func:`option` with
|
||||
the following parameters::
|
||||
|
||||
def callback(ctx, param, value):
|
||||
if not value:
|
||||
ctx.abort()
|
||||
|
||||
@click.command()
|
||||
@click.option('--yes', is_flag=True, callback=callback,
|
||||
expose_value=False, prompt='Do you want to continue?')
|
||||
def dropdb():
|
||||
pass
|
||||
"""
|
||||
def decorator(f):
|
||||
def callback(ctx, param, value):
|
||||
if not value:
|
||||
ctx.abort()
|
||||
attrs.setdefault('is_flag', True)
|
||||
attrs.setdefault('callback', callback)
|
||||
attrs.setdefault('expose_value', False)
|
||||
attrs.setdefault('prompt', 'Do you want to continue?')
|
||||
attrs.setdefault('help', 'Confirm the action without prompting.')
|
||||
return option(*(param_decls or ('--yes',)), **attrs)(f)
|
||||
return decorator
|
||||
|
||||
|
||||
def password_option(*param_decls, **attrs):
|
||||
"""Shortcut for password prompts.
|
||||
|
||||
This is equivalent to decorating a function with :func:`option` with
|
||||
the following parameters::
|
||||
|
||||
@click.command()
|
||||
@click.option('--password', prompt=True, confirmation_prompt=True,
|
||||
hide_input=True)
|
||||
def changeadmin(password):
|
||||
pass
|
||||
"""
|
||||
def decorator(f):
|
||||
attrs.setdefault('prompt', True)
|
||||
attrs.setdefault('confirmation_prompt', True)
|
||||
attrs.setdefault('hide_input', True)
|
||||
return option(*(param_decls or ('--password',)), **attrs)(f)
|
||||
return decorator
|
||||
|
||||
|
||||
def version_option(version=None, *param_decls, **attrs):
|
||||
"""Adds a ``--version`` option which immediately ends the program
|
||||
printing out the version number. This is implemented as an eager
|
||||
option that prints the version and exits the program in the callback.
|
||||
|
||||
:param version: the version number to show. If not provided Click
|
||||
attempts an auto discovery via setuptools.
|
||||
:param prog_name: the name of the program (defaults to autodetection)
|
||||
:param message: custom message to show instead of the default
|
||||
(``'%(prog)s, version %(version)s'``)
|
||||
:param others: everything else is forwarded to :func:`option`.
|
||||
"""
|
||||
if version is None:
|
||||
module = sys._getframe(1).f_globals.get('__name__')
|
||||
def decorator(f):
|
||||
prog_name = attrs.pop('prog_name', None)
|
||||
message = attrs.pop('message', '%(prog)s, version %(version)s')
|
||||
|
||||
def callback(ctx, param, value):
|
||||
if not value or ctx.resilient_parsing:
|
||||
return
|
||||
prog = prog_name
|
||||
if prog is None:
|
||||
prog = ctx.find_root().info_name
|
||||
ver = version
|
||||
if ver is None:
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
for dist in pkg_resources.working_set:
|
||||
scripts = dist.get_entry_map().get('console_scripts') or {}
|
||||
for script_name, entry_point in iteritems(scripts):
|
||||
if entry_point.module_name == module:
|
||||
ver = dist.version
|
||||
break
|
||||
if ver is None:
|
||||
raise RuntimeError('Could not determine version')
|
||||
echo(message % {
|
||||
'prog': prog,
|
||||
'version': ver,
|
||||
}, color=ctx.color)
|
||||
ctx.exit()
|
||||
|
||||
attrs.setdefault('is_flag', True)
|
||||
attrs.setdefault('expose_value', False)
|
||||
attrs.setdefault('is_eager', True)
|
||||
attrs.setdefault('help', 'Show the version and exit.')
|
||||
attrs['callback'] = callback
|
||||
return option(*(param_decls or ('--version',)), **attrs)(f)
|
||||
return decorator
|
||||
|
||||
|
||||
def help_option(*param_decls, **attrs):
|
||||
"""Adds a ``--help`` option which immediately ends the program
|
||||
printing out the help page. This is usually unnecessary to add as
|
||||
this is added by default to all commands unless suppressed.
|
||||
|
||||
Like :func:`version_option`, this is implemented as eager option that
|
||||
prints in the callback and exits.
|
||||
|
||||
All arguments are forwarded to :func:`option`.
|
||||
"""
|
||||
def decorator(f):
|
||||
def callback(ctx, param, value):
|
||||
if value and not ctx.resilient_parsing:
|
||||
echo(ctx.get_help(), color=ctx.color)
|
||||
ctx.exit()
|
||||
attrs.setdefault('is_flag', True)
|
||||
attrs.setdefault('expose_value', False)
|
||||
attrs.setdefault('help', 'Show this message and exit.')
|
||||
attrs.setdefault('is_eager', True)
|
||||
attrs['callback'] = callback
|
||||
return option(*(param_decls or ('--help',)), **attrs)(f)
|
||||
return decorator
|
||||
|
||||
|
||||
# Circular dependencies between core and decorators
|
||||
from .core import Command, Group, Argument, Option
|
||||
Vendored
-201
@@ -1,201 +0,0 @@
|
||||
from ._compat import PY2, filename_to_ui, get_text_stderr
|
||||
from .utils import echo
|
||||
|
||||
|
||||
class ClickException(Exception):
|
||||
"""An exception that Click can handle and show to the user."""
|
||||
|
||||
#: The exit code for this exception
|
||||
exit_code = 1
|
||||
|
||||
def __init__(self, message):
|
||||
if PY2:
|
||||
if message is not None:
|
||||
message = message.encode('utf-8')
|
||||
Exception.__init__(self, message)
|
||||
self.message = message
|
||||
|
||||
def format_message(self):
|
||||
return self.message
|
||||
|
||||
def show(self, file=None):
|
||||
if file is None:
|
||||
file = get_text_stderr()
|
||||
echo('Error: %s' % self.format_message(), file=file)
|
||||
|
||||
|
||||
class UsageError(ClickException):
|
||||
"""An internal exception that signals a usage error. This typically
|
||||
aborts any further handling.
|
||||
|
||||
:param message: the error message to display.
|
||||
:param ctx: optionally the context that caused this error. Click will
|
||||
fill in the context automatically in some situations.
|
||||
"""
|
||||
exit_code = 2
|
||||
|
||||
def __init__(self, message, ctx=None):
|
||||
ClickException.__init__(self, message)
|
||||
self.ctx = ctx
|
||||
|
||||
def show(self, file=None):
|
||||
if file is None:
|
||||
file = get_text_stderr()
|
||||
color = None
|
||||
if self.ctx is not None:
|
||||
color = self.ctx.color
|
||||
echo(self.ctx.get_usage() + '\n', file=file, color=color)
|
||||
echo('Error: %s' % self.format_message(), file=file, color=color)
|
||||
|
||||
|
||||
class BadParameter(UsageError):
|
||||
"""An exception that formats out a standardized error message for a
|
||||
bad parameter. This is useful when thrown from a callback or type as
|
||||
Click will attach contextual information to it (for instance, which
|
||||
parameter it is).
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param param: the parameter object that caused this error. This can
|
||||
be left out, and Click will attach this info itself
|
||||
if possible.
|
||||
:param param_hint: a string that shows up as parameter name. This
|
||||
can be used as alternative to `param` in cases
|
||||
where custom validation should happen. If it is
|
||||
a string it's used as such, if it's a list then
|
||||
each item is quoted and separated.
|
||||
"""
|
||||
|
||||
def __init__(self, message, ctx=None, param=None,
|
||||
param_hint=None):
|
||||
UsageError.__init__(self, message, ctx)
|
||||
self.param = param
|
||||
self.param_hint = param_hint
|
||||
|
||||
def format_message(self):
|
||||
if self.param_hint is not None:
|
||||
param_hint = self.param_hint
|
||||
elif self.param is not None:
|
||||
param_hint = self.param.opts or [self.param.human_readable_name]
|
||||
else:
|
||||
return 'Invalid value: %s' % self.message
|
||||
if isinstance(param_hint, (tuple, list)):
|
||||
param_hint = ' / '.join('"%s"' % x for x in param_hint)
|
||||
return 'Invalid value for %s: %s' % (param_hint, self.message)
|
||||
|
||||
|
||||
class MissingParameter(BadParameter):
|
||||
"""Raised if click required an option or argument but it was not
|
||||
provided when invoking the script.
|
||||
|
||||
.. versionadded:: 4.0
|
||||
|
||||
:param param_type: a string that indicates the type of the parameter.
|
||||
The default is to inherit the parameter type from
|
||||
the given `param`. Valid values are ``'parameter'``,
|
||||
``'option'`` or ``'argument'``.
|
||||
"""
|
||||
|
||||
def __init__(self, message=None, ctx=None, param=None,
|
||||
param_hint=None, param_type=None):
|
||||
BadParameter.__init__(self, message, ctx, param, param_hint)
|
||||
self.param_type = param_type
|
||||
|
||||
def format_message(self):
|
||||
if self.param_hint is not None:
|
||||
param_hint = self.param_hint
|
||||
elif self.param is not None:
|
||||
param_hint = self.param.opts or [self.param.human_readable_name]
|
||||
else:
|
||||
param_hint = None
|
||||
if isinstance(param_hint, (tuple, list)):
|
||||
param_hint = ' / '.join('"%s"' % x for x in param_hint)
|
||||
|
||||
param_type = self.param_type
|
||||
if param_type is None and self.param is not None:
|
||||
param_type = self.param.param_type_name
|
||||
|
||||
msg = self.message
|
||||
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,
|
||||
param_hint and ' %s' % param_hint or '',
|
||||
msg and '. ' or '.',
|
||||
msg or '',
|
||||
)
|
||||
|
||||
|
||||
class NoSuchOption(UsageError):
|
||||
"""Raised if click attempted to handle an option that does not
|
||||
exist.
|
||||
|
||||
.. versionadded:: 4.0
|
||||
"""
|
||||
|
||||
def __init__(self, option_name, message=None, possibilities=None,
|
||||
ctx=None):
|
||||
if message is None:
|
||||
message = 'no such option: %s' % option_name
|
||||
UsageError.__init__(self, message, ctx)
|
||||
self.option_name = option_name
|
||||
self.possibilities = possibilities
|
||||
|
||||
def format_message(self):
|
||||
bits = [self.message]
|
||||
if self.possibilities:
|
||||
if len(self.possibilities) == 1:
|
||||
bits.append('Did you mean %s?' % self.possibilities[0])
|
||||
else:
|
||||
possibilities = sorted(self.possibilities)
|
||||
bits.append('(Possible options: %s)' % ', '.join(possibilities))
|
||||
return ' '.join(bits)
|
||||
|
||||
|
||||
class BadOptionUsage(UsageError):
|
||||
"""Raised if an option is generally supplied but the use of the option
|
||||
was incorrect. This is for instance raised if the number of arguments
|
||||
for an option is not correct.
|
||||
|
||||
.. versionadded:: 4.0
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class FileError(ClickException):
|
||||
"""Raised if a file cannot be opened."""
|
||||
|
||||
def __init__(self, filename, hint=None):
|
||||
ui_filename = filename_to_ui(filename)
|
||||
if hint is None:
|
||||
hint = 'unknown error'
|
||||
ClickException.__init__(self, hint)
|
||||
self.ui_filename = ui_filename
|
||||
self.filename = filename
|
||||
|
||||
def format_message(self):
|
||||
return 'Could not open file %s: %s' % (self.ui_filename, self.message)
|
||||
|
||||
|
||||
class Abort(RuntimeError):
|
||||
"""An internal signalling exception that signals Click to abort."""
|
||||
Vendored
-256
@@ -1,256 +0,0 @@
|
||||
from contextlib import contextmanager
|
||||
from .termui import get_terminal_size
|
||||
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:
|
||||
for idx, col in enumerate(row):
|
||||
widths[idx] = max(widths.get(idx, 0), term_len(col))
|
||||
return tuple(y for x, y in sorted(widths.items()))
|
||||
|
||||
|
||||
def iter_rows(rows, col_count):
|
||||
for row in rows:
|
||||
row = tuple(row)
|
||||
yield row + ('',) * (col_count - len(row))
|
||||
|
||||
|
||||
def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
|
||||
preserve_paragraphs=False):
|
||||
"""A helper function that intelligently wraps text. By default, it
|
||||
assumes that it operates on a single paragraph of text but if the
|
||||
`preserve_paragraphs` parameter is provided it will intelligently
|
||||
handle paragraphs (defined by two empty lines).
|
||||
|
||||
If paragraphs are handled, a paragraph can be prefixed with an empty
|
||||
line containing the ``\\b`` character (``\\x08``) to indicate that
|
||||
no rewrapping should happen in that block.
|
||||
|
||||
:param text: the text that should be rewrapped.
|
||||
:param width: the maximum width for the text.
|
||||
:param initial_indent: the initial indent that should be placed on the
|
||||
first line as a string.
|
||||
:param subsequent_indent: the indent string that should be placed on
|
||||
each consecutive line.
|
||||
:param preserve_paragraphs: if this flag is set then the wrapping will
|
||||
intelligently handle paragraphs.
|
||||
"""
|
||||
from ._textwrap import TextWrapper
|
||||
text = text.expandtabs()
|
||||
wrapper = TextWrapper(width, initial_indent=initial_indent,
|
||||
subsequent_indent=subsequent_indent,
|
||||
replace_whitespace=False)
|
||||
if not preserve_paragraphs:
|
||||
return wrapper.fill(text)
|
||||
|
||||
p = []
|
||||
buf = []
|
||||
indent = None
|
||||
|
||||
def _flush_par():
|
||||
if not buf:
|
||||
return
|
||||
if buf[0].strip() == '\b':
|
||||
p.append((indent or 0, True, '\n'.join(buf[1:])))
|
||||
else:
|
||||
p.append((indent or 0, False, ' '.join(buf)))
|
||||
del buf[:]
|
||||
|
||||
for line in text.splitlines():
|
||||
if not line:
|
||||
_flush_par()
|
||||
indent = None
|
||||
else:
|
||||
if indent is None:
|
||||
orig_len = term_len(line)
|
||||
line = line.lstrip()
|
||||
indent = orig_len - term_len(line)
|
||||
buf.append(line)
|
||||
_flush_par()
|
||||
|
||||
rv = []
|
||||
for indent, raw, text in p:
|
||||
with wrapper.extra_indent(' ' * indent):
|
||||
if raw:
|
||||
rv.append(wrapper.indent_only(text))
|
||||
else:
|
||||
rv.append(wrapper.fill(text))
|
||||
|
||||
return '\n\n'.join(rv)
|
||||
|
||||
|
||||
class HelpFormatter(object):
|
||||
"""This class helps with formatting text-based help pages. It's
|
||||
usually just needed for very special internal cases, but it's also
|
||||
exposed so that developers can write their own fancy outputs.
|
||||
|
||||
At present, it always writes into memory.
|
||||
|
||||
:param indent_increment: the additional increment for each level.
|
||||
:param width: the width for the text. This defaults to the terminal
|
||||
width clamped to a maximum of 78.
|
||||
"""
|
||||
|
||||
def __init__(self, indent_increment=2, width=None, max_width=None):
|
||||
self.indent_increment = indent_increment
|
||||
if max_width is None:
|
||||
max_width = 80
|
||||
if width is None:
|
||||
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 = []
|
||||
|
||||
def write(self, string):
|
||||
"""Writes a unicode string into the internal buffer."""
|
||||
self.buffer.append(string)
|
||||
|
||||
def indent(self):
|
||||
"""Increases the indentation."""
|
||||
self.current_indent += self.indent_increment
|
||||
|
||||
def dedent(self):
|
||||
"""Decreases the indentation."""
|
||||
self.current_indent -= self.indent_increment
|
||||
|
||||
def write_usage(self, prog, args='', prefix='Usage: '):
|
||||
"""Writes a usage line into the buffer.
|
||||
|
||||
:param prog: the program name.
|
||||
:param args: whitespace separated list of arguments.
|
||||
:param prefix: the prefix for the first line.
|
||||
"""
|
||||
usage_prefix = '%*s%s ' % (self.current_indent, prefix, prog)
|
||||
text_width = self.width - self.current_indent
|
||||
|
||||
if text_width >= (term_len(usage_prefix) + 20):
|
||||
# The arguments will fit to the right of the prefix.
|
||||
indent = ' ' * term_len(usage_prefix)
|
||||
self.write(wrap_text(args, text_width,
|
||||
initial_indent=usage_prefix,
|
||||
subsequent_indent=indent))
|
||||
else:
|
||||
# The prefix is too long, put the arguments on the next line.
|
||||
self.write(usage_prefix)
|
||||
self.write('\n')
|
||||
indent = ' ' * (max(self.current_indent, term_len(prefix)) + 4)
|
||||
self.write(wrap_text(args, text_width,
|
||||
initial_indent=indent,
|
||||
subsequent_indent=indent))
|
||||
|
||||
self.write('\n')
|
||||
|
||||
def write_heading(self, heading):
|
||||
"""Writes a heading into the buffer."""
|
||||
self.write('%*s%s:\n' % (self.current_indent, '', heading))
|
||||
|
||||
def write_paragraph(self):
|
||||
"""Writes a paragraph into the buffer."""
|
||||
if self.buffer:
|
||||
self.write('\n')
|
||||
|
||||
def write_text(self, text):
|
||||
"""Writes re-indented text into the buffer. This rewraps and
|
||||
preserves paragraphs.
|
||||
"""
|
||||
text_width = max(self.width - self.current_indent, 11)
|
||||
indent = ' ' * self.current_indent
|
||||
self.write(wrap_text(text, text_width,
|
||||
initial_indent=indent,
|
||||
subsequent_indent=indent,
|
||||
preserve_paragraphs=True))
|
||||
self.write('\n')
|
||||
|
||||
def write_dl(self, rows, col_max=30, col_spacing=2):
|
||||
"""Writes a definition list into the buffer. This is how options
|
||||
and commands are usually formatted.
|
||||
|
||||
:param rows: a list of two item tuples for the terms and values.
|
||||
:param col_max: the maximum width of the first column.
|
||||
:param col_spacing: the number of spaces between the first and
|
||||
second column.
|
||||
"""
|
||||
rows = list(rows)
|
||||
widths = measure_table(rows)
|
||||
if len(widths) != 2:
|
||||
raise TypeError('Expected two columns for definition list')
|
||||
|
||||
first_col = min(widths[0], col_max) + col_spacing
|
||||
|
||||
for first, second in iter_rows(rows, len(widths)):
|
||||
self.write('%*s%s' % (self.current_indent, '', first))
|
||||
if not second:
|
||||
self.write('\n')
|
||||
continue
|
||||
if term_len(first) <= first_col - col_spacing:
|
||||
self.write(' ' * (first_col - term_len(first)))
|
||||
else:
|
||||
self.write('\n')
|
||||
self.write(' ' * (first_col + self.current_indent))
|
||||
|
||||
text_width = max(self.width - first_col - 2, 10)
|
||||
lines = iter(wrap_text(second, text_width).splitlines())
|
||||
if lines:
|
||||
self.write(next(lines) + '\n')
|
||||
for line in lines:
|
||||
self.write('%*s%s\n' % (
|
||||
first_col + self.current_indent, '', line))
|
||||
else:
|
||||
self.write('\n')
|
||||
|
||||
@contextmanager
|
||||
def section(self, name):
|
||||
"""Helpful context manager that writes a paragraph, a heading,
|
||||
and the indents.
|
||||
|
||||
:param name: the section name that is written as heading.
|
||||
"""
|
||||
self.write_paragraph()
|
||||
self.write_heading(name)
|
||||
self.indent()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.dedent()
|
||||
|
||||
@contextmanager
|
||||
def indentation(self):
|
||||
"""A context manager that increases the indentation."""
|
||||
self.indent()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.dedent()
|
||||
|
||||
def getvalue(self):
|
||||
"""Returns the buffer contents."""
|
||||
return ''.join(self.buffer)
|
||||
|
||||
|
||||
def join_options(options):
|
||||
"""Given a list of option strings this joins them in the most appropriate
|
||||
way and returns them in the form ``(formatted_string,
|
||||
any_prefix_is_slash)`` where the second item in the tuple is a flag that
|
||||
indicates if any of the option prefixes was a slash.
|
||||
"""
|
||||
rv = []
|
||||
any_prefix_is_slash = False
|
||||
for opt in options:
|
||||
prefix = split_opt(opt)[0]
|
||||
if prefix == '/':
|
||||
any_prefix_is_slash = True
|
||||
rv.append((len(prefix), opt))
|
||||
|
||||
rv.sort(key=lambda x: x[0])
|
||||
|
||||
rv = ', '.join(x[1] for x in rv)
|
||||
return rv, any_prefix_is_slash
|
||||
Vendored
-48
@@ -1,48 +0,0 @@
|
||||
from threading import local
|
||||
|
||||
|
||||
_local = local()
|
||||
|
||||
|
||||
def get_current_context(silent=False):
|
||||
"""Returns the current click context. This can be used as a way to
|
||||
access the current context object from anywhere. This is a more implicit
|
||||
alternative to the :func:`pass_context` decorator. This function is
|
||||
primarily useful for helpers such as :func:`echo` which might be
|
||||
interested in changing it's behavior based on the current context.
|
||||
|
||||
To push the current context, :meth:`Context.scope` can be used.
|
||||
|
||||
.. versionadded:: 5.0
|
||||
|
||||
:param silent: is set to `True` the return value is `None` if no context
|
||||
is available. The default behavior is to raise a
|
||||
:exc:`RuntimeError`.
|
||||
"""
|
||||
try:
|
||||
return getattr(_local, 'stack')[-1]
|
||||
except (AttributeError, IndexError):
|
||||
if not silent:
|
||||
raise RuntimeError('There is no active click context.')
|
||||
|
||||
|
||||
def push_context(ctx):
|
||||
"""Pushes a new context to the current stack."""
|
||||
_local.__dict__.setdefault('stack', []).append(ctx)
|
||||
|
||||
|
||||
def pop_context():
|
||||
"""Removes the top level from the stack."""
|
||||
_local.stack.pop()
|
||||
|
||||
|
||||
def resolve_color_default(color=None):
|
||||
""""Internal helper to get the default value of the color flag. If a
|
||||
value is passed it's returned unchanged, otherwise it's looked up from
|
||||
the current context.
|
||||
"""
|
||||
if color is not None:
|
||||
return color
|
||||
ctx = get_current_context(silent=True)
|
||||
if ctx is not None:
|
||||
return ctx.color
|
||||
Vendored
-426
@@ -1,426 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
click.parser
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This module started out as largely a copy paste from the stdlib's
|
||||
optparse module with the features removed that we do not need from
|
||||
optparse because we implement them in Click on a higher level (for
|
||||
instance type handling, help formatting and a lot more).
|
||||
|
||||
The plan is to remove more and more from here over time.
|
||||
|
||||
The reason this is a different module and not optparse from the stdlib
|
||||
is that there are differences in 2.x and 3.x about the error messages
|
||||
generated and optparse in the stdlib uses gettext for no good reason
|
||||
and might cause us issues.
|
||||
"""
|
||||
import re
|
||||
from collections import deque
|
||||
from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \
|
||||
BadArgumentUsage
|
||||
|
||||
|
||||
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('%s option requires an argument' % opt)
|
||||
raise BadOptionUsage('%s option requires %d arguments' % (opt, nargs))
|
||||
|
||||
|
||||
def split_opt(opt):
|
||||
first = opt[:1]
|
||||
if first.isalnum():
|
||||
return '', opt
|
||||
if opt[1:2] == first:
|
||||
return opt[:2], opt[2:]
|
||||
return first, opt[1:]
|
||||
|
||||
|
||||
def normalize_opt(opt, ctx):
|
||||
if ctx is None or ctx.token_normalize_func is None:
|
||||
return opt
|
||||
prefix, opt = split_opt(opt)
|
||||
return prefix + ctx.token_normalize_func(opt)
|
||||
|
||||
|
||||
def split_arg_string(string):
|
||||
"""Given an argument string this attempts to split it into small parts."""
|
||||
rv = []
|
||||
for match in re.finditer(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
|
||||
r'|"([^"\\]*(?:\\.[^"\\]*)*)"'
|
||||
r'|\S+)\s*', string, re.S):
|
||||
arg = match.group().strip()
|
||||
if arg[:1] == arg[-1:] and arg[:1] in '"\'':
|
||||
arg = arg[1:-1].encode('ascii', 'backslashreplace') \
|
||||
.decode('unicode-escape')
|
||||
try:
|
||||
arg = type(string)(arg)
|
||||
except UnicodeError:
|
||||
pass
|
||||
rv.append(arg)
|
||||
return rv
|
||||
|
||||
|
||||
class Option(object):
|
||||
|
||||
def __init__(self, opts, dest, action=None, nargs=1, const=None, obj=None):
|
||||
self._short_opts = []
|
||||
self._long_opts = []
|
||||
self.prefixes = set()
|
||||
|
||||
for opt in opts:
|
||||
prefix, value = split_opt(opt)
|
||||
if not prefix:
|
||||
raise ValueError('Invalid start character for option (%s)'
|
||||
% opt)
|
||||
self.prefixes.add(prefix[0])
|
||||
if len(prefix) == 1 and len(value) == 1:
|
||||
self._short_opts.append(opt)
|
||||
else:
|
||||
self._long_opts.append(opt)
|
||||
self.prefixes.add(prefix)
|
||||
|
||||
if action is None:
|
||||
action = 'store'
|
||||
|
||||
self.dest = dest
|
||||
self.action = action
|
||||
self.nargs = nargs
|
||||
self.const = const
|
||||
self.obj = obj
|
||||
|
||||
@property
|
||||
def takes_value(self):
|
||||
return self.action in ('store', 'append')
|
||||
|
||||
def process(self, value, state):
|
||||
if self.action == 'store':
|
||||
state.opts[self.dest] = value
|
||||
elif self.action == 'store_const':
|
||||
state.opts[self.dest] = self.const
|
||||
elif self.action == 'append':
|
||||
state.opts.setdefault(self.dest, []).append(value)
|
||||
elif self.action == 'append_const':
|
||||
state.opts.setdefault(self.dest, []).append(self.const)
|
||||
elif self.action == 'count':
|
||||
state.opts[self.dest] = state.opts.get(self.dest, 0) + 1
|
||||
else:
|
||||
raise ValueError('unknown action %r' % self.action)
|
||||
state.order.append(self.obj)
|
||||
|
||||
|
||||
class Argument(object):
|
||||
|
||||
def __init__(self, dest, nargs=1, obj=None):
|
||||
self.dest = dest
|
||||
self.nargs = nargs
|
||||
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)
|
||||
|
||||
|
||||
class ParsingState(object):
|
||||
|
||||
def __init__(self, rargs):
|
||||
self.opts = {}
|
||||
self.largs = []
|
||||
self.rargs = rargs
|
||||
self.order = []
|
||||
|
||||
|
||||
class OptionParser(object):
|
||||
"""The option parser is an internal class that is ultimately used to
|
||||
parse options and arguments. It's modelled after optparse and brings
|
||||
a similar but vastly simplified API. It should generally not be used
|
||||
directly as the high level Click classes wrap it for you.
|
||||
|
||||
It's not nearly as extensible as optparse or argparse as it does not
|
||||
implement features that are implemented on a higher level (such as
|
||||
types or defaults).
|
||||
|
||||
:param ctx: optionally the :class:`~click.Context` where this parser
|
||||
should go with.
|
||||
"""
|
||||
|
||||
def __init__(self, ctx=None):
|
||||
#: The :class:`~click.Context` for this parser. This might be
|
||||
#: `None` for some advanced use cases.
|
||||
self.ctx = ctx
|
||||
#: This controls how the parser deals with interspersed arguments.
|
||||
#: If this is set to `False`, the parser will stop on the first
|
||||
#: non-option. Click uses this to implement nested subcommands
|
||||
#: safely.
|
||||
self.allow_interspersed_args = True
|
||||
#: This tells the parser how to deal with unknown options. By
|
||||
#: default it will error out (which is sensible), but there is a
|
||||
#: second mode where it will ignore it and continue processing
|
||||
#: after shifting all the unknown options into the resulting args.
|
||||
self.ignore_unknown_options = False
|
||||
if ctx is not None:
|
||||
self.allow_interspersed_args = ctx.allow_interspersed_args
|
||||
self.ignore_unknown_options = ctx.ignore_unknown_options
|
||||
self._short_opt = {}
|
||||
self._long_opt = {}
|
||||
self._opt_prefixes = set(['-', '--'])
|
||||
self._args = []
|
||||
|
||||
def add_option(self, opts, dest, action=None, nargs=1, const=None,
|
||||
obj=None):
|
||||
"""Adds a new option named `dest` to the parser. The destination
|
||||
is not inferred (unlike with optparse) and needs to be explicitly
|
||||
provided. Action can be any of ``store``, ``store_const``,
|
||||
``append``, ``appnd_const`` or ``count``.
|
||||
|
||||
The `obj` can be used to identify the option in the order list
|
||||
that is returned from the parser.
|
||||
"""
|
||||
if obj is None:
|
||||
obj = dest
|
||||
opts = [normalize_opt(opt, self.ctx) for opt in opts]
|
||||
option = Option(opts, dest, action=action, nargs=nargs,
|
||||
const=const, obj=obj)
|
||||
self._opt_prefixes.update(option.prefixes)
|
||||
for opt in option._short_opts:
|
||||
self._short_opt[opt] = option
|
||||
for opt in option._long_opts:
|
||||
self._long_opt[opt] = option
|
||||
|
||||
def add_argument(self, dest, nargs=1, obj=None):
|
||||
"""Adds a positional argument named `dest` to the parser.
|
||||
|
||||
The `obj` can be used to identify the option in the order list
|
||||
that is returned from the parser.
|
||||
"""
|
||||
if obj is None:
|
||||
obj = dest
|
||||
self._args.append(Argument(dest=dest, nargs=nargs, obj=obj))
|
||||
|
||||
def parse_args(self, args):
|
||||
"""Parses positional arguments and returns ``(values, args, order)``
|
||||
for the parsed options and arguments as well as the leftover
|
||||
arguments if there are any. The order is a list of objects as they
|
||||
appear on the command line. If arguments appear multiple times they
|
||||
will be memorized multiple times as well.
|
||||
"""
|
||||
state = ParsingState(args)
|
||||
try:
|
||||
self._process_args_for_options(state)
|
||||
self._process_args_for_args(state)
|
||||
except UsageError:
|
||||
if self.ctx is None or not self.ctx.resilient_parsing:
|
||||
raise
|
||||
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])
|
||||
|
||||
for idx, arg in enumerate(self._args):
|
||||
arg.process(pargs[idx], state)
|
||||
|
||||
state.largs = args
|
||||
state.rargs = []
|
||||
|
||||
def _process_args_for_options(self, state):
|
||||
while state.rargs:
|
||||
arg = state.rargs.pop(0)
|
||||
arglen = len(arg)
|
||||
# Double dashes always handled explicitly regardless of what
|
||||
# prefixes are valid.
|
||||
if arg == '--':
|
||||
return
|
||||
elif arg[:1] in self._opt_prefixes and arglen > 1:
|
||||
self._process_opts(arg, state)
|
||||
elif self.allow_interspersed_args:
|
||||
state.largs.append(arg)
|
||||
else:
|
||||
state.rargs.insert(0, arg)
|
||||
return
|
||||
|
||||
# Say this is the original argument list:
|
||||
# [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
|
||||
# ^
|
||||
# (we are about to process arg(i)).
|
||||
#
|
||||
# Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
|
||||
# [arg0, ..., arg(i-1)] (any options and their arguments will have
|
||||
# been removed from largs).
|
||||
#
|
||||
# The while loop will usually consume 1 or more arguments per pass.
|
||||
# If it consumes 1 (eg. arg is an option that takes no arguments),
|
||||
# then after _process_arg() is done the situation is:
|
||||
#
|
||||
# largs = subset of [arg0, ..., arg(i)]
|
||||
# rargs = [arg(i+1), ..., arg(N-1)]
|
||||
#
|
||||
# If allow_interspersed_args is false, largs will always be
|
||||
# *empty* -- still a subset of [arg0, ..., arg(i-1)], but
|
||||
# not a very interesting subset!
|
||||
|
||||
def _match_long_opt(self, opt, explicit_value, state):
|
||||
if opt not in self._long_opt:
|
||||
possibilities = [word for word in self._long_opt
|
||||
if word.startswith(opt)]
|
||||
raise NoSuchOption(opt, possibilities=possibilities)
|
||||
|
||||
option = self._long_opt[opt]
|
||||
if option.takes_value:
|
||||
# At this point it's safe to modify rargs by injecting the
|
||||
# explicit value, because no exception is raised in this
|
||||
# branch. This means that the inserted value will be fully
|
||||
# consumed.
|
||||
if explicit_value is not None:
|
||||
state.rargs.insert(0, explicit_value)
|
||||
|
||||
nargs = option.nargs
|
||||
if len(state.rargs) < nargs:
|
||||
_error_opt_args(nargs, opt)
|
||||
elif nargs == 1:
|
||||
value = state.rargs.pop(0)
|
||||
else:
|
||||
value = tuple(state.rargs[:nargs])
|
||||
del state.rargs[:nargs]
|
||||
|
||||
elif explicit_value is not None:
|
||||
raise BadOptionUsage('%s option does not take a value' % opt)
|
||||
|
||||
else:
|
||||
value = None
|
||||
|
||||
option.process(value, state)
|
||||
|
||||
def _match_short_opt(self, arg, state):
|
||||
stop = False
|
||||
i = 1
|
||||
prefix = arg[0]
|
||||
unknown_options = []
|
||||
|
||||
for ch in arg[1:]:
|
||||
opt = normalize_opt(prefix + ch, self.ctx)
|
||||
option = self._short_opt.get(opt)
|
||||
i += 1
|
||||
|
||||
if not option:
|
||||
if self.ignore_unknown_options:
|
||||
unknown_options.append(ch)
|
||||
continue
|
||||
raise NoSuchOption(opt)
|
||||
if option.takes_value:
|
||||
# Any characters left in arg? Pretend they're the
|
||||
# next arg, and stop consuming characters of arg.
|
||||
if i < len(arg):
|
||||
state.rargs.insert(0, arg[i:])
|
||||
stop = True
|
||||
|
||||
nargs = option.nargs
|
||||
if len(state.rargs) < nargs:
|
||||
_error_opt_args(nargs, opt)
|
||||
elif nargs == 1:
|
||||
value = state.rargs.pop(0)
|
||||
else:
|
||||
value = tuple(state.rargs[:nargs])
|
||||
del state.rargs[:nargs]
|
||||
|
||||
else:
|
||||
value = None
|
||||
|
||||
option.process(value, state)
|
||||
|
||||
if stop:
|
||||
break
|
||||
|
||||
# If we got any unknown options we re-combinate the string of the
|
||||
# remaining options and re-attach the prefix, then report that
|
||||
# to the state as new larg. This way there is basic combinatorics
|
||||
# that can be achieved while still ignoring unknown arguments.
|
||||
if self.ignore_unknown_options and unknown_options:
|
||||
state.largs.append(prefix + ''.join(unknown_options))
|
||||
|
||||
def _process_opts(self, arg, state):
|
||||
explicit_value = None
|
||||
# Long option handling happens in two parts. The first part is
|
||||
# supporting explicitly attached values. In any case, we will try
|
||||
# to long match the option first.
|
||||
if '=' in arg:
|
||||
long_opt, explicit_value = arg.split('=', 1)
|
||||
else:
|
||||
long_opt = arg
|
||||
norm_long_opt = normalize_opt(long_opt, self.ctx)
|
||||
|
||||
# At this point we will match the (assumed) long option through
|
||||
# the long option matching code. Note that this allows options
|
||||
# like "-foo" to be matched as long options.
|
||||
try:
|
||||
self._match_long_opt(norm_long_opt, explicit_value, state)
|
||||
except NoSuchOption:
|
||||
# At this point the long option matching failed, and we need
|
||||
# to try with short options. However there is a special rule
|
||||
# which says, that if we have a two character options prefix
|
||||
# (applies to "--foo" for instance), we do not dispatch to the
|
||||
# short option code and will instead raise the no option
|
||||
# error.
|
||||
if arg[:2] not in self._opt_prefixes:
|
||||
return self._match_short_opt(arg, state)
|
||||
if not self.ignore_unknown_options:
|
||||
raise
|
||||
state.largs.append(arg)
|
||||
Vendored
-539
@@ -1,539 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import struct
|
||||
|
||||
from ._compat import raw_input, text_type, string_types, \
|
||||
isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN
|
||||
from .utils import echo
|
||||
from .exceptions import Abort, UsageError
|
||||
from .types import convert_type
|
||||
from .globals import resolve_color_default
|
||||
|
||||
|
||||
# The prompt functions to use. The doc tools currently override these
|
||||
# functions to customize how they work.
|
||||
visible_prompt_func = raw_input
|
||||
|
||||
_ansi_colors = ('black', 'red', 'green', 'yellow', 'blue', 'magenta',
|
||||
'cyan', 'white', 'reset')
|
||||
_ansi_reset_all = '\033[0m'
|
||||
|
||||
|
||||
def hidden_prompt_func(prompt):
|
||||
import getpass
|
||||
return getpass.getpass(prompt)
|
||||
|
||||
|
||||
def _build_prompt(text, suffix, show_default=False, default=None):
|
||||
prompt = text
|
||||
if default is not None and show_default:
|
||||
prompt = '%s [%s]' % (prompt, default)
|
||||
return prompt + suffix
|
||||
|
||||
|
||||
def prompt(text, default=None, hide_input=False,
|
||||
confirmation_prompt=False, type=None,
|
||||
value_proc=None, prompt_suffix=': ',
|
||||
show_default=True, err=False):
|
||||
"""Prompts a user for input. This is a convenience function that can
|
||||
be used to prompt a user for input later.
|
||||
|
||||
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.
|
||||
|
||||
:param text: the text to show for the prompt.
|
||||
:param default: the default value to use if no input happens. If this
|
||||
is not given it will prompt until it's aborted.
|
||||
:param hide_input: if this is set to true then the input value will
|
||||
be hidden.
|
||||
:param confirmation_prompt: asks for confirmation for the value.
|
||||
:param type: the type to use to check the value against.
|
||||
:param value_proc: if this parameter is provided it's a function that
|
||||
is invoked instead of the type conversion to
|
||||
convert a value.
|
||||
:param prompt_suffix: a suffix that should be added to the prompt.
|
||||
:param show_default: shows or hides the default value in the prompt.
|
||||
:param err: if set to true the file defaults to ``stderr`` instead of
|
||||
``stdout``, the same as with echo.
|
||||
"""
|
||||
result = None
|
||||
|
||||
def prompt_func(text):
|
||||
f = hide_input and hidden_prompt_func or visible_prompt_func
|
||||
try:
|
||||
# Write the prompt separately so that we get nice
|
||||
# coloring through colorama on Windows
|
||||
echo(text, nl=False, err=err)
|
||||
return f('')
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
# getpass doesn't print a newline if the user aborts input with ^C.
|
||||
# Allegedly this behavior is inherited from getpass(3).
|
||||
# A doc bug has been filed at https://bugs.python.org/issue24711
|
||||
if hide_input:
|
||||
echo(None, err=err)
|
||||
raise Abort()
|
||||
|
||||
if value_proc is None:
|
||||
value_proc = convert_type(type, default)
|
||||
|
||||
prompt = _build_prompt(text, prompt_suffix, show_default, default)
|
||||
|
||||
while 1:
|
||||
while 1:
|
||||
value = prompt_func(prompt)
|
||||
if value:
|
||||
break
|
||||
# If a default is set and used, then the confirmation
|
||||
# prompt is always skipped because that's the only thing
|
||||
# that really makes sense.
|
||||
elif default is not None:
|
||||
return default
|
||||
try:
|
||||
result = value_proc(value)
|
||||
except UsageError as e:
|
||||
echo('Error: %s' % e.message, err=err)
|
||||
continue
|
||||
if not confirmation_prompt:
|
||||
return result
|
||||
while 1:
|
||||
value2 = prompt_func('Repeat for confirmation: ')
|
||||
if value2:
|
||||
break
|
||||
if value == value2:
|
||||
return result
|
||||
echo('Error: the two entered values do not match', err=err)
|
||||
|
||||
|
||||
def confirm(text, default=False, abort=False, prompt_suffix=': ',
|
||||
show_default=True, err=False):
|
||||
"""Prompts for confirmation (yes/no question).
|
||||
|
||||
If the user aborts the input by sending a interrupt signal this
|
||||
function will catch it and raise a :exc:`Abort` exception.
|
||||
|
||||
.. versionadded:: 4.0
|
||||
Added the `err` parameter.
|
||||
|
||||
:param text: the question to ask.
|
||||
:param default: the default for the prompt.
|
||||
:param abort: if this is set to `True` a negative answer aborts the
|
||||
exception by raising :exc:`Abort`.
|
||||
:param prompt_suffix: a suffix that should be added to the prompt.
|
||||
:param show_default: shows or hides the default value in the prompt.
|
||||
:param err: if set to true the file defaults to ``stderr`` instead of
|
||||
``stdout``, the same as with echo.
|
||||
"""
|
||||
prompt = _build_prompt(text, prompt_suffix, show_default,
|
||||
default and 'Y/n' or 'y/N')
|
||||
while 1:
|
||||
try:
|
||||
# Write the prompt separately so that we get nice
|
||||
# coloring through colorama on Windows
|
||||
echo(prompt, nl=False, err=err)
|
||||
value = visible_prompt_func('').lower().strip()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
raise Abort()
|
||||
if value in ('y', 'yes'):
|
||||
rv = True
|
||||
elif value in ('n', 'no'):
|
||||
rv = False
|
||||
elif value == '':
|
||||
rv = default
|
||||
else:
|
||||
echo('Error: invalid input', err=err)
|
||||
continue
|
||||
break
|
||||
if abort and not rv:
|
||||
raise Abort()
|
||||
return rv
|
||||
|
||||
|
||||
def get_terminal_size():
|
||||
"""Returns the current size of the terminal as tuple in the form
|
||||
``(width, height)`` in columns and rows.
|
||||
"""
|
||||
# If shutil has get_terminal_size() (Python 3.3 and later) use that
|
||||
if sys.version_info >= (3, 3):
|
||||
import shutil
|
||||
shutil_get_terminal_size = getattr(shutil, 'get_terminal_size', None)
|
||||
if shutil_get_terminal_size:
|
||||
sz = shutil_get_terminal_size()
|
||||
return sz.columns, sz.lines
|
||||
|
||||
if get_winterm_size is not None:
|
||||
return get_winterm_size()
|
||||
|
||||
def ioctl_gwinsz(fd):
|
||||
try:
|
||||
import fcntl
|
||||
import termios
|
||||
cr = struct.unpack(
|
||||
'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
|
||||
except Exception:
|
||||
return
|
||||
return cr
|
||||
|
||||
cr = ioctl_gwinsz(0) or ioctl_gwinsz(1) or ioctl_gwinsz(2)
|
||||
if not cr:
|
||||
try:
|
||||
fd = os.open(os.ctermid(), os.O_RDONLY)
|
||||
try:
|
||||
cr = ioctl_gwinsz(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
except Exception:
|
||||
pass
|
||||
if not cr or not cr[0] or not cr[1]:
|
||||
cr = (os.environ.get('LINES', 25),
|
||||
os.environ.get('COLUMNS', DEFAULT_COLUMNS))
|
||||
return int(cr[1]), int(cr[0])
|
||||
|
||||
|
||||
def echo_via_pager(text, color=None):
|
||||
"""This function takes a text and shows it via an environment specific
|
||||
pager on stdout.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Added the `color` flag.
|
||||
|
||||
:param text: the text to page.
|
||||
:param color: controls if the pager supports ANSI colors or not. The
|
||||
default is autodetection.
|
||||
"""
|
||||
color = resolve_color_default(color)
|
||||
if not isinstance(text, string_types):
|
||||
text = text_type(text)
|
||||
from ._termui_impl import pager
|
||||
return pager(text + '\n', color)
|
||||
|
||||
|
||||
def progressbar(iterable=None, length=None, label=None, show_eta=True,
|
||||
show_percent=None, show_pos=False,
|
||||
item_show_func=None, fill_char='#', empty_char='-',
|
||||
bar_template='%(label)s [%(bar)s] %(info)s',
|
||||
info_sep=' ', width=36, file=None, color=None):
|
||||
"""This function creates an iterable context manager that can be used
|
||||
to iterate over something while showing a progress bar. It will
|
||||
either iterate over the `iterable` or `length` items (that are counted
|
||||
up). While iteration happens, this function will print a rendered
|
||||
progress bar to the given `file` (defaults to stdout) and will attempt
|
||||
to calculate remaining time and more. By default, this progress bar
|
||||
will not be rendered if the file is not a terminal.
|
||||
|
||||
The context manager creates the progress bar. When the context
|
||||
manager is entered the progress bar is already displayed. With every
|
||||
iteration over the progress bar, the iterable passed to the bar is
|
||||
advanced and the bar is updated. When the context manager exits,
|
||||
a newline is printed and the progress bar is finalized on screen.
|
||||
|
||||
No printing must happen or the progress bar will be unintentionally
|
||||
destroyed.
|
||||
|
||||
Example usage::
|
||||
|
||||
with progressbar(items) as bar:
|
||||
for item in bar:
|
||||
do_something_with(item)
|
||||
|
||||
Alternatively, if no iterable is specified, one can manually update the
|
||||
progress bar through the `update()` method instead of directly
|
||||
iterating over the progress bar. The update method accepts the number
|
||||
of steps to increment the bar with::
|
||||
|
||||
with progressbar(length=chunks.total_bytes) as bar:
|
||||
for chunk in chunks:
|
||||
process_chunk(chunk)
|
||||
bar.update(chunks.bytes)
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
.. versionadded:: 4.0
|
||||
Added the `color` parameter. Added a `update` method to the
|
||||
progressbar object.
|
||||
|
||||
:param iterable: an iterable to iterate over. If not provided the length
|
||||
is required.
|
||||
:param length: the number of items to iterate over. By default the
|
||||
progressbar will attempt to ask the iterator about its
|
||||
length, which might or might not work. If an iterable is
|
||||
also provided this parameter can be used to override the
|
||||
length. If an iterable is not provided the progress bar
|
||||
will iterate over a range of that length.
|
||||
:param label: the label to show next to the progress bar.
|
||||
:param show_eta: enables or disables the estimated time display. This is
|
||||
automatically disabled if the length cannot be
|
||||
determined.
|
||||
:param show_percent: enables or disables the percentage display. The
|
||||
default is `True` if the iterable has a length or
|
||||
`False` if not.
|
||||
:param show_pos: enables or disables the absolute position display. The
|
||||
default is `False`.
|
||||
:param item_show_func: a function called with the current item which
|
||||
can return a string to show the current item
|
||||
next to the progress bar. Note that the current
|
||||
item can be `None`!
|
||||
:param fill_char: the character to use to show the filled part of the
|
||||
progress bar.
|
||||
:param empty_char: the character to use to show the non-filled part of
|
||||
the progress bar.
|
||||
:param bar_template: the format string to use as template for the bar.
|
||||
The parameters in it are ``label`` for the label,
|
||||
``bar`` for the progress bar and ``info`` for the
|
||||
info section.
|
||||
:param info_sep: the separator between multiple info items (eta etc.)
|
||||
:param width: the width of the progress bar in characters, 0 means full
|
||||
terminal width
|
||||
:param file: the file to write to. If this is not a terminal then
|
||||
only the label is printed.
|
||||
:param color: controls if the terminal supports ANSI colors or not. The
|
||||
default is autodetection. This is only needed if ANSI
|
||||
codes are included anywhere in the progress bar output
|
||||
which is not the case by default.
|
||||
"""
|
||||
from ._termui_impl import ProgressBar
|
||||
color = resolve_color_default(color)
|
||||
return ProgressBar(iterable=iterable, length=length, show_eta=show_eta,
|
||||
show_percent=show_percent, show_pos=show_pos,
|
||||
item_show_func=item_show_func, fill_char=fill_char,
|
||||
empty_char=empty_char, bar_template=bar_template,
|
||||
info_sep=info_sep, file=file, label=label,
|
||||
width=width, color=color)
|
||||
|
||||
|
||||
def clear():
|
||||
"""Clears the terminal screen. This will have the effect of clearing
|
||||
the whole visible space of the terminal and moving the cursor to the
|
||||
top left. This does not do anything if not connected to a terminal.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
if not isatty(sys.stdout):
|
||||
return
|
||||
# If we're on Windows and we don't have colorama available, then we
|
||||
# clear the screen by shelling out. Otherwise we can use an escape
|
||||
# sequence.
|
||||
if WIN:
|
||||
os.system('cls')
|
||||
else:
|
||||
sys.stdout.write('\033[2J\033[1;1H')
|
||||
|
||||
|
||||
def style(text, fg=None, bg=None, bold=None, dim=None, underline=None,
|
||||
blink=None, reverse=None, reset=True):
|
||||
"""Styles a text with ANSI styles and returns the new string. By
|
||||
default the styling is self contained which means that at the end
|
||||
of the string a reset code is issued. This can be prevented by
|
||||
passing ``reset=False``.
|
||||
|
||||
Examples::
|
||||
|
||||
click.echo(click.style('Hello World!', fg='green'))
|
||||
click.echo(click.style('ATTENTION!', blink=True))
|
||||
click.echo(click.style('Some things', reverse=True, fg='cyan'))
|
||||
|
||||
Supported color names:
|
||||
|
||||
* ``black`` (might be a gray)
|
||||
* ``red``
|
||||
* ``green``
|
||||
* ``yellow`` (might be an orange)
|
||||
* ``blue``
|
||||
* ``magenta``
|
||||
* ``cyan``
|
||||
* ``white`` (might be light gray)
|
||||
* ``reset`` (reset the color code only)
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param text: the string to style with ansi codes.
|
||||
:param fg: if provided this will become the foreground color.
|
||||
:param bg: if provided this will become the background color.
|
||||
:param bold: if provided this will enable or disable bold mode.
|
||||
:param dim: if provided this will enable or disable dim mode. This is
|
||||
badly supported.
|
||||
:param underline: if provided this will enable or disable underline.
|
||||
:param blink: if provided this will enable or disable blinking.
|
||||
:param reverse: if provided this will enable or disable inverse
|
||||
rendering (foreground becomes background and the
|
||||
other way round).
|
||||
:param reset: by default a reset-all code is added at the end of the
|
||||
string which means that styles do not carry over. This
|
||||
can be disabled to compose styles.
|
||||
"""
|
||||
bits = []
|
||||
if fg:
|
||||
try:
|
||||
bits.append('\033[%dm' % (_ansi_colors.index(fg) + 30))
|
||||
except ValueError:
|
||||
raise TypeError('Unknown color %r' % fg)
|
||||
if bg:
|
||||
try:
|
||||
bits.append('\033[%dm' % (_ansi_colors.index(bg) + 40))
|
||||
except ValueError:
|
||||
raise TypeError('Unknown color %r' % bg)
|
||||
if bold is not None:
|
||||
bits.append('\033[%dm' % (1 if bold else 22))
|
||||
if dim is not None:
|
||||
bits.append('\033[%dm' % (2 if dim else 22))
|
||||
if underline is not None:
|
||||
bits.append('\033[%dm' % (4 if underline else 24))
|
||||
if blink is not None:
|
||||
bits.append('\033[%dm' % (5 if blink else 25))
|
||||
if reverse is not None:
|
||||
bits.append('\033[%dm' % (7 if reverse else 27))
|
||||
bits.append(text)
|
||||
if reset:
|
||||
bits.append(_ansi_reset_all)
|
||||
return ''.join(bits)
|
||||
|
||||
|
||||
def unstyle(text):
|
||||
"""Removes ANSI styling information from a string. Usually it's not
|
||||
necessary to use this function as Click's echo function will
|
||||
automatically remove styling if necessary.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param text: the text to remove style information from.
|
||||
"""
|
||||
return strip_ansi(text)
|
||||
|
||||
|
||||
def secho(text, file=None, nl=True, err=False, color=None, **styles):
|
||||
"""This function combines :func:`echo` and :func:`style` into one
|
||||
call. As such the following two calls are the same::
|
||||
|
||||
click.secho('Hello World!', fg='green')
|
||||
click.echo(click.style('Hello World!', fg='green'))
|
||||
|
||||
All keyword arguments are forwarded to the underlying functions
|
||||
depending on which one they go with.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
return echo(style(text, **styles), file=file, nl=nl, err=err, color=color)
|
||||
|
||||
|
||||
def edit(text=None, editor=None, env=None, require_save=True,
|
||||
extension='.txt', filename=None):
|
||||
r"""Edits the given text in the defined editor. If an editor is given
|
||||
(should be the full path to the executable but the regular operating
|
||||
system search path is used for finding the executable) it overrides
|
||||
the detected editor. Optionally, some environment variables can be
|
||||
used. If the editor is closed without changes, `None` is returned. In
|
||||
case a file is edited directly the return value is always `None` and
|
||||
`require_save` and `extension` are ignored.
|
||||
|
||||
If the editor cannot be opened a :exc:`UsageError` is raised.
|
||||
|
||||
Note for Windows: to simplify cross-platform usage, the newlines are
|
||||
automatically converted from POSIX to Windows and vice versa. As such,
|
||||
the message here will have ``\n`` as newline markers.
|
||||
|
||||
:param text: the text to edit.
|
||||
:param editor: optionally the editor to use. Defaults to automatic
|
||||
detection.
|
||||
:param env: environment variables to forward to the editor.
|
||||
:param require_save: if this is true, then not saving in the editor
|
||||
will make the return value become `None`.
|
||||
:param extension: the extension to tell the editor about. This defaults
|
||||
to `.txt` but changing this might change syntax
|
||||
highlighting.
|
||||
:param filename: if provided it will edit this file instead of the
|
||||
provided text contents. It will not use a temporary
|
||||
file as an indirection in that case.
|
||||
"""
|
||||
from ._termui_impl import Editor
|
||||
editor = Editor(editor=editor, env=env, require_save=require_save,
|
||||
extension=extension)
|
||||
if filename is None:
|
||||
return editor.edit(text)
|
||||
editor.edit_file(filename)
|
||||
|
||||
|
||||
def launch(url, wait=False, locate=False):
|
||||
"""This function launches the given URL (or filename) in the default
|
||||
viewer application for this file type. If this is an executable, it
|
||||
might launch the executable in a new session. The return value is
|
||||
the exit code of the launched application. Usually, ``0`` indicates
|
||||
success.
|
||||
|
||||
Examples::
|
||||
|
||||
click.launch('http://click.pocoo.org/')
|
||||
click.launch('/my/downloaded/file', locate=True)
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param url: URL or filename of the thing to launch.
|
||||
:param wait: waits for the program to stop.
|
||||
:param locate: if this is set to `True` then instead of launching the
|
||||
application associated with the URL it will attempt to
|
||||
launch a file manager with the file located. This
|
||||
might have weird effects if the URL does not point to
|
||||
the filesystem.
|
||||
"""
|
||||
from ._termui_impl import open_url
|
||||
return open_url(url, wait=wait, locate=locate)
|
||||
|
||||
|
||||
# If this is provided, getchar() calls into this instead. This is used
|
||||
# for unittesting purposes.
|
||||
_getchar = None
|
||||
|
||||
|
||||
def getchar(echo=False):
|
||||
"""Fetches a single character from the terminal and returns it. This
|
||||
will always return a unicode character and under certain rare
|
||||
circumstances this might return more than one character. The
|
||||
situations which more than one character is returned is when for
|
||||
whatever reason multiple characters end up in the terminal buffer or
|
||||
standard input was not actually a terminal.
|
||||
|
||||
Note that this will always read from the terminal, even if something
|
||||
is piped into the standard input.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param echo: if set to `True`, the character read will also show up on
|
||||
the terminal. The default is to not show it.
|
||||
"""
|
||||
f = _getchar
|
||||
if f is None:
|
||||
from ._termui_impl import getchar as f
|
||||
return f(echo)
|
||||
|
||||
|
||||
def pause(info='Press any key to continue ...', err=False):
|
||||
"""This command stops execution and waits for the user to press any
|
||||
key to continue. This is similar to the Windows batch "pause"
|
||||
command. If the program is not run through a terminal, this command
|
||||
will instead do nothing.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
.. versionadded:: 4.0
|
||||
Added the `err` parameter.
|
||||
|
||||
:param info: the info string to print before pausing.
|
||||
:param err: if set to message goes to ``stderr`` instead of
|
||||
``stdout``, the same as with echo.
|
||||
"""
|
||||
if not isatty(sys.stdin) or not isatty(sys.stdout):
|
||||
return
|
||||
try:
|
||||
if info:
|
||||
echo(info, nl=False, err=err)
|
||||
try:
|
||||
getchar()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
pass
|
||||
finally:
|
||||
if info:
|
||||
echo(err=err)
|
||||
Vendored
-322
@@ -1,322 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import tempfile
|
||||
import contextlib
|
||||
|
||||
from ._compat import iteritems, 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.
|
||||
clickpkg = sys.modules[__name__.rsplit('.', 1)[0]]
|
||||
|
||||
|
||||
if PY2:
|
||||
from cStringIO import StringIO
|
||||
else:
|
||||
import io
|
||||
from ._compat import _find_binary_reader
|
||||
|
||||
|
||||
class EchoingStdin(object):
|
||||
|
||||
def __init__(self, input, output):
|
||||
self._input = input
|
||||
self._output = output
|
||||
|
||||
def __getattr__(self, x):
|
||||
return getattr(self._input, x)
|
||||
|
||||
def _echo(self, rv):
|
||||
self._output.write(rv)
|
||||
return rv
|
||||
|
||||
def read(self, n=-1):
|
||||
return self._echo(self._input.read(n))
|
||||
|
||||
def readline(self, n=-1):
|
||||
return self._echo(self._input.readline(n))
|
||||
|
||||
def readlines(self):
|
||||
return [self._echo(x) for x in self._input.readlines()]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._echo(x) for x in self._input)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._input)
|
||||
|
||||
|
||||
def make_input_stream(input, charset):
|
||||
# Is already an input stream.
|
||||
if hasattr(input, 'read'):
|
||||
if PY2:
|
||||
return input
|
||||
rv = _find_binary_reader(input)
|
||||
if rv is not None:
|
||||
return rv
|
||||
raise TypeError('Could not find binary reader for input stream.')
|
||||
|
||||
if input is None:
|
||||
input = b''
|
||||
elif not isinstance(input, bytes):
|
||||
input = input.encode(charset)
|
||||
if PY2:
|
||||
return StringIO(input)
|
||||
return io.BytesIO(input)
|
||||
|
||||
|
||||
class Result(object):
|
||||
"""Holds the captured result of an invoked CLI script."""
|
||||
|
||||
def __init__(self, runner, output_bytes, exit_code, exception,
|
||||
exc_info=None):
|
||||
#: The runner that created the result
|
||||
self.runner = runner
|
||||
#: The output as bytes.
|
||||
self.output_bytes = output_bytes
|
||||
#: The exit code as integer.
|
||||
self.exit_code = exit_code
|
||||
#: The exception that happend if one did.
|
||||
self.exception = exception
|
||||
#: The traceback
|
||||
self.exc_info = exc_info
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
"""The output as unicode string."""
|
||||
return self.output_bytes.decode(self.runner.charset, 'replace') \
|
||||
.replace('\r\n', '\n')
|
||||
|
||||
def __repr__(self):
|
||||
return '<Result %s>' % (
|
||||
self.exception and repr(self.exception) or 'okay',
|
||||
)
|
||||
|
||||
|
||||
class CliRunner(object):
|
||||
"""The CLI runner provides functionality to invoke a Click command line
|
||||
script for unittesting purposes in a isolated environment. This only
|
||||
works in single-threaded systems without any concurrency as it changes the
|
||||
global interpreter state.
|
||||
|
||||
:param charset: the character set for the input and output data. This is
|
||||
UTF-8 by default and should not be changed currently as
|
||||
the reporting to Click only works in Python 2 properly.
|
||||
:param env: a dictionary with environment variables for overriding.
|
||||
:param echo_stdin: if this is set to `True`, then reading from stdin writes
|
||||
to stdout. This is useful for showing examples in
|
||||
some circumstances. Note that regular prompts
|
||||
will automatically echo the input.
|
||||
"""
|
||||
|
||||
def __init__(self, charset=None, env=None, echo_stdin=False):
|
||||
if charset is None:
|
||||
charset = 'utf-8'
|
||||
self.charset = charset
|
||||
self.env = env or {}
|
||||
self.echo_stdin = echo_stdin
|
||||
|
||||
def get_default_prog_name(self, cli):
|
||||
"""Given a command object it will return the default program name
|
||||
for it. The default is the `name` attribute or ``"root"`` if not
|
||||
set.
|
||||
"""
|
||||
return cli.name or 'root'
|
||||
|
||||
def make_env(self, overrides=None):
|
||||
"""Returns the environment overrides for invoking a script."""
|
||||
rv = dict(self.env)
|
||||
if overrides:
|
||||
rv.update(overrides)
|
||||
return rv
|
||||
|
||||
@contextlib.contextmanager
|
||||
def isolation(self, input=None, env=None, color=False):
|
||||
"""A context manager that sets up the isolation for invoking of a
|
||||
command line tool. This sets up stdin with the given input data
|
||||
and `os.environ` with the overrides from the given dictionary.
|
||||
This also rebinds some internals in Click to be mocked (like the
|
||||
prompt functionality).
|
||||
|
||||
This is automatically done in the :meth:`invoke` method.
|
||||
|
||||
.. versionadded:: 4.0
|
||||
The ``color`` parameter was added.
|
||||
|
||||
:param input: the input stream to put into sys.stdin.
|
||||
:param env: the environment overrides as dictionary.
|
||||
:param color: whether the output should contain color codes. The
|
||||
application can still override this explicitly.
|
||||
"""
|
||||
input = make_input_stream(input, self.charset)
|
||||
|
||||
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)
|
||||
|
||||
if PY2:
|
||||
sys.stdout = sys.stderr = bytes_output = StringIO()
|
||||
if self.echo_stdin:
|
||||
input = EchoingStdin(input, bytes_output)
|
||||
else:
|
||||
bytes_output = io.BytesIO()
|
||||
if self.echo_stdin:
|
||||
input = EchoingStdin(input, bytes_output)
|
||||
input = io.TextIOWrapper(input, encoding=self.charset)
|
||||
sys.stdout = sys.stderr = io.TextIOWrapper(
|
||||
bytes_output, encoding=self.charset)
|
||||
|
||||
sys.stdin = input
|
||||
|
||||
def visible_input(prompt=None):
|
||||
sys.stdout.write(prompt or '')
|
||||
val = input.readline().rstrip('\r\n')
|
||||
sys.stdout.write(val + '\n')
|
||||
sys.stdout.flush()
|
||||
return val
|
||||
|
||||
def hidden_input(prompt=None):
|
||||
sys.stdout.write((prompt or '') + '\n')
|
||||
sys.stdout.flush()
|
||||
return input.readline().rstrip('\r\n')
|
||||
|
||||
def _getchar(echo):
|
||||
char = sys.stdin.read(1)
|
||||
if echo:
|
||||
sys.stdout.write(char)
|
||||
sys.stdout.flush()
|
||||
return char
|
||||
|
||||
default_color = color
|
||||
def should_strip_ansi(stream=None, color=None):
|
||||
if color is None:
|
||||
return not default_color
|
||||
return not color
|
||||
|
||||
old_visible_prompt_func = clickpkg.termui.visible_prompt_func
|
||||
old_hidden_prompt_func = clickpkg.termui.hidden_prompt_func
|
||||
old__getchar_func = clickpkg.termui._getchar
|
||||
old_should_strip_ansi = clickpkg.utils.should_strip_ansi
|
||||
clickpkg.termui.visible_prompt_func = visible_input
|
||||
clickpkg.termui.hidden_prompt_func = hidden_input
|
||||
clickpkg.termui._getchar = _getchar
|
||||
clickpkg.utils.should_strip_ansi = should_strip_ansi
|
||||
|
||||
old_env = {}
|
||||
try:
|
||||
for key, value in iteritems(env):
|
||||
old_env[key] = os.environ.get(key)
|
||||
if value is None:
|
||||
try:
|
||||
del os.environ[key]
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
os.environ[key] = value
|
||||
yield bytes_output
|
||||
finally:
|
||||
for key, value in iteritems(old_env):
|
||||
if value is None:
|
||||
try:
|
||||
del os.environ[key]
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
os.environ[key] = value
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
sys.stdin = old_stdin
|
||||
clickpkg.termui.visible_prompt_func = old_visible_prompt_func
|
||||
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):
|
||||
"""Invokes a command in an isolated environment. The arguments are
|
||||
forwarded directly to the command line script, the `extra` keyword
|
||||
arguments are passed to the :meth:`~clickpkg.Command.main` function of
|
||||
the command.
|
||||
|
||||
This returns a :class:`Result` object.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
The ``catch_exceptions`` parameter was added.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The result object now has an `exc_info` attribute with the
|
||||
traceback if available.
|
||||
|
||||
.. versionadded:: 4.0
|
||||
The ``color`` parameter was added.
|
||||
|
||||
:param cli: the command to invoke
|
||||
:param args: the arguments to invoke
|
||||
:param input: the input data for `sys.stdin`.
|
||||
:param env: the environment overrides.
|
||||
:param catch_exceptions: Whether to catch any other exceptions than
|
||||
``SystemExit``.
|
||||
:param extra: the keyword arguments to pass to :meth:`main`.
|
||||
:param color: whether the output should contain color codes. The
|
||||
application can still override this explicitly.
|
||||
"""
|
||||
exc_info = None
|
||||
with self.isolation(input=input, env=env, color=color) as out:
|
||||
exception = None
|
||||
exit_code = 0
|
||||
|
||||
try:
|
||||
cli.main(args=args or (),
|
||||
prog_name=self.get_default_prog_name(cli), **extra)
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
exception = e
|
||||
|
||||
exc_info = sys.exc_info()
|
||||
|
||||
exit_code = e.code
|
||||
if not isinstance(exit_code, int):
|
||||
sys.stdout.write(str(exit_code))
|
||||
sys.stdout.write('\n')
|
||||
exit_code = 1
|
||||
except Exception as e:
|
||||
if not catch_exceptions:
|
||||
raise
|
||||
exception = e
|
||||
exit_code = -1
|
||||
exc_info = sys.exc_info()
|
||||
finally:
|
||||
sys.stdout.flush()
|
||||
output = out.getvalue()
|
||||
|
||||
return Result(runner=self,
|
||||
output_bytes=output,
|
||||
exit_code=exit_code,
|
||||
exception=exception,
|
||||
exc_info=exc_info)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def isolated_filesystem(self):
|
||||
"""A context manager that creates a temporary folder and changes
|
||||
the current working directory to it for isolated filesystem tests.
|
||||
"""
|
||||
cwd = os.getcwd()
|
||||
t = tempfile.mkdtemp()
|
||||
os.chdir(t)
|
||||
try:
|
||||
yield t
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
try:
|
||||
shutil.rmtree(t)
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
Vendored
-550
@@ -1,550 +0,0 @@
|
||||
import os
|
||||
import stat
|
||||
|
||||
from ._compat import open_stream, text_type, filename_to_ui, \
|
||||
get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2
|
||||
from .exceptions import BadParameter
|
||||
from .utils import safecall, LazyFile
|
||||
|
||||
|
||||
class ParamType(object):
|
||||
"""Helper for converting values through types. The following is
|
||||
necessary for a valid type:
|
||||
|
||||
* it needs a name
|
||||
* it needs to pass through None unchanged
|
||||
* it needs to convert from a string
|
||||
* it needs to convert its result type through unchanged
|
||||
(eg: needs to be idempotent)
|
||||
* it needs to be able to deal with param and context being `None`.
|
||||
This can be the case when the object is used with prompt
|
||||
inputs.
|
||||
"""
|
||||
is_composite = False
|
||||
|
||||
#: the descriptive name of this type
|
||||
name = None
|
||||
|
||||
#: if a list of this type is expected and the value is pulled from a
|
||||
#: string environment variable, this is what splits it up. `None`
|
||||
#: means any whitespace. For all parameters the general rule is that
|
||||
#: whitespace splits them up. The exception are paths and files which
|
||||
#: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on
|
||||
#: Windows).
|
||||
envvar_list_splitter = None
|
||||
|
||||
def __call__(self, value, param=None, ctx=None):
|
||||
if value is not None:
|
||||
return self.convert(value, param, ctx)
|
||||
|
||||
def get_metavar(self, param):
|
||||
"""Returns the metavar default for this param if it provides one."""
|
||||
|
||||
def get_missing_message(self, param):
|
||||
"""Optionally might return extra information about a missing
|
||||
parameter.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
"""Converts the value. This is not invoked for values that are
|
||||
`None` (the missing value).
|
||||
"""
|
||||
return value
|
||||
|
||||
def split_envvar_value(self, rv):
|
||||
"""Given a value from an environment variable this splits it up
|
||||
into small chunks depending on the defined envvar list splitter.
|
||||
|
||||
If the splitter is set to `None`, which means that whitespace splits,
|
||||
then leading and trailing whitespace is ignored. Otherwise, leading
|
||||
and trailing splitters usually lead to empty items being included.
|
||||
"""
|
||||
return (rv or '').split(self.envvar_list_splitter)
|
||||
|
||||
def fail(self, message, param=None, ctx=None):
|
||||
"""Helper method to fail with an invalid value message."""
|
||||
raise BadParameter(message, ctx=ctx, param=param)
|
||||
|
||||
|
||||
class CompositeParamType(ParamType):
|
||||
is_composite = True
|
||||
|
||||
@property
|
||||
def arity(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class FuncParamType(ParamType):
|
||||
|
||||
def __init__(self, func):
|
||||
self.name = func.__name__
|
||||
self.func = func
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return self.func(value)
|
||||
except ValueError:
|
||||
try:
|
||||
value = text_type(value)
|
||||
except UnicodeError:
|
||||
value = str(value).decode('utf-8', 'replace')
|
||||
self.fail(value, param, ctx)
|
||||
|
||||
|
||||
class UnprocessedParamType(ParamType):
|
||||
name = 'text'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
return value
|
||||
|
||||
def __repr__(self):
|
||||
return 'UNPROCESSED'
|
||||
|
||||
|
||||
class StringParamType(ParamType):
|
||||
name = 'text'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
if isinstance(value, bytes):
|
||||
enc = _get_argv_encoding()
|
||||
try:
|
||||
value = value.decode(enc)
|
||||
except UnicodeError:
|
||||
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
|
||||
|
||||
def __repr__(self):
|
||||
return 'STRING'
|
||||
|
||||
|
||||
class Choice(ParamType):
|
||||
"""The choice type allows a value to be checked against a fixed set of
|
||||
supported values. All of these values have to be strings.
|
||||
|
||||
See :ref:`choice-opts` for an example.
|
||||
"""
|
||||
name = 'choice'
|
||||
|
||||
def __init__(self, choices):
|
||||
self.choices = choices
|
||||
|
||||
def get_metavar(self, param):
|
||||
return '[%s]' % '|'.join(self.choices)
|
||||
|
||||
def get_missing_message(self, param):
|
||||
return 'Choose from %s.' % ', '.join(self.choices)
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
# Exact match
|
||||
if value in self.choices:
|
||||
return value
|
||||
|
||||
# Match through normalization
|
||||
if ctx is not None and \
|
||||
ctx.token_normalize_func is not None:
|
||||
value = ctx.token_normalize_func(value)
|
||||
for choice in self.choices:
|
||||
if ctx.token_normalize_func(choice) == value:
|
||||
return choice
|
||||
|
||||
self.fail('invalid choice: %s. (choose from %s)' %
|
||||
(value, ', '.join(self.choices)), param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Choice(%r)' % list(self.choices)
|
||||
|
||||
|
||||
class IntParamType(ParamType):
|
||||
name = 'integer'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return int(value)
|
||||
except (ValueError, UnicodeError):
|
||||
self.fail('%s is not a valid integer' % value, param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'INT'
|
||||
|
||||
|
||||
class IntRange(IntParamType):
|
||||
"""A parameter that works similar to :data:`click.INT` but restricts
|
||||
the value to fit into a range. The default behavior is to fail if the
|
||||
value falls outside the range, but it can also be silently clamped
|
||||
between the two edges.
|
||||
|
||||
See :ref:`ranges` for an example.
|
||||
"""
|
||||
name = 'integer range'
|
||||
|
||||
def __init__(self, min=None, max=None, clamp=False):
|
||||
self.min = min
|
||||
self.max = max
|
||||
self.clamp = clamp
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
rv = IntParamType.convert(self, value, param, ctx)
|
||||
if self.clamp:
|
||||
if self.min is not None and rv < self.min:
|
||||
return self.min
|
||||
if self.max is not None and rv > self.max:
|
||||
return self.max
|
||||
if self.min is not None and rv < self.min or \
|
||||
self.max is not None and rv > self.max:
|
||||
if self.min is None:
|
||||
self.fail('%s is bigger than the maximum valid value '
|
||||
'%s.' % (rv, self.max), param, ctx)
|
||||
elif self.max is None:
|
||||
self.fail('%s is smaller than the minimum valid value '
|
||||
'%s.' % (rv, self.min), param, ctx)
|
||||
else:
|
||||
self.fail('%s is not in the valid range of %s to %s.'
|
||||
% (rv, self.min, self.max), param, ctx)
|
||||
return rv
|
||||
|
||||
def __repr__(self):
|
||||
return 'IntRange(%r, %r)' % (self.min, self.max)
|
||||
|
||||
|
||||
class BoolParamType(ParamType):
|
||||
name = 'boolean'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
if isinstance(value, bool):
|
||||
return bool(value)
|
||||
value = value.lower()
|
||||
if value in ('true', '1', 'yes', 'y'):
|
||||
return True
|
||||
elif value in ('false', '0', 'no', 'n'):
|
||||
return False
|
||||
self.fail('%s is not a valid boolean' % value, param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'BOOL'
|
||||
|
||||
|
||||
class FloatParamType(ParamType):
|
||||
name = 'float'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
return float(value)
|
||||
except (UnicodeError, ValueError):
|
||||
self.fail('%s is not a valid floating point value' %
|
||||
value, param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'FLOAT'
|
||||
|
||||
|
||||
class UUIDParameterType(ParamType):
|
||||
name = 'uuid'
|
||||
|
||||
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 (UnicodeError, ValueError):
|
||||
self.fail('%s is not a valid UUID value' % value, param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'UUID'
|
||||
|
||||
|
||||
class File(ParamType):
|
||||
"""Declares a parameter to be a file for reading or writing. The file
|
||||
is automatically closed once the context tears down (after the command
|
||||
finished working).
|
||||
|
||||
Files can be opened for reading or writing. The special value ``-``
|
||||
indicates stdin or stdout depending on the mode.
|
||||
|
||||
By default, the file is opened for reading text data, but it can also be
|
||||
opened in binary mode or for writing. The encoding parameter can be used
|
||||
to force a specific encoding.
|
||||
|
||||
The `lazy` flag controls if the file should be opened immediately or
|
||||
upon first IO. The default is to be non lazy for standard input and
|
||||
output streams as well as files opened for reading, lazy otherwise.
|
||||
|
||||
Starting with Click 2.0, files can also be opened atomically in which
|
||||
case all writes go into a separate file in the same folder and upon
|
||||
completion the file will be moved over to the original location. This
|
||||
is useful if a file regularly read by other users is modified.
|
||||
|
||||
See :ref:`file-args` for more information.
|
||||
"""
|
||||
name = 'filename'
|
||||
envvar_list_splitter = os.path.pathsep
|
||||
|
||||
def __init__(self, mode='r', encoding=None, errors='strict', lazy=None,
|
||||
atomic=False):
|
||||
self.mode = mode
|
||||
self.encoding = encoding
|
||||
self.errors = errors
|
||||
self.lazy = lazy
|
||||
self.atomic = atomic
|
||||
|
||||
def resolve_lazy_flag(self, value):
|
||||
if self.lazy is not None:
|
||||
return self.lazy
|
||||
if value == '-':
|
||||
return False
|
||||
elif 'w' in self.mode:
|
||||
return True
|
||||
return False
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
try:
|
||||
if hasattr(value, 'read') or hasattr(value, 'write'):
|
||||
return value
|
||||
|
||||
lazy = self.resolve_lazy_flag(value)
|
||||
|
||||
if lazy:
|
||||
f = LazyFile(value, self.mode, self.encoding, self.errors,
|
||||
atomic=self.atomic)
|
||||
if ctx is not None:
|
||||
ctx.call_on_close(f.close_intelligently)
|
||||
return f
|
||||
|
||||
f, should_close = open_stream(value, self.mode,
|
||||
self.encoding, self.errors,
|
||||
atomic=self.atomic)
|
||||
# If a context is provided, we automatically close the file
|
||||
# at the end of the context execution (or flush out). If a
|
||||
# context does not exist, it's the caller's responsibility to
|
||||
# properly close the file. This for instance happens when the
|
||||
# type is used with prompts.
|
||||
if ctx is not None:
|
||||
if should_close:
|
||||
ctx.call_on_close(safecall(f.close))
|
||||
else:
|
||||
ctx.call_on_close(safecall(f.flush))
|
||||
return f
|
||||
except (IOError, OSError) as e:
|
||||
self.fail('Could not open file: %s: %s' % (
|
||||
filename_to_ui(value),
|
||||
get_streerror(e),
|
||||
), param, ctx)
|
||||
|
||||
|
||||
class Path(ParamType):
|
||||
"""The path type is similar to the :class:`File` type but it performs
|
||||
different checks. First of all, instead of returning an open file
|
||||
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
|
||||
silently skipped.
|
||||
:param file_okay: controls if a file is a possible value.
|
||||
:param dir_okay: controls if a directory is a possible value.
|
||||
:param writable: if true, a writable check is performed.
|
||||
:param readable: if true, a readable check is performed.
|
||||
: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,
|
||||
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'
|
||||
self.path_type = 'File'
|
||||
if self.dir_okay and not self.file_okay:
|
||||
self.name = 'directory'
|
||||
self.path_type = 'Directory'
|
||||
else:
|
||||
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
|
||||
|
||||
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.' % (
|
||||
self.path_type,
|
||||
filename_to_ui(value)
|
||||
), param, ctx)
|
||||
if not self.dir_okay and stat.S_ISDIR(st.st_mode):
|
||||
self.fail('%s "%s" is a directory.' % (
|
||||
self.path_type,
|
||||
filename_to_ui(value)
|
||||
), param, ctx)
|
||||
if self.writable and not os.access(value, os.W_OK):
|
||||
self.fail('%s "%s" is not writable.' % (
|
||||
self.path_type,
|
||||
filename_to_ui(value)
|
||||
), param, ctx)
|
||||
if self.readable and not os.access(value, os.R_OK):
|
||||
self.fail('%s "%s" is not readable.' % (
|
||||
self.path_type,
|
||||
filename_to_ui(value)
|
||||
), param, ctx)
|
||||
|
||||
return self.coerce_path_result(rv)
|
||||
|
||||
|
||||
class Tuple(CompositeParamType):
|
||||
"""The default behavior of Click is to apply a type on a value directly.
|
||||
This works well in most cases, except for when `nargs` is set to a fixed
|
||||
count and different types should be used for different items. In this
|
||||
case the :class:`Tuple` type can be used. This type can only be used
|
||||
if `nargs` is set to a fixed number.
|
||||
|
||||
For more information see :ref:`tuple-type`.
|
||||
|
||||
This can be selected by using a Python tuple literal as a type.
|
||||
|
||||
:param types: a list of types that should be used for the tuple items.
|
||||
"""
|
||||
|
||||
def __init__(self, types):
|
||||
self.types = [convert_type(ty) for ty in types]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return "<" + " ".join(ty.name for ty in self.types) + ">"
|
||||
|
||||
@property
|
||||
def arity(self):
|
||||
return len(self.types)
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
if len(value) != len(self.types):
|
||||
raise TypeError('It would appear that nargs is set to conflict '
|
||||
'with the composite type arity.')
|
||||
return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value))
|
||||
|
||||
|
||||
def convert_type(ty, default=None):
|
||||
"""Converts a callable or python ty into the most appropriate param
|
||||
ty.
|
||||
"""
|
||||
guessed_type = False
|
||||
if ty is None and default is not None:
|
||||
if isinstance(default, tuple):
|
||||
ty = tuple(map(type, default))
|
||||
else:
|
||||
ty = type(default)
|
||||
guessed_type = True
|
||||
|
||||
if isinstance(ty, tuple):
|
||||
return Tuple(ty)
|
||||
if isinstance(ty, ParamType):
|
||||
return ty
|
||||
if ty is text_type or ty is str or ty is None:
|
||||
return STRING
|
||||
if ty is int:
|
||||
return INT
|
||||
# Booleans are only okay if not guessed. This is done because for
|
||||
# flags the default value is actually a bit of a lie in that it
|
||||
# indicates which of the flags is the one we want. See get_default()
|
||||
# for more information.
|
||||
if ty is bool and not guessed_type:
|
||||
return BOOL
|
||||
if ty is float:
|
||||
return FLOAT
|
||||
if guessed_type:
|
||||
return STRING
|
||||
|
||||
# Catch a common mistake
|
||||
if __debug__:
|
||||
try:
|
||||
if issubclass(ty, ParamType):
|
||||
raise AssertionError('Attempted to use an uninstantiated '
|
||||
'parameter type (%s).' % ty)
|
||||
except TypeError:
|
||||
pass
|
||||
return FuncParamType(ty)
|
||||
|
||||
|
||||
#: A dummy parameter type that just does nothing. From a user's
|
||||
#: perspective this appears to just be the same as `STRING` but internally
|
||||
#: no string conversion takes place. This is necessary to achieve the
|
||||
#: same bytes/unicode behavior on Python 2/3 in situations where you want
|
||||
#: to not convert argument types. This is usually useful when working
|
||||
#: with file paths as they can appear in bytes and unicode.
|
||||
#:
|
||||
#: For path related uses the :class:`Path` type is a better choice but
|
||||
#: there are situations where an unprocessed type is useful which is why
|
||||
#: it is is provided.
|
||||
#:
|
||||
#: .. versionadded:: 4.0
|
||||
UNPROCESSED = UnprocessedParamType()
|
||||
|
||||
#: A unicode string parameter type which is the implicit default. This
|
||||
#: can also be selected by using ``str`` as type.
|
||||
STRING = StringParamType()
|
||||
|
||||
#: An integer parameter. This can also be selected by using ``int`` as
|
||||
#: type.
|
||||
INT = IntParamType()
|
||||
|
||||
#: A floating point value parameter. This can also be selected by using
|
||||
#: ``float`` as type.
|
||||
FLOAT = FloatParamType()
|
||||
|
||||
#: A boolean parameter. This is the default for boolean flags. This can
|
||||
#: also be selected by using ``bool`` as a type.
|
||||
BOOL = BoolParamType()
|
||||
|
||||
#: A UUID parameter.
|
||||
UUID = UUIDParameterType()
|
||||
Vendored
-415
@@ -1,415 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from .globals import resolve_color_default
|
||||
|
||||
from ._compat import text_type, open_stream, get_filesystem_encoding, \
|
||||
get_streerror, string_types, PY2, binary_streams, text_streams, \
|
||||
filename_to_ui, auto_wrap_for_ansi, strip_ansi, should_strip_ansi, \
|
||||
_default_text_stdout, _default_text_stderr, is_bytes, WIN
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def _posixify(name):
|
||||
return '-'.join(name.split()).lower()
|
||||
|
||||
|
||||
def safecall(func):
|
||||
"""Wraps a function so that it swallows exceptions."""
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception:
|
||||
pass
|
||||
return wrapper
|
||||
|
||||
|
||||
def make_str(value):
|
||||
"""Converts a value into a valid string."""
|
||||
if isinstance(value, bytes):
|
||||
try:
|
||||
return value.decode(get_filesystem_encoding())
|
||||
except UnicodeError:
|
||||
return value.decode('utf-8', 'replace')
|
||||
return text_type(value)
|
||||
|
||||
|
||||
def make_default_short_help(help, max_length=45):
|
||||
words = help.split()
|
||||
total_length = 0
|
||||
result = []
|
||||
done = False
|
||||
|
||||
for word in words:
|
||||
if word[-1:] == '.':
|
||||
done = True
|
||||
new_length = result and 1 + len(word) or len(word)
|
||||
if total_length + new_length > max_length:
|
||||
result.append('...')
|
||||
done = True
|
||||
else:
|
||||
if result:
|
||||
result.append(' ')
|
||||
result.append(word)
|
||||
if done:
|
||||
break
|
||||
total_length += new_length
|
||||
|
||||
return ''.join(result)
|
||||
|
||||
|
||||
class LazyFile(object):
|
||||
"""A lazy file works like a regular file but it does not fully open
|
||||
the file but it does perform some basic checks early to see if the
|
||||
filename parameter does make sense. This is useful for safely opening
|
||||
files for writing.
|
||||
"""
|
||||
|
||||
def __init__(self, filename, mode='r', encoding=None, errors='strict',
|
||||
atomic=False):
|
||||
self.name = filename
|
||||
self.mode = mode
|
||||
self.encoding = encoding
|
||||
self.errors = errors
|
||||
self.atomic = atomic
|
||||
|
||||
if filename == '-':
|
||||
self._f, self.should_close = open_stream(filename, mode,
|
||||
encoding, errors)
|
||||
else:
|
||||
if 'r' in mode:
|
||||
# Open and close the file in case we're opening it for
|
||||
# reading so that we can catch at least some errors in
|
||||
# some cases early.
|
||||
open(filename, mode).close()
|
||||
self._f = None
|
||||
self.should_close = True
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.open(), name)
|
||||
|
||||
def __repr__(self):
|
||||
if self._f is not None:
|
||||
return repr(self._f)
|
||||
return '<unopened file %r %s>' % (self.name, self.mode)
|
||||
|
||||
def open(self):
|
||||
"""Opens the file if it's not yet open. This call might fail with
|
||||
a :exc:`FileError`. Not handling this error will produce an error
|
||||
that Click shows.
|
||||
"""
|
||||
if self._f is not None:
|
||||
return self._f
|
||||
try:
|
||||
rv, self.should_close = open_stream(self.name, self.mode,
|
||||
self.encoding,
|
||||
self.errors,
|
||||
atomic=self.atomic)
|
||||
except (IOError, OSError) as e:
|
||||
from .exceptions import FileError
|
||||
raise FileError(self.name, hint=get_streerror(e))
|
||||
self._f = rv
|
||||
return rv
|
||||
|
||||
def close(self):
|
||||
"""Closes the underlying file, no matter what."""
|
||||
if self._f is not None:
|
||||
self._f.close()
|
||||
|
||||
def close_intelligently(self):
|
||||
"""This function only closes the file if it was opened by the lazy
|
||||
file wrapper. For instance this will never close stdin.
|
||||
"""
|
||||
if self.should_close:
|
||||
self.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.close_intelligently()
|
||||
|
||||
def __iter__(self):
|
||||
self.open()
|
||||
return iter(self._f)
|
||||
|
||||
|
||||
class KeepOpenFile(object):
|
||||
|
||||
def __init__(self, file):
|
||||
self._file = file
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._file, name)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._file)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._file)
|
||||
|
||||
|
||||
def echo(message=None, file=None, nl=True, err=False, color=None):
|
||||
"""Prints a message plus a newline to the given file or stdout. On
|
||||
first sight, this looks like the print function, but it has improved
|
||||
support for handling Unicode and binary data that does not fail no
|
||||
matter how badly configured the system is.
|
||||
|
||||
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. 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
|
||||
do the following:
|
||||
|
||||
- add transparent handling of ANSI color codes on Windows.
|
||||
- hide ANSI codes automatically if the destination file is not a
|
||||
terminal.
|
||||
|
||||
.. _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.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
The `err` parameter was added.
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
Added the `color` flag.
|
||||
|
||||
:param message: the message to print
|
||||
:param file: the file to write to (defaults to ``stdout``)
|
||||
:param err: if set to true the file defaults to ``stderr`` instead of
|
||||
``stdout``. This is faster and easier than calling
|
||||
:func:`get_text_stderr` yourself.
|
||||
:param nl: if set to `True` (the default) a newline is printed afterwards.
|
||||
:param color: controls if the terminal supports ANSI colors or not. The
|
||||
default is autodetection.
|
||||
"""
|
||||
if file is None:
|
||||
if err:
|
||||
file = _default_text_stderr()
|
||||
else:
|
||||
file = _default_text_stdout()
|
||||
|
||||
# Convert non bytes/text into the native string type.
|
||||
if message is not None and not isinstance(message, echo_native_types):
|
||||
message = text_type(message)
|
||||
|
||||
if nl:
|
||||
message = message or u''
|
||||
if isinstance(message, text_type):
|
||||
message += u'\n'
|
||||
else:
|
||||
message += b'\n'
|
||||
|
||||
# If there is a message, and we're in Python 3, and the value looks
|
||||
# like bytes, we manually need to find the binary stream and write the
|
||||
# message in there. This is done separately so that most stream
|
||||
# types will work as you would expect. Eg: you can write to StringIO
|
||||
# for other cases.
|
||||
if message and not PY2 and is_bytes(message):
|
||||
binary_file = _find_binary_writer(file)
|
||||
if binary_file is not None:
|
||||
file.flush()
|
||||
binary_file.write(message)
|
||||
binary_file.flush()
|
||||
return
|
||||
|
||||
# ANSI-style support. If there is no message or we are dealing with
|
||||
# bytes nothing is happening. If we are connected to a file we want
|
||||
# to strip colors. If we are on windows we either wrap the stream
|
||||
# to strip the color or we use the colorama support to translate the
|
||||
# ansi codes to API calls.
|
||||
if message and not is_bytes(message):
|
||||
color = resolve_color_default(color)
|
||||
if should_strip_ansi(file, color):
|
||||
message = strip_ansi(message)
|
||||
elif WIN:
|
||||
if auto_wrap_for_ansi is not None:
|
||||
file = auto_wrap_for_ansi(file)
|
||||
elif not color:
|
||||
message = strip_ansi(message)
|
||||
|
||||
if message:
|
||||
file.write(message)
|
||||
file.flush()
|
||||
|
||||
|
||||
def get_binary_stream(name):
|
||||
"""Returns a system stream for byte processing. This essentially
|
||||
returns the stream from the sys module with the given name but it
|
||||
solves some compatibility issues between different Python versions.
|
||||
Primarily this function is necessary for getting binary streams on
|
||||
Python 3.
|
||||
|
||||
:param name: the name of the stream to open. Valid names are ``'stdin'``,
|
||||
``'stdout'`` and ``'stderr'``
|
||||
"""
|
||||
opener = binary_streams.get(name)
|
||||
if opener is None:
|
||||
raise TypeError('Unknown standard stream %r' % name)
|
||||
return opener()
|
||||
|
||||
|
||||
def get_text_stream(name, encoding=None, errors='strict'):
|
||||
"""Returns a system stream for text processing. This usually returns
|
||||
a wrapped stream around a binary stream returned from
|
||||
:func:`get_binary_stream` but it also can take shortcuts on Python 3
|
||||
for already correctly configured streams.
|
||||
|
||||
:param name: the name of the stream to open. Valid names are ``'stdin'``,
|
||||
``'stdout'`` and ``'stderr'``
|
||||
:param encoding: overrides the detected default encoding.
|
||||
:param errors: overrides the default error mode.
|
||||
"""
|
||||
opener = text_streams.get(name)
|
||||
if opener is None:
|
||||
raise TypeError('Unknown standard stream %r' % name)
|
||||
return opener(encoding, errors)
|
||||
|
||||
|
||||
def open_file(filename, mode='r', encoding=None, errors='strict',
|
||||
lazy=False, atomic=False):
|
||||
"""This is similar to how the :class:`File` works but for manual
|
||||
usage. Files are opened non lazy by default. This can open regular
|
||||
files as well as stdin/stdout if ``'-'`` is passed.
|
||||
|
||||
If stdin/stdout is returned the stream is wrapped so that the context
|
||||
manager will not close the stream accidentally. This makes it possible
|
||||
to always use the function like this without having to worry to
|
||||
accidentally close a standard stream::
|
||||
|
||||
with open_file(filename) as f:
|
||||
...
|
||||
|
||||
.. versionadded:: 3.0
|
||||
|
||||
:param filename: the name of the file to open (or ``'-'`` for stdin/stdout).
|
||||
:param mode: the mode in which to open the file.
|
||||
:param encoding: the encoding to use.
|
||||
:param errors: the error handling for this file.
|
||||
:param lazy: can be flipped to true to open the file lazily.
|
||||
:param atomic: in atomic mode writes go into a temporary file and it's
|
||||
moved on close.
|
||||
"""
|
||||
if lazy:
|
||||
return LazyFile(filename, mode, encoding, errors, atomic=atomic)
|
||||
f, should_close = open_stream(filename, mode, encoding, errors,
|
||||
atomic=atomic)
|
||||
if not should_close:
|
||||
f = KeepOpenFile(f)
|
||||
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
|
||||
will decode the filename to unicode if necessary in a way that it will
|
||||
not fail. Optionally, it can shorten the filename to not include the
|
||||
full path to the filename.
|
||||
|
||||
:param filename: formats a filename for UI display. This will also convert
|
||||
the filename into unicode without failing.
|
||||
:param shorten: this optionally shortens the filename to strip of the
|
||||
path that leads up to it.
|
||||
"""
|
||||
if shorten:
|
||||
filename = os.path.basename(filename)
|
||||
return filename_to_ui(filename)
|
||||
|
||||
|
||||
def get_app_dir(app_name, roaming=True, force_posix=False):
|
||||
r"""Returns the config folder for the application. The default behavior
|
||||
is to return whatever is most appropriate for the operating system.
|
||||
|
||||
To give you an idea, for an app called ``"Foo Bar"``, something like
|
||||
the following folders could be returned:
|
||||
|
||||
Mac OS X:
|
||||
``~/Library/Application Support/Foo Bar``
|
||||
Mac OS X (POSIX):
|
||||
``~/.foo-bar``
|
||||
Unix:
|
||||
``~/.config/foo-bar``
|
||||
Unix (POSIX):
|
||||
``~/.foo-bar``
|
||||
Win XP (roaming):
|
||||
``C:\Documents and Settings\<user>\Local Settings\Application Data\Foo Bar``
|
||||
Win XP (not roaming):
|
||||
``C:\Documents and Settings\<user>\Application Data\Foo Bar``
|
||||
Win 7 (roaming):
|
||||
``C:\Users\<user>\AppData\Roaming\Foo Bar``
|
||||
Win 7 (not roaming):
|
||||
``C:\Users\<user>\AppData\Local\Foo Bar``
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param app_name: the application name. This should be properly capitalized
|
||||
and can contain whitespace.
|
||||
:param roaming: controls if the folder should be roaming or not on Windows.
|
||||
Has no affect otherwise.
|
||||
:param force_posix: if this is set to `True` then on any POSIX system the
|
||||
folder will be stored in the home folder with a leading
|
||||
dot instead of the XDG config home or darwin's
|
||||
application support folder.
|
||||
"""
|
||||
if WIN:
|
||||
key = roaming and 'APPDATA' or 'LOCALAPPDATA'
|
||||
folder = os.environ.get(key)
|
||||
if folder is None:
|
||||
folder = os.path.expanduser('~')
|
||||
return os.path.join(folder, app_name)
|
||||
if force_posix:
|
||||
return os.path.join(os.path.expanduser('~/.' + _posixify(app_name)))
|
||||
if sys.platform == 'darwin':
|
||||
return os.path.join(os.path.expanduser(
|
||||
'~/Library/Application Support'), app_name)
|
||||
return os.path.join(
|
||||
os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')),
|
||||
_posixify(app_name))
|
||||
Vendored
-470
@@ -1,470 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from __future__ import print_function, absolute_import
|
||||
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import shlex
|
||||
import subprocess
|
||||
|
||||
import click
|
||||
import six
|
||||
|
||||
from click import echo, MultiCommand, Option, Argument, ParamType
|
||||
|
||||
__version__ = '0.2.1'
|
||||
|
||||
|
||||
FISH_TEMPLATE = 'complete --command {{prog_name}} --arguments "(env {{complete_var}}=complete-fish COMMANDLINE=(commandline -cp){% for k, v in extra_env.items() %} {{k}}={{v}}{% endfor %} {{prog_name}})" -f'
|
||||
|
||||
ZSH_TEMPLATE = '''
|
||||
#compdef {{prog_name}}
|
||||
_{{prog_name}}() {
|
||||
eval $(env COMMANDLINE="${words[1,$CURRENT]}" {{complete_var}}=complete-zsh {% for k, v in extra_env.items() %} {{k}}={{v}}{% endfor %} {{prog_name}})
|
||||
}
|
||||
if [[ "$(basename ${(%):-%x})" != "_{{prog_name}}" ]]; then
|
||||
autoload -U compinit && compinit
|
||||
compdef _{{prog_name}} {{prog_name}}
|
||||
fi
|
||||
'''
|
||||
|
||||
POWERSHELL_COMPLETION_SCRIPT = '''
|
||||
if ((Test-Path Function:\TabExpansion) -and -not (Test-Path Function:\{{prog_name}}TabExpansionBackup)) {
|
||||
Rename-Item Function:\TabExpansion {{prog_name}}TabExpansionBackup
|
||||
}
|
||||
|
||||
function TabExpansion($line, $lastWord) {
|
||||
$lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart()
|
||||
$aliases = @("{{prog_name}}") + @(Get-Alias | where { $_.Definition -eq "{{prog_name}}" } | select -Exp Name)
|
||||
$aliasPattern = "($($aliases -join '|'))"
|
||||
if($lastBlock -match "^$aliasPattern ") {
|
||||
$Env:{{complete_var}} = "complete-powershell"
|
||||
$Env:COMMANDLINE = "$lastBlock"
|
||||
{%- for k, v in extra_env.items() %}
|
||||
$Env:{{k}} = "{{v}}"
|
||||
{%- endfor %}
|
||||
({{prog_name}}) | ? {$_.trim() -ne "" }
|
||||
Remove-Item Env:{{complete_var}}
|
||||
Remove-Item Env:COMMANDLINE
|
||||
{%- for k in extra_env.keys() %}
|
||||
Remove-Item $Env:{{k}}
|
||||
{%- endfor %}
|
||||
}
|
||||
elseif (Test-Path Function:\{{prog_name}}TabExpansionBackup) {
|
||||
# Fall back on existing tab expansion
|
||||
{{prog_name}}TabExpansionBackup $line $lastWord
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
BASH_COMPLETION_SCRIPT = '''
|
||||
_{{prog_name}}_completion() {
|
||||
local IFS=$'\\t'
|
||||
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||
COMP_CWORD=$COMP_CWORD \\
|
||||
{%- for k, v in extra_env.items() %}
|
||||
{{k}}={{v}} \\
|
||||
{%- endfor %}
|
||||
{{complete_var}}=complete-bash $1 ) )
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _{{prog_name}}_completion -o default {{prog_name}}
|
||||
'''
|
||||
|
||||
_invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]')
|
||||
|
||||
|
||||
def resolve_ctx(cli, prog_name, args):
|
||||
ctx = cli.make_context(prog_name, list(args), resilient_parsing=True)
|
||||
while ctx.args + ctx.protected_args and isinstance(ctx.command, MultiCommand):
|
||||
a = ctx.protected_args + ctx.args
|
||||
cmd = ctx.command.get_command(ctx, a[0])
|
||||
if cmd is None:
|
||||
return None
|
||||
ctx = cmd.make_context(a[0], a[1:], parent=ctx, resilient_parsing=True)
|
||||
return ctx
|
||||
|
||||
|
||||
def startswith(string, incomplete):
|
||||
"""Returns True when string starts with incomplete
|
||||
|
||||
It might be overridden with a fuzzier version - for example a case insensitive version"""
|
||||
return string.startswith(incomplete)
|
||||
|
||||
|
||||
def get_choices(cli, prog_name, args, incomplete):
|
||||
ctx = resolve_ctx(cli, prog_name, args)
|
||||
if ctx is None:
|
||||
return
|
||||
|
||||
optctx = None
|
||||
if args:
|
||||
for param in ctx.command.get_params(ctx):
|
||||
if isinstance(param, Option) and not param.is_flag and args[-1] in param.opts + param.secondary_opts:
|
||||
optctx = param
|
||||
|
||||
choices = []
|
||||
if optctx:
|
||||
choices += [c if isinstance(c, tuple) else (c, None) for c in optctx.type.complete(ctx, incomplete)]
|
||||
elif incomplete and not incomplete[:1].isalnum():
|
||||
for param in ctx.command.get_params(ctx):
|
||||
if not isinstance(param, Option):
|
||||
continue
|
||||
for opt in param.opts:
|
||||
if startswith(opt, incomplete):
|
||||
choices.append((opt, param.help))
|
||||
for opt in param.secondary_opts:
|
||||
if startswith(opt, incomplete):
|
||||
# don't put the doc so fish won't group the primary and
|
||||
# and secondary options
|
||||
choices.append((opt, None))
|
||||
elif isinstance(ctx.command, MultiCommand):
|
||||
for name in ctx.command.list_commands(ctx):
|
||||
if startswith(name, incomplete):
|
||||
choices.append((name, ctx.command.get_command_short_help(ctx, name)))
|
||||
else:
|
||||
for param in ctx.command.get_params(ctx):
|
||||
if isinstance(param, Argument):
|
||||
choices += [c if isinstance(c, tuple) else (c, None) for c in param.type.complete(ctx, incomplete)]
|
||||
|
||||
for item, help in choices:
|
||||
yield (item, help)
|
||||
|
||||
|
||||
def split_args(line):
|
||||
"""Version of shlex.split that silently accept incomplete strings."""
|
||||
lex = shlex.shlex(line, posix=True)
|
||||
lex.whitespace_split = True
|
||||
lex.commenters = ''
|
||||
res = []
|
||||
try:
|
||||
while True:
|
||||
res.append(next(lex))
|
||||
except ValueError: # No closing quotation
|
||||
pass
|
||||
except StopIteration: # End of loop
|
||||
pass
|
||||
if lex.token:
|
||||
res.append(lex.token)
|
||||
return res
|
||||
|
||||
|
||||
def decode_args(strings):
|
||||
res = []
|
||||
for s in strings:
|
||||
s = split_args(s)
|
||||
s = s[0] if s else ''
|
||||
res.append(s)
|
||||
return res
|
||||
|
||||
|
||||
def do_bash_complete(cli, prog_name):
|
||||
comp_words = os.environ['COMP_WORDS']
|
||||
try:
|
||||
cwords = shlex.split(comp_words)
|
||||
quoted = False
|
||||
except ValueError: # No closing quotation
|
||||
cwords = split_args(comp_words)
|
||||
quoted = True
|
||||
cword = int(os.environ['COMP_CWORD'])
|
||||
args = cwords[1:cword]
|
||||
try:
|
||||
incomplete = cwords[cword]
|
||||
except IndexError:
|
||||
incomplete = ''
|
||||
choices = get_choices(cli, prog_name, args, incomplete)
|
||||
|
||||
if quoted:
|
||||
echo('\t'.join(opt for opt, _ in choices), nl=False)
|
||||
else:
|
||||
echo('\t'.join(re.sub(r"""([\s\\"'])""", r'\\\1', opt) for opt, _ in choices), nl=False)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def do_fish_complete(cli, prog_name):
|
||||
commandline = os.environ['COMMANDLINE']
|
||||
args = split_args(commandline)[1:]
|
||||
if args and not commandline.endswith(' '):
|
||||
incomplete = args[-1]
|
||||
args = args[:-1]
|
||||
else:
|
||||
incomplete = ''
|
||||
|
||||
for item, help in get_choices(cli, prog_name, args, incomplete):
|
||||
if help:
|
||||
echo("%s\t%s" % (item, help))
|
||||
else:
|
||||
echo(item)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def do_zsh_complete(cli, prog_name):
|
||||
commandline = os.environ['COMMANDLINE']
|
||||
args = split_args(commandline)[1:]
|
||||
if args and not commandline.endswith(' '):
|
||||
incomplete = args[-1]
|
||||
args = args[:-1]
|
||||
else:
|
||||
incomplete = ''
|
||||
|
||||
def escape(s):
|
||||
return s.replace('"', '""').replace("'", "''").replace('$', '\\$')
|
||||
res = []
|
||||
for item, help in get_choices(cli, prog_name, args, incomplete):
|
||||
if help:
|
||||
res.append('"%s"\:"%s"' % (escape(item), escape(help)))
|
||||
else:
|
||||
res.append('"%s"' % escape(item))
|
||||
echo("_arguments '*: :((%s))'" % '\n'.join(res))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def do_powershell_complete(cli, prog_name):
|
||||
commandline = os.environ['COMMANDLINE']
|
||||
args = split_args(commandline)[1:]
|
||||
quote = single_quote
|
||||
incomplete = ''
|
||||
if args and not commandline.endswith(' '):
|
||||
incomplete = args[-1]
|
||||
args = args[:-1]
|
||||
quote_pos = commandline.rfind(incomplete) - 1
|
||||
if quote_pos >= 0 and commandline[quote_pos] == '"':
|
||||
quote = double_quote
|
||||
|
||||
for item, help in get_choices(cli, prog_name, args, incomplete):
|
||||
echo(quote(item))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
|
||||
|
||||
|
||||
def single_quote(s):
|
||||
"""Return a shell-escaped version of the string *s*."""
|
||||
if not s:
|
||||
return "''"
|
||||
if find_unsafe(s) is None:
|
||||
return s
|
||||
|
||||
# use single quotes, and put single quotes into double quotes
|
||||
# the string $'b is then quoted as '$'"'"'b'
|
||||
return "'" + s.replace("'", "'\"'\"'") + "'"
|
||||
|
||||
|
||||
def double_quote(s):
|
||||
'''Return a shell-escaped version of the string *s*.'''
|
||||
if not s:
|
||||
return '""'
|
||||
if find_unsafe(s) is None:
|
||||
return s
|
||||
|
||||
# use double quotes, and put double quotes into single quotes
|
||||
# the string $"b is then quoted as "$"'"'"b"
|
||||
return '"' + s.replace('"', '"\'"\'"') + '"'
|
||||
|
||||
|
||||
# extend click completion features
|
||||
|
||||
def param_type_complete(self, ctx, incomplete):
|
||||
return []
|
||||
|
||||
|
||||
def choice_complete(self, ctx, incomplete):
|
||||
return [c for c in self.choices if c.startswith(incomplete)]
|
||||
|
||||
|
||||
def multicommand_get_command_short_help(self, ctx, cmd_name):
|
||||
return self.get_command(ctx, cmd_name).short_help
|
||||
|
||||
|
||||
def _shellcomplete(cli, prog_name, complete_var=None):
|
||||
"""Internal handler for the bash completion support."""
|
||||
if complete_var is None:
|
||||
complete_var = '_%s_COMPLETE' % (prog_name.replace('-', '_')).upper()
|
||||
complete_instr = os.environ.get(complete_var)
|
||||
if not complete_instr:
|
||||
return
|
||||
|
||||
if complete_instr == 'source':
|
||||
echo(get_code(prog_name=prog_name, env_name=complete_var))
|
||||
elif complete_instr == 'source-bash':
|
||||
echo(get_code('bash', prog_name, complete_var))
|
||||
elif complete_instr == 'source-fish':
|
||||
echo(get_code('fish', prog_name, complete_var))
|
||||
elif complete_instr == 'source-powershell':
|
||||
echo(get_code('powershell', prog_name, complete_var))
|
||||
elif complete_instr == 'source-zsh':
|
||||
echo(get_code('zsh', prog_name, complete_var))
|
||||
elif complete_instr in ['complete', 'complete-bash']:
|
||||
# keep 'complete' for bash for backward compatibility
|
||||
do_bash_complete(cli, prog_name)
|
||||
elif complete_instr == 'complete-fish':
|
||||
do_fish_complete(cli, prog_name)
|
||||
elif complete_instr == 'complete-powershell':
|
||||
do_powershell_complete(cli, prog_name)
|
||||
elif complete_instr == 'complete-zsh':
|
||||
do_zsh_complete(cli, prog_name)
|
||||
elif complete_instr == 'install':
|
||||
shell, path = install(prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
elif complete_instr == 'install-bash':
|
||||
shell, path = install(shell='bash', prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
elif complete_instr == 'install-fish':
|
||||
shell, path = install(shell='fish', prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
elif complete_instr == 'install-zsh':
|
||||
shell, path = install(shell='zsh', prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
elif complete_instr == 'install-powershell':
|
||||
shell, path = install(shell='powershell', prog_name=prog_name, env_name=complete_var)
|
||||
click.echo('%s completion installed in %s' % (shell, path))
|
||||
sys.exit()
|
||||
|
||||
|
||||
def init():
|
||||
"""patch click to support enhanced completion"""
|
||||
import click
|
||||
click.types.ParamType.complete = param_type_complete
|
||||
click.types.Choice.complete = choice_complete
|
||||
click.core.MultiCommand.get_command_short_help = multicommand_get_command_short_help
|
||||
click.core._bashcomplete = _shellcomplete
|
||||
|
||||
|
||||
class DocumentedChoice(ParamType):
|
||||
"""The choice type allows a value to be checked against a fixed set of
|
||||
supported values. All of these values have to be strings. Each value may
|
||||
be associated to a help message that will be display in the error message
|
||||
and during the completion.
|
||||
"""
|
||||
name = 'choice'
|
||||
|
||||
def __init__(self, choices):
|
||||
self.choices = dict(choices)
|
||||
|
||||
def get_metavar(self, param):
|
||||
return '[%s]' % '|'.join(self.choices.keys())
|
||||
|
||||
def get_missing_message(self, param):
|
||||
formated_choices = ['{:<12} {}'.format(k, self.choices[k] or '') for k in sorted(self.choices.keys())]
|
||||
return 'Choose from\n ' + '\n '.join(formated_choices)
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
# Exact match
|
||||
if value in self.choices:
|
||||
return value
|
||||
|
||||
# Match through normalization
|
||||
if ctx is not None and \
|
||||
ctx.token_normalize_func is not None:
|
||||
value = ctx.token_normalize_func(value)
|
||||
for choice in self.choices:
|
||||
if ctx.token_normalize_func(choice) == value:
|
||||
return choice
|
||||
|
||||
self.fail('invalid choice: %s. %s' %
|
||||
(value, self.get_missing_message(param)), param, ctx)
|
||||
|
||||
def __repr__(self):
|
||||
return 'DocumentedChoice(%r)' % list(self.choices.keys())
|
||||
|
||||
def complete(self, ctx, incomplete):
|
||||
return [(c, v) for c, v in six.iteritems(self.choices) if startswith(c, incomplete)]
|
||||
|
||||
|
||||
def get_code(shell=None, prog_name=None, env_name=None, extra_env=None):
|
||||
"""Return the specified completion code"""
|
||||
from jinja2 import Template
|
||||
if shell in [None, 'auto']:
|
||||
shell = get_auto_shell()
|
||||
prog_name = prog_name or click.get_current_context().find_root().info_name
|
||||
env_name = env_name or '_%s_COMPLETE' % prog_name.upper().replace('-', '_')
|
||||
extra_env = extra_env if extra_env else {}
|
||||
if shell == 'fish':
|
||||
return Template(FISH_TEMPLATE).render(prog_name=prog_name, complete_var=env_name, extra_env=extra_env)
|
||||
elif shell == 'bash':
|
||||
return Template(BASH_COMPLETION_SCRIPT).render(prog_name=prog_name, complete_var=env_name, extra_env=extra_env)
|
||||
elif shell == 'zsh':
|
||||
return Template(ZSH_TEMPLATE).render(prog_name=prog_name, complete_var=env_name, extra_env=extra_env)
|
||||
elif shell == 'powershell':
|
||||
return Template(POWERSHELL_COMPLETION_SCRIPT).render(prog_name=prog_name, complete_var=env_name, extra_env=extra_env)
|
||||
else:
|
||||
raise click.ClickException('%s is not supported.' % shell)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_auto_shell():
|
||||
"""Return the shell that is calling this process"""
|
||||
try:
|
||||
import psutil
|
||||
parent = psutil.Process(os.getpid()).parent()
|
||||
if platform.system() == 'Windows':
|
||||
parent = parent.parent() or parent
|
||||
return parent.name().replace('.exe', '')
|
||||
except ImportError:
|
||||
raise click.UsageError("Please explicitly give the shell type or install the psutil package to activate the"
|
||||
" automatic shell detection.")
|
||||
|
||||
|
||||
def install(shell=None, prog_name=None, env_name=None, path=None, append=None, extra_env=None):
|
||||
"""Install the completion"""
|
||||
prog_name = prog_name or click.get_current_context().find_root().info_name
|
||||
shell = shell or get_auto_shell()
|
||||
if append is None and path is not None:
|
||||
append = True
|
||||
if append is not None:
|
||||
mode = 'a' if append else 'w'
|
||||
else:
|
||||
mode = None
|
||||
|
||||
if shell == 'fish':
|
||||
path = path or os.path.expanduser('~') + '/.config/fish/completions/%s.fish' % prog_name
|
||||
mode = mode or 'w'
|
||||
elif shell == 'bash':
|
||||
path = path or os.path.expanduser('~') + '/.bash_completion'
|
||||
mode = mode or 'a'
|
||||
elif shell == 'zsh':
|
||||
ohmyzsh = os.path.expanduser('~') + '/.oh-my-zsh'
|
||||
if os.path.exists(ohmyzsh):
|
||||
path = path or ohmyzsh + '/completions/_%s' % prog_name
|
||||
mode = mode or 'w'
|
||||
else:
|
||||
path = path or os.path.expanduser('~') + '/.zshrc'
|
||||
mode = mode or 'a'
|
||||
elif shell == 'powershell':
|
||||
subprocess.check_call(['powershell', 'Set-ExecutionPolicy Unrestricted -Scope CurrentUser'])
|
||||
path = path or subprocess.check_output(['powershell', '-NoProfile', 'echo $profile']).strip() if install else ''
|
||||
mode = mode or 'a'
|
||||
else:
|
||||
raise click.ClickException('%s is not supported.' % shell)
|
||||
|
||||
if append is not None:
|
||||
mode = 'a' if append else 'w'
|
||||
else:
|
||||
mode = mode
|
||||
d = os.path.dirname(path)
|
||||
if not os.path.exists(d):
|
||||
os.makedirs(d)
|
||||
f = open(path, mode)
|
||||
f.write(get_code(shell, prog_name, env_name, extra_env))
|
||||
f.write("\n")
|
||||
f.close()
|
||||
return shell, path
|
||||
|
||||
|
||||
shells = {
|
||||
'bash': 'Bourne again shell',
|
||||
'fish': 'Friendly interactive shell',
|
||||
'zsh': 'Z shell',
|
||||
'powershell': 'Windows PowerShell'
|
||||
}
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
from .initialise import init, deinit, reinit, colorama_text
|
||||
from .ansi import Fore, Back, Style, Cursor
|
||||
from .ansitowin32 import AnsiToWin32
|
||||
|
||||
__version__ = '0.3.7'
|
||||
|
||||
Vendored
-102
@@ -1,102 +0,0 @@
|
||||
# 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['
|
||||
OSC = '\033]'
|
||||
BEL = '\007'
|
||||
|
||||
|
||||
def code_to_chars(code):
|
||||
return CSI + str(code) + 'm'
|
||||
|
||||
def set_title(title):
|
||||
return OSC + '2;' + title + BEL
|
||||
|
||||
def clear_screen(mode=2):
|
||||
return CSI + str(mode) + 'J'
|
||||
|
||||
def clear_line(mode=2):
|
||||
return CSI + str(mode) + 'K'
|
||||
|
||||
|
||||
class AnsiCodes(object):
|
||||
def __init__(self):
|
||||
# the subclasses declare class attributes which are numbers.
|
||||
# Upon instantiation we define instance attributes, which are the same
|
||||
# as the class attributes but wrapped with the ANSI escape sequence
|
||||
for name in dir(self):
|
||||
if not name.startswith('_'):
|
||||
value = getattr(self, name)
|
||||
setattr(self, name, code_to_chars(value))
|
||||
|
||||
|
||||
class AnsiCursor(object):
|
||||
def UP(self, n=1):
|
||||
return CSI + str(n) + 'A'
|
||||
def DOWN(self, n=1):
|
||||
return CSI + str(n) + 'B'
|
||||
def FORWARD(self, n=1):
|
||||
return CSI + str(n) + 'C'
|
||||
def BACK(self, n=1):
|
||||
return CSI + str(n) + 'D'
|
||||
def POS(self, x=1, y=1):
|
||||
return CSI + str(y) + ';' + str(x) + 'H'
|
||||
|
||||
|
||||
class AnsiFore(AnsiCodes):
|
||||
BLACK = 30
|
||||
RED = 31
|
||||
GREEN = 32
|
||||
YELLOW = 33
|
||||
BLUE = 34
|
||||
MAGENTA = 35
|
||||
CYAN = 36
|
||||
WHITE = 37
|
||||
RESET = 39
|
||||
|
||||
# These are fairly well supported, but not part of the standard.
|
||||
LIGHTBLACK_EX = 90
|
||||
LIGHTRED_EX = 91
|
||||
LIGHTGREEN_EX = 92
|
||||
LIGHTYELLOW_EX = 93
|
||||
LIGHTBLUE_EX = 94
|
||||
LIGHTMAGENTA_EX = 95
|
||||
LIGHTCYAN_EX = 96
|
||||
LIGHTWHITE_EX = 97
|
||||
|
||||
|
||||
class AnsiBack(AnsiCodes):
|
||||
BLACK = 40
|
||||
RED = 41
|
||||
GREEN = 42
|
||||
YELLOW = 43
|
||||
BLUE = 44
|
||||
MAGENTA = 45
|
||||
CYAN = 46
|
||||
WHITE = 47
|
||||
RESET = 49
|
||||
|
||||
# These are fairly well supported, but not part of the standard.
|
||||
LIGHTBLACK_EX = 100
|
||||
LIGHTRED_EX = 101
|
||||
LIGHTGREEN_EX = 102
|
||||
LIGHTYELLOW_EX = 103
|
||||
LIGHTBLUE_EX = 104
|
||||
LIGHTMAGENTA_EX = 105
|
||||
LIGHTCYAN_EX = 106
|
||||
LIGHTWHITE_EX = 107
|
||||
|
||||
|
||||
class AnsiStyle(AnsiCodes):
|
||||
BRIGHT = 1
|
||||
DIM = 2
|
||||
NORMAL = 22
|
||||
RESET_ALL = 0
|
||||
|
||||
Fore = AnsiFore()
|
||||
Back = AnsiBack()
|
||||
Style = AnsiStyle()
|
||||
Cursor = AnsiCursor()
|
||||
-236
@@ -1,236 +0,0 @@
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
|
||||
from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style
|
||||
from .winterm import WinTerm, WinColor, WinStyle
|
||||
from .win32 import windll, winapi_test
|
||||
|
||||
|
||||
winterm = None
|
||||
if windll is not None:
|
||||
winterm = WinTerm()
|
||||
|
||||
|
||||
def is_stream_closed(stream):
|
||||
return not hasattr(stream, 'closed') or stream.closed
|
||||
|
||||
|
||||
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_CSI_RE = re.compile('\001?\033\[((?:\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer
|
||||
ANSI_OSC_RE = re.compile('\001?\033\]((?:.|;)*?)(\x07)\002?') # Operating System Command
|
||||
|
||||
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 = os.name == 'nt'
|
||||
# We test if the WinAPI works, because even if we are on Windows
|
||||
# we may be using a terminal that doesn't support the WinAPI
|
||||
# (e.g. Cygwin Terminal). In this case it's up to the terminal
|
||||
# to support the ANSI codes.
|
||||
conversion_supported = on_windows and winapi_test()
|
||||
|
||||
# should we strip ANSI sequences from our output?
|
||||
if strip is None:
|
||||
strip = conversion_supported or (not is_stream_closed(wrapped) and not is_a_tty(wrapped))
|
||||
self.strip = strip
|
||||
|
||||
# should we should convert ANSI sequences into win32 calls?
|
||||
if convert is None:
|
||||
convert = conversion_supported and not is_stream_closed(wrapped) 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, ),
|
||||
AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True),
|
||||
AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True),
|
||||
AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True),
|
||||
AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True),
|
||||
AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True),
|
||||
AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True),
|
||||
AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True),
|
||||
AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True),
|
||||
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, ),
|
||||
AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True),
|
||||
AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True),
|
||||
AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True),
|
||||
AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True),
|
||||
AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True),
|
||||
AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True),
|
||||
AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True),
|
||||
AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True),
|
||||
}
|
||||
return dict()
|
||||
|
||||
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.strip and not is_stream_closed(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
|
||||
text = self.convert_osc(text)
|
||||
for match in self.ANSI_CSI_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(command, paramstring)
|
||||
self.call_win32(command, params)
|
||||
|
||||
|
||||
def extract_params(self, command, paramstring):
|
||||
if command in 'Hf':
|
||||
params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';'))
|
||||
while len(params) < 2:
|
||||
# defaults:
|
||||
params = params + (1,)
|
||||
else:
|
||||
params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0)
|
||||
if len(params) == 0:
|
||||
# defaults:
|
||||
if command in 'JKm':
|
||||
params = (0,)
|
||||
elif command in 'ABCD':
|
||||
params = (1,)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def call_win32(self, command, params):
|
||||
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 'J':
|
||||
winterm.erase_screen(params[0], on_stderr=self.on_stderr)
|
||||
elif command in 'K':
|
||||
winterm.erase_line(params[0], on_stderr=self.on_stderr)
|
||||
elif command in 'Hf': # cursor position - absolute
|
||||
winterm.set_cursor_position(params, on_stderr=self.on_stderr)
|
||||
elif command in 'ABCD': # cursor position - relative
|
||||
n = params[0]
|
||||
# A - up, B - down, C - forward, D - back
|
||||
x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command]
|
||||
winterm.cursor_adjust(x, y, on_stderr=self.on_stderr)
|
||||
|
||||
|
||||
def convert_osc(self, text):
|
||||
for match in self.ANSI_OSC_RE.finditer(text):
|
||||
start, end = match.span()
|
||||
text = text[:start] + text[end:]
|
||||
paramstring, command = match.groups()
|
||||
if command in '\x07': # \x07 = BEL
|
||||
params = paramstring.split(";")
|
||||
# 0 - change title and icon (we will only change title)
|
||||
# 1 - change icon (we don't support this)
|
||||
# 2 - change title
|
||||
if params[0] in '02':
|
||||
winterm.set_title(params[1])
|
||||
return text
|
||||
Vendored
-82
@@ -1,82 +0,0 @@
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
import atexit
|
||||
import contextlib
|
||||
import sys
|
||||
|
||||
from .ansitowin32 import AnsiToWin32
|
||||
|
||||
|
||||
orig_stdout = None
|
||||
orig_stderr = None
|
||||
|
||||
wrapped_stdout = None
|
||||
wrapped_stderr = None
|
||||
|
||||
atexit_done = False
|
||||
|
||||
|
||||
def reset_all():
|
||||
if AnsiToWin32 is not None: # Issue #74: objects might become None at exit
|
||||
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
|
||||
global orig_stdout, orig_stderr
|
||||
|
||||
orig_stdout = sys.stdout
|
||||
orig_stderr = sys.stderr
|
||||
|
||||
if sys.stdout is None:
|
||||
wrapped_stdout = None
|
||||
else:
|
||||
sys.stdout = wrapped_stdout = \
|
||||
wrap_stream(orig_stdout, convert, strip, autoreset, wrap)
|
||||
if sys.stderr is None:
|
||||
wrapped_stderr = None
|
||||
else:
|
||||
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():
|
||||
if orig_stdout is not None:
|
||||
sys.stdout = orig_stdout
|
||||
if orig_stderr is not None:
|
||||
sys.stderr = orig_stderr
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def colorama_text(*args, **kwargs):
|
||||
init(*args, **kwargs)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
deinit()
|
||||
|
||||
|
||||
def reinit():
|
||||
if wrapped_stdout is not None:
|
||||
sys.stdout = wrapped_stdout
|
||||
if wrapped_stderr is not None:
|
||||
sys.stderr = wrapped_stderr
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
Vendored
-154
@@ -1,154 +0,0 @@
|
||||
# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
|
||||
|
||||
# from winbase.h
|
||||
STDOUT = -11
|
||||
STDERR = -12
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
from ctypes import LibraryLoader
|
||||
windll = LibraryLoader(ctypes.WinDLL)
|
||||
from ctypes import wintypes
|
||||
except (AttributeError, ImportError):
|
||||
windll = None
|
||||
SetConsoleTextAttribute = lambda *_: None
|
||||
winapi_test = lambda *_: None
|
||||
else:
|
||||
from ctypes import byref, Structure, c_char, POINTER
|
||||
|
||||
COORD = wintypes._COORD
|
||||
|
||||
class CONSOLE_SCREEN_BUFFER_INFO(Structure):
|
||||
"""struct in wincon.h."""
|
||||
_fields_ = [
|
||||
("dwSize", COORD),
|
||||
("dwCursorPosition", COORD),
|
||||
("wAttributes", wintypes.WORD),
|
||||
("srWindow", wintypes.SMALL_RECT),
|
||||
("dwMaximumWindowSize", COORD),
|
||||
]
|
||||
def __str__(self):
|
||||
return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % (
|
||||
self.dwSize.Y, self.dwSize.X
|
||||
, self.dwCursorPosition.Y, self.dwCursorPosition.X
|
||||
, self.wAttributes
|
||||
, self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right
|
||||
, self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X
|
||||
)
|
||||
|
||||
_GetStdHandle = windll.kernel32.GetStdHandle
|
||||
_GetStdHandle.argtypes = [
|
||||
wintypes.DWORD,
|
||||
]
|
||||
_GetStdHandle.restype = wintypes.HANDLE
|
||||
|
||||
_GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
|
||||
_GetConsoleScreenBufferInfo.argtypes = [
|
||||
wintypes.HANDLE,
|
||||
POINTER(CONSOLE_SCREEN_BUFFER_INFO),
|
||||
]
|
||||
_GetConsoleScreenBufferInfo.restype = wintypes.BOOL
|
||||
|
||||
_SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
|
||||
_SetConsoleTextAttribute.argtypes = [
|
||||
wintypes.HANDLE,
|
||||
wintypes.WORD,
|
||||
]
|
||||
_SetConsoleTextAttribute.restype = wintypes.BOOL
|
||||
|
||||
_SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition
|
||||
_SetConsoleCursorPosition.argtypes = [
|
||||
wintypes.HANDLE,
|
||||
COORD,
|
||||
]
|
||||
_SetConsoleCursorPosition.restype = wintypes.BOOL
|
||||
|
||||
_FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA
|
||||
_FillConsoleOutputCharacterA.argtypes = [
|
||||
wintypes.HANDLE,
|
||||
c_char,
|
||||
wintypes.DWORD,
|
||||
COORD,
|
||||
POINTER(wintypes.DWORD),
|
||||
]
|
||||
_FillConsoleOutputCharacterA.restype = wintypes.BOOL
|
||||
|
||||
_FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
|
||||
_FillConsoleOutputAttribute.argtypes = [
|
||||
wintypes.HANDLE,
|
||||
wintypes.WORD,
|
||||
wintypes.DWORD,
|
||||
COORD,
|
||||
POINTER(wintypes.DWORD),
|
||||
]
|
||||
_FillConsoleOutputAttribute.restype = wintypes.BOOL
|
||||
|
||||
_SetConsoleTitleW = windll.kernel32.SetConsoleTitleA
|
||||
_SetConsoleTitleW.argtypes = [
|
||||
wintypes.LPCSTR
|
||||
]
|
||||
_SetConsoleTitleW.restype = wintypes.BOOL
|
||||
|
||||
handles = {
|
||||
STDOUT: _GetStdHandle(STDOUT),
|
||||
STDERR: _GetStdHandle(STDERR),
|
||||
}
|
||||
|
||||
def winapi_test():
|
||||
handle = handles[STDOUT]
|
||||
csbi = CONSOLE_SCREEN_BUFFER_INFO()
|
||||
success = _GetConsoleScreenBufferInfo(
|
||||
handle, byref(csbi))
|
||||
return bool(success)
|
||||
|
||||
def GetConsoleScreenBufferInfo(stream_id=STDOUT):
|
||||
handle = handles[stream_id]
|
||||
csbi = CONSOLE_SCREEN_BUFFER_INFO()
|
||||
success = _GetConsoleScreenBufferInfo(
|
||||
handle, byref(csbi))
|
||||
return csbi
|
||||
|
||||
def SetConsoleTextAttribute(stream_id, attrs):
|
||||
handle = handles[stream_id]
|
||||
return _SetConsoleTextAttribute(handle, attrs)
|
||||
|
||||
def SetConsoleCursorPosition(stream_id, position, adjust=True):
|
||||
position = COORD(*position)
|
||||
# If the position is out of range, do nothing.
|
||||
if position.Y <= 0 or position.X <= 0:
|
||||
return
|
||||
# Adjust for Windows' SetConsoleCursorPosition:
|
||||
# 1. being 0-based, while ANSI is 1-based.
|
||||
# 2. expecting (x,y), while ANSI uses (y,x).
|
||||
adjusted_position = COORD(position.Y - 1, position.X - 1)
|
||||
if adjust:
|
||||
# Adjust for viewport's scroll position
|
||||
sr = GetConsoleScreenBufferInfo(STDOUT).srWindow
|
||||
adjusted_position.Y += sr.Top
|
||||
adjusted_position.X += sr.Left
|
||||
# Resume normal processing
|
||||
handle = handles[stream_id]
|
||||
return _SetConsoleCursorPosition(handle, adjusted_position)
|
||||
|
||||
def FillConsoleOutputCharacter(stream_id, char, length, start):
|
||||
handle = handles[stream_id]
|
||||
char = c_char(char.encode())
|
||||
length = wintypes.DWORD(length)
|
||||
num_written = wintypes.DWORD(0)
|
||||
# Note that this is hard-coded for ANSI (vs wide) bytes.
|
||||
success = _FillConsoleOutputCharacterA(
|
||||
handle, char, length, start, byref(num_written))
|
||||
return num_written.value
|
||||
|
||||
def FillConsoleOutputAttribute(stream_id, attr, length, start):
|
||||
''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )'''
|
||||
handle = handles[stream_id]
|
||||
attribute = wintypes.WORD(attr)
|
||||
length = wintypes.DWORD(length)
|
||||
num_written = wintypes.DWORD(0)
|
||||
# Note that this is hard-coded for ANSI (vs wide) bytes.
|
||||
return _FillConsoleOutputAttribute(
|
||||
handle, attribute, length, start, byref(num_written))
|
||||
|
||||
def SetConsoleTitle(title):
|
||||
return _SetConsoleTitleW(title)
|
||||
Vendored
-162
@@ -1,162 +0,0 @@
|
||||
# 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
|
||||
BRIGHT_BACKGROUND = 0x80 # dim text, bright 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
|
||||
# In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style.
|
||||
# So that LIGHT_EX colors and BRIGHT style do not clobber each other,
|
||||
# we track them separately, since LIGHT_EX is overwritten by Fore/Back
|
||||
# and BRIGHT is overwritten by Style codes.
|
||||
self._light = 0
|
||||
|
||||
def get_attrs(self):
|
||||
return self._fore + self._back * 16 + (self._style | self._light)
|
||||
|
||||
def set_attrs(self, value):
|
||||
self._fore = value & 7
|
||||
self._back = (value >> 4) & 7
|
||||
self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND)
|
||||
|
||||
def reset_all(self, on_stderr=None):
|
||||
self.set_attrs(self._default)
|
||||
self.set_console(attrs=self._default)
|
||||
|
||||
def fore(self, fore=None, light=False, on_stderr=False):
|
||||
if fore is None:
|
||||
fore = self._default_fore
|
||||
self._fore = fore
|
||||
# Emulate LIGHT_EX with BRIGHT Style
|
||||
if light:
|
||||
self._light |= WinStyle.BRIGHT
|
||||
else:
|
||||
self._light &= ~WinStyle.BRIGHT
|
||||
self.set_console(on_stderr=on_stderr)
|
||||
|
||||
def back(self, back=None, light=False, on_stderr=False):
|
||||
if back is None:
|
||||
back = self._default_back
|
||||
self._back = back
|
||||
# Emulate LIGHT_EX with BRIGHT_BACKGROUND Style
|
||||
if light:
|
||||
self._light |= WinStyle.BRIGHT_BACKGROUND
|
||||
else:
|
||||
self._light &= ~WinStyle.BRIGHT_BACKGROUND
|
||||
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_adjust(self, x, y, on_stderr=False):
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
position = self.get_position(handle)
|
||||
adjusted_position = (position.Y + y, position.X + x)
|
||||
win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False)
|
||||
|
||||
def erase_screen(self, mode=0, on_stderr=False):
|
||||
# 0 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 move cursor to (1,1)
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
csbi = win32.GetConsoleScreenBufferInfo(handle)
|
||||
# get the number of character cells in the current buffer
|
||||
cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y
|
||||
# get number of character cells before current cursor position
|
||||
cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X
|
||||
if mode == 0:
|
||||
from_coord = csbi.dwCursorPosition
|
||||
cells_to_erase = cells_in_screen - cells_before_cursor
|
||||
if mode == 1:
|
||||
from_coord = win32.COORD(0, 0)
|
||||
cells_to_erase = cells_before_cursor
|
||||
elif mode == 2:
|
||||
from_coord = win32.COORD(0, 0)
|
||||
cells_to_erase = cells_in_screen
|
||||
# fill the entire screen with blanks
|
||||
win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
|
||||
# now set the buffer's attributes accordingly
|
||||
win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
|
||||
if mode == 2:
|
||||
# put the cursor where needed
|
||||
win32.SetConsoleCursorPosition(handle, (1, 1))
|
||||
|
||||
def erase_line(self, mode=0, on_stderr=False):
|
||||
# 0 should clear from the cursor to the end of the line.
|
||||
# 1 should clear from the cursor to the beginning of the line.
|
||||
# 2 should clear the entire line.
|
||||
handle = win32.STDOUT
|
||||
if on_stderr:
|
||||
handle = win32.STDERR
|
||||
csbi = win32.GetConsoleScreenBufferInfo(handle)
|
||||
if mode == 0:
|
||||
from_coord = csbi.dwCursorPosition
|
||||
cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X
|
||||
if mode == 1:
|
||||
from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
|
||||
cells_to_erase = csbi.dwCursorPosition.X
|
||||
elif mode == 2:
|
||||
from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
|
||||
cells_to_erase = csbi.dwSize.X
|
||||
# fill the entire screen with blanks
|
||||
win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
|
||||
# now set the buffer's attributes accordingly
|
||||
win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
|
||||
|
||||
def set_title(self, title):
|
||||
win32.SetConsoleTitle(title)
|
||||
Vendored
-152
@@ -1,152 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
clint.colored
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This module provides a simple and elegant wrapper for colorama.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
PY3 = sys.version_info[0] >= 3
|
||||
|
||||
import colorama
|
||||
|
||||
__all__ = (
|
||||
'red', 'green', 'yellow', 'blue',
|
||||
'black', 'magenta', 'cyan', 'white',
|
||||
'clean', 'disable'
|
||||
)
|
||||
|
||||
COLORS = __all__[:-2]
|
||||
|
||||
if 'get_ipython' in dir():
|
||||
"""
|
||||
when ipython is fired lot of variables like _oh, etc are used.
|
||||
There are so many ways to find current python interpreter is ipython.
|
||||
get_ipython is easiest is most appealing for readers to understand.
|
||||
"""
|
||||
DISABLE_COLOR = True
|
||||
else:
|
||||
DISABLE_COLOR = False
|
||||
|
||||
|
||||
class ColoredString(object):
|
||||
"""Enhanced string for __len__ operations on Colored output."""
|
||||
def __init__(self, color, s, always_color=False, bold=False):
|
||||
super(ColoredString, self).__init__()
|
||||
if not PY3 and isinstance(s, unicode):
|
||||
self.s = s.encode('utf-8')
|
||||
else:
|
||||
self.s = s
|
||||
self.color = color
|
||||
self.always_color = always_color
|
||||
self.bold = bold
|
||||
if os.environ.get('CLINT_FORCE_COLOR'):
|
||||
self.always_color = True
|
||||
|
||||
def __getattr__(self, att):
|
||||
def func_help(*args, **kwargs):
|
||||
result = getattr(self.s, att)(*args, **kwargs)
|
||||
try:
|
||||
is_result_string = isinstance(result, basestring)
|
||||
except NameError:
|
||||
is_result_string = isinstance(result, str)
|
||||
if is_result_string:
|
||||
return self._new(result)
|
||||
elif isinstance(result, list):
|
||||
return [self._new(x) for x in result]
|
||||
else:
|
||||
return result
|
||||
return func_help
|
||||
|
||||
@property
|
||||
def color_str(self):
|
||||
style = 'BRIGHT' if self.bold else 'NORMAL'
|
||||
c = '%s%s%s%s%s' % (getattr(colorama.Fore, self.color), getattr(colorama.Style, style), self.s, colorama.Fore.RESET, getattr(colorama.Style, 'NORMAL'))
|
||||
|
||||
if self.always_color:
|
||||
return c
|
||||
elif sys.stdout.isatty() and not DISABLE_COLOR:
|
||||
return c
|
||||
else:
|
||||
return self.s
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return len(self.s)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s-string: '%s'>" % (self.color, self.s)
|
||||
|
||||
def __unicode__(self):
|
||||
value = self.color_str
|
||||
if isinstance(value, bytes):
|
||||
return value.decode('utf8')
|
||||
return value
|
||||
|
||||
if PY3:
|
||||
__str__ = __unicode__
|
||||
else:
|
||||
def __str__(self):
|
||||
return self.color_str
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.color_str)
|
||||
|
||||
def __add__(self, other):
|
||||
return str(self.color_str) + str(other)
|
||||
|
||||
def __radd__(self, other):
|
||||
return str(other) + str(self.color_str)
|
||||
|
||||
def __mul__(self, other):
|
||||
return (self.color_str * other)
|
||||
|
||||
def _new(self, s):
|
||||
return ColoredString(self.color, s)
|
||||
|
||||
|
||||
def clean(s):
|
||||
strip = re.compile("([^-_a-zA-Z0-9!@#%&=,/'\";:~`\$\^\*\(\)\+\[\]\.\{\}\|\?\<\>\\]+|[^\s]+)")
|
||||
txt = strip.sub('', str(s))
|
||||
|
||||
strip = re.compile(r'\[\d+m')
|
||||
txt = strip.sub('', txt)
|
||||
|
||||
return txt
|
||||
|
||||
|
||||
def black(string, always=False, bold=False):
|
||||
return ColoredString('BLACK', string, always_color=always, bold=bold)
|
||||
|
||||
def red(string, always=False, bold=False):
|
||||
return ColoredString('RED', string, always_color=always, bold=bold)
|
||||
|
||||
def green(string, always=False, bold=False):
|
||||
return ColoredString('GREEN', string, always_color=always, bold=bold)
|
||||
|
||||
def yellow(string, always=False, bold=False):
|
||||
return ColoredString('YELLOW', string, always_color=always, bold=bold)
|
||||
|
||||
def blue(string, always=False, bold=False):
|
||||
return ColoredString('BLUE', string, always_color=always, bold=bold)
|
||||
|
||||
def magenta(string, always=False, bold=False):
|
||||
return ColoredString('MAGENTA', string, always_color=always, bold=bold)
|
||||
|
||||
def cyan(string, always=False, bold=False):
|
||||
return ColoredString('CYAN', string, always_color=always, bold=bold)
|
||||
|
||||
def white(string, always=False, bold=False):
|
||||
return ColoredString('WHITE', string, always_color=always, bold=bold)
|
||||
|
||||
def disable():
|
||||
"""Disables colors."""
|
||||
global DISABLE_COLOR
|
||||
|
||||
DISABLE_COLOR = True
|
||||
Vendored
-262
@@ -1,262 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
import shlex
|
||||
import signal
|
||||
|
||||
from pexpect.popen_spawn import PopenSpawn
|
||||
|
||||
# Include `unicode` in STR_TYPES for Python 2.X
|
||||
try:
|
||||
STR_TYPES = (str, unicode)
|
||||
except NameError:
|
||||
STR_TYPES = (str, )
|
||||
|
||||
TIMEOUT = 30
|
||||
|
||||
class Command(object):
|
||||
|
||||
def __init__(self, cmd, timeout=TIMEOUT):
|
||||
super(Command, self).__init__()
|
||||
self.cmd = cmd
|
||||
self.timeout = timeout
|
||||
self.subprocess = None
|
||||
self.blocking = None
|
||||
self.was_run = False
|
||||
self.__out = None
|
||||
self.__err = None
|
||||
|
||||
def __repr__(self):
|
||||
return '<Command {!r}>'.format(self.cmd)
|
||||
|
||||
@property
|
||||
def _popen_args(self):
|
||||
return self.cmd
|
||||
|
||||
@property
|
||||
def _default_popen_kwargs(self):
|
||||
return {
|
||||
'env': os.environ.copy(),
|
||||
'stdin': subprocess.PIPE,
|
||||
'stdout': subprocess.PIPE,
|
||||
'stderr': subprocess.PIPE,
|
||||
'shell': True,
|
||||
'universal_newlines': True,
|
||||
'bufsize': 0,
|
||||
}
|
||||
|
||||
@property
|
||||
def _default_pexpect_kwargs(self):
|
||||
return {
|
||||
'env': os.environ.copy(),
|
||||
'encoding': 'utf-8',
|
||||
'timeout': self.timeout
|
||||
}
|
||||
|
||||
@property
|
||||
def _uses_subprocess(self):
|
||||
return isinstance(self.subprocess, subprocess.Popen)
|
||||
|
||||
@property
|
||||
def _uses_pexpect(self):
|
||||
return isinstance(self.subprocess, PopenSpawn)
|
||||
|
||||
@property
|
||||
def std_out(self):
|
||||
return self.subprocess.stdout
|
||||
|
||||
@property
|
||||
def _pexpect_out(self):
|
||||
if self.subprocess.encoding:
|
||||
result = ''
|
||||
else:
|
||||
result = b''
|
||||
|
||||
if self.subprocess.before:
|
||||
result += self.subprocess.before
|
||||
|
||||
if self.subprocess.after:
|
||||
result += self.subprocess.after
|
||||
|
||||
result += self.subprocess.read()
|
||||
return result
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
"""Std/out output (cached)"""
|
||||
if self.__out is not None:
|
||||
return self.__out
|
||||
|
||||
if self._uses_subprocess:
|
||||
self.__out = self.std_out.read()
|
||||
else:
|
||||
self.__out = self._pexpect_out
|
||||
|
||||
return self.__out
|
||||
|
||||
@property
|
||||
def std_err(self):
|
||||
return self.subprocess.stderr
|
||||
|
||||
@property
|
||||
def err(self):
|
||||
"""Std/err output (cached)"""
|
||||
if self.__err is not None:
|
||||
return self.__err
|
||||
|
||||
if self._uses_subprocess:
|
||||
self.__err = self.std_err.read()
|
||||
return self.__err
|
||||
else:
|
||||
return self._pexpect_out
|
||||
|
||||
@property
|
||||
def pid(self):
|
||||
"""The process' PID."""
|
||||
# Support for pexpect's functionality.
|
||||
if hasattr(self.subprocess, 'proc'):
|
||||
return self.subprocess.proc.pid
|
||||
# Standard subprocess method.
|
||||
return self.subprocess.pid
|
||||
|
||||
@property
|
||||
def return_code(self):
|
||||
# Support for pexpect's functionality.
|
||||
if self._uses_pexpect:
|
||||
return self.subprocess.exitstatus
|
||||
# Standard subprocess method.
|
||||
return self.subprocess.returncode
|
||||
|
||||
@property
|
||||
def std_in(self):
|
||||
return self.subprocess.stdin
|
||||
|
||||
def run(self, block=True, binary=False):
|
||||
"""Runs the given command, with or without pexpect functionality enabled."""
|
||||
self.blocking = block
|
||||
|
||||
# Use subprocess.
|
||||
if self.blocking:
|
||||
popen_kwargs = self._default_popen_kwargs.copy()
|
||||
popen_kwargs['universal_newlines'] = not binary
|
||||
s = subprocess.Popen(self._popen_args, **popen_kwargs)
|
||||
# Otherwise, use pexpect.
|
||||
else:
|
||||
pexpect_kwargs = self._default_pexpect_kwargs.copy()
|
||||
if binary:
|
||||
pexpect_kwargs['encoding'] = None
|
||||
# Enable Python subprocesses to work with expect functionality.
|
||||
pexpect_kwargs['env']['PYTHONUNBUFFERED'] = '1'
|
||||
s = PopenSpawn(self._popen_args, **pexpect_kwargs)
|
||||
self.subprocess = s
|
||||
self.was_run = True
|
||||
|
||||
def expect(self, pattern, timeout=-1):
|
||||
"""Waits on the given pattern to appear in std_out"""
|
||||
|
||||
if self.blocking:
|
||||
raise RuntimeError('expect can only be used on non-blocking commands.')
|
||||
|
||||
self.subprocess.expect(pattern=pattern, timeout=timeout)
|
||||
|
||||
def send(self, s, end=os.linesep, signal=False):
|
||||
"""Sends the given string or signal to std_in."""
|
||||
|
||||
if self.blocking:
|
||||
raise RuntimeError('send can only be used on non-blocking commands.')
|
||||
|
||||
if not signal:
|
||||
if self._uses_subprocess:
|
||||
return self.subprocess.communicate(s + end)
|
||||
else:
|
||||
return self.subprocess.send(s + end)
|
||||
else:
|
||||
self.subprocess.send_signal(s)
|
||||
|
||||
def terminate(self):
|
||||
self.subprocess.terminate()
|
||||
|
||||
def kill(self):
|
||||
self.subprocess.kill(signal.SIGINT)
|
||||
|
||||
def block(self):
|
||||
"""Blocks until process is complete."""
|
||||
if self._uses_subprocess:
|
||||
# consume stdout and stderr
|
||||
stdout, stderr = self.subprocess.communicate()
|
||||
self.__out = stdout
|
||||
self.__err = stderr
|
||||
else:
|
||||
self.subprocess.wait()
|
||||
|
||||
def pipe(self, command, timeout=None):
|
||||
"""Runs the current command and passes its output to the next
|
||||
given process.
|
||||
"""
|
||||
if not timeout:
|
||||
timeout = self.timeout
|
||||
|
||||
if not self.was_run:
|
||||
self.run(block=False)
|
||||
|
||||
data = self.out
|
||||
|
||||
if timeout:
|
||||
c = Command(command, timeout)
|
||||
else:
|
||||
c = Command(command)
|
||||
|
||||
c.run(block=False)
|
||||
if data:
|
||||
c.send(data)
|
||||
c.subprocess.sendeof()
|
||||
c.block()
|
||||
return c
|
||||
|
||||
|
||||
def _expand_args(command):
|
||||
"""Parses command strings and returns a Popen-ready list."""
|
||||
|
||||
# Prepare arguments.
|
||||
if isinstance(command, STR_TYPES):
|
||||
splitter = shlex.shlex(command.encode('utf-8'))
|
||||
splitter.whitespace = '|'
|
||||
splitter.whitespace_split = True
|
||||
command = []
|
||||
|
||||
while True:
|
||||
token = splitter.get_token()
|
||||
if token:
|
||||
command.append(token)
|
||||
else:
|
||||
break
|
||||
|
||||
command = list(map(shlex.split, command))
|
||||
|
||||
return command
|
||||
|
||||
|
||||
def chain(command, timeout=TIMEOUT):
|
||||
commands = _expand_args(command)
|
||||
data = None
|
||||
|
||||
for command in commands:
|
||||
|
||||
c = run(command, block=False, timeout=timeout)
|
||||
|
||||
if data:
|
||||
c.send(data)
|
||||
c.subprocess.sendeof()
|
||||
|
||||
data = c.out
|
||||
|
||||
return c
|
||||
|
||||
|
||||
def run(command, block=True, binary=False, timeout=TIMEOUT):
|
||||
c = Command(command, timeout=timeout)
|
||||
c.run(block=block, binary=binary)
|
||||
|
||||
if block:
|
||||
c.block()
|
||||
|
||||
return c
|
||||
Vendored
-82
@@ -1,82 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2
|
||||
~~~~~~
|
||||
|
||||
Jinja2 is a template engine written in pure Python. It provides a
|
||||
Django inspired non-XML syntax but supports inline expressions and
|
||||
an optional sandboxed environment.
|
||||
|
||||
Nutshell
|
||||
--------
|
||||
|
||||
Here a small example of a Jinja2 template::
|
||||
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Memberlist{% endblock %}
|
||||
{% block content %}
|
||||
<ul>
|
||||
{% for user in users %}
|
||||
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__version__ = '2.9.5'
|
||||
|
||||
# high level interface
|
||||
from jinja2.environment import Environment, Template
|
||||
|
||||
# loaders
|
||||
from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \
|
||||
DictLoader, FunctionLoader, PrefixLoader, ChoiceLoader, \
|
||||
ModuleLoader
|
||||
|
||||
# bytecode caches
|
||||
from jinja2.bccache import BytecodeCache, FileSystemBytecodeCache, \
|
||||
MemcachedBytecodeCache
|
||||
|
||||
# undefined types
|
||||
from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined, \
|
||||
make_logging_undefined
|
||||
|
||||
# exceptions
|
||||
from jinja2.exceptions import TemplateError, UndefinedError, \
|
||||
TemplateNotFound, TemplatesNotFound, TemplateSyntaxError, \
|
||||
TemplateAssertionError
|
||||
|
||||
# decorators and public utilities
|
||||
from jinja2.filters import environmentfilter, contextfilter, \
|
||||
evalcontextfilter
|
||||
from jinja2.utils import Markup, escape, clear_caches, \
|
||||
environmentfunction, evalcontextfunction, contextfunction, \
|
||||
is_undefined, select_autoescape
|
||||
|
||||
__all__ = [
|
||||
'Environment', 'Template', 'BaseLoader', 'FileSystemLoader',
|
||||
'PackageLoader', 'DictLoader', 'FunctionLoader', 'PrefixLoader',
|
||||
'ChoiceLoader', 'BytecodeCache', 'FileSystemBytecodeCache',
|
||||
'MemcachedBytecodeCache', 'Undefined', 'DebugUndefined',
|
||||
'StrictUndefined', 'TemplateError', 'UndefinedError', 'TemplateNotFound',
|
||||
'TemplatesNotFound', 'TemplateSyntaxError', 'TemplateAssertionError',
|
||||
'ModuleLoader', 'environmentfilter', 'contextfilter', 'Markup', 'escape',
|
||||
'environmentfunction', 'contextfunction', 'clear_caches', 'is_undefined',
|
||||
'evalcontextfilter', 'evalcontextfunction', 'make_logging_undefined',
|
||||
'select_autoescape',
|
||||
]
|
||||
|
||||
|
||||
def _patch_async():
|
||||
from jinja2.utils import have_async_gen
|
||||
if have_async_gen:
|
||||
from jinja2.asyncsupport import patch_all
|
||||
patch_all()
|
||||
|
||||
|
||||
_patch_async()
|
||||
del _patch_async
|
||||
Vendored
-99
@@ -1,99 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2._compat
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Some py2/py3 compatibility support based on a stripped down
|
||||
version of six so we don't have to depend on a specific version
|
||||
of it.
|
||||
|
||||
:copyright: Copyright 2013 by the Jinja team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
"""
|
||||
import sys
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PYPY = hasattr(sys, 'pypy_translation_info')
|
||||
_identity = lambda x: x
|
||||
|
||||
|
||||
if not PY2:
|
||||
unichr = chr
|
||||
range_type = range
|
||||
text_type = str
|
||||
string_types = (str,)
|
||||
integer_types = (int,)
|
||||
|
||||
iterkeys = lambda d: iter(d.keys())
|
||||
itervalues = lambda d: iter(d.values())
|
||||
iteritems = lambda d: iter(d.items())
|
||||
|
||||
import pickle
|
||||
from io import BytesIO, StringIO
|
||||
NativeStringIO = StringIO
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
|
||||
ifilter = filter
|
||||
imap = map
|
||||
izip = zip
|
||||
intern = sys.intern
|
||||
|
||||
implements_iterator = _identity
|
||||
implements_to_string = _identity
|
||||
encode_filename = _identity
|
||||
|
||||
else:
|
||||
unichr = unichr
|
||||
text_type = unicode
|
||||
range_type = xrange
|
||||
string_types = (str, unicode)
|
||||
integer_types = (int, long)
|
||||
|
||||
iterkeys = lambda d: d.iterkeys()
|
||||
itervalues = lambda d: d.itervalues()
|
||||
iteritems = lambda d: d.iteritems()
|
||||
|
||||
import cPickle as pickle
|
||||
from cStringIO import StringIO as BytesIO, StringIO
|
||||
NativeStringIO = BytesIO
|
||||
|
||||
exec('def reraise(tp, value, tb=None):\n raise tp, value, tb')
|
||||
|
||||
from itertools import imap, izip, ifilter
|
||||
intern = intern
|
||||
|
||||
def implements_iterator(cls):
|
||||
cls.next = cls.__next__
|
||||
del cls.__next__
|
||||
return cls
|
||||
|
||||
def implements_to_string(cls):
|
||||
cls.__unicode__ = cls.__str__
|
||||
cls.__str__ = lambda x: x.__unicode__().encode('utf-8')
|
||||
return cls
|
||||
|
||||
def encode_filename(filename):
|
||||
if isinstance(filename, unicode):
|
||||
return filename.encode('utf-8')
|
||||
return filename
|
||||
|
||||
|
||||
def with_metaclass(meta, *bases):
|
||||
"""Create a base class with a metaclass."""
|
||||
# This requires a bit of explanation: the basic idea is to make a
|
||||
# dummy metaclass for one level of class instantiation that replaces
|
||||
# itself with the actual metaclass.
|
||||
class metaclass(type):
|
||||
def __new__(cls, name, this_bases, d):
|
||||
return meta(name, bases, d)
|
||||
return type.__new__(metaclass, 'temporary_class', (), {})
|
||||
|
||||
|
||||
try:
|
||||
from urllib.parse import quote_from_bytes as url_quote
|
||||
except ImportError:
|
||||
from urllib import quote as url_quote
|
||||
Vendored
-71
File diff suppressed because one or more lines are too long
Vendored
-146
@@ -1,146 +0,0 @@
|
||||
from functools import wraps
|
||||
|
||||
from jinja2.asyncsupport import auto_aiter
|
||||
from jinja2 import filters
|
||||
|
||||
|
||||
async def auto_to_seq(value):
|
||||
seq = []
|
||||
if hasattr(value, '__aiter__'):
|
||||
async for item in value:
|
||||
seq.append(item)
|
||||
else:
|
||||
for item in value:
|
||||
seq.append(item)
|
||||
return seq
|
||||
|
||||
|
||||
async def async_select_or_reject(args, kwargs, modfunc, lookup_attr):
|
||||
seq, func = filters.prepare_select_or_reject(
|
||||
args, kwargs, modfunc, lookup_attr)
|
||||
if seq:
|
||||
async for item in auto_aiter(seq):
|
||||
if func(item):
|
||||
yield item
|
||||
|
||||
|
||||
def dualfilter(normal_filter, async_filter):
|
||||
wrap_evalctx = False
|
||||
if getattr(normal_filter, 'environmentfilter', False):
|
||||
is_async = lambda args: args[0].is_async
|
||||
wrap_evalctx = False
|
||||
else:
|
||||
if not getattr(normal_filter, 'evalcontextfilter', False) and \
|
||||
not getattr(normal_filter, 'contextfilter', False):
|
||||
wrap_evalctx = True
|
||||
is_async = lambda args: args[0].environment.is_async
|
||||
|
||||
@wraps(normal_filter)
|
||||
def wrapper(*args, **kwargs):
|
||||
b = is_async(args)
|
||||
if wrap_evalctx:
|
||||
args = args[1:]
|
||||
if b:
|
||||
return async_filter(*args, **kwargs)
|
||||
return normal_filter(*args, **kwargs)
|
||||
|
||||
if wrap_evalctx:
|
||||
wrapper.evalcontextfilter = True
|
||||
|
||||
wrapper.asyncfiltervariant = True
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def asyncfiltervariant(original):
|
||||
def decorator(f):
|
||||
return dualfilter(original, f)
|
||||
return decorator
|
||||
|
||||
|
||||
@asyncfiltervariant(filters.do_first)
|
||||
async def do_first(environment, seq):
|
||||
try:
|
||||
return await auto_aiter(seq).__anext__()
|
||||
except StopAsyncIteration:
|
||||
return environment.undefined('No first item, sequence was empty.')
|
||||
|
||||
|
||||
@asyncfiltervariant(filters.do_groupby)
|
||||
async def do_groupby(environment, value, attribute):
|
||||
expr = filters.make_attrgetter(environment, attribute)
|
||||
return [filters._GroupTuple(key, await auto_to_seq(values))
|
||||
for key, values in filters.groupby(sorted(
|
||||
await auto_to_seq(value), key=expr), expr)]
|
||||
|
||||
|
||||
@asyncfiltervariant(filters.do_join)
|
||||
async def do_join(eval_ctx, value, d=u'', attribute=None):
|
||||
return filters.do_join(eval_ctx, await auto_to_seq(value), d, attribute)
|
||||
|
||||
|
||||
@asyncfiltervariant(filters.do_list)
|
||||
async def do_list(value):
|
||||
return await auto_to_seq(value)
|
||||
|
||||
|
||||
@asyncfiltervariant(filters.do_reject)
|
||||
async def do_reject(*args, **kwargs):
|
||||
return async_select_or_reject(args, kwargs, lambda x: not x, False)
|
||||
|
||||
|
||||
@asyncfiltervariant(filters.do_rejectattr)
|
||||
async def do_rejectattr(*args, **kwargs):
|
||||
return async_select_or_reject(args, kwargs, lambda x: not x, True)
|
||||
|
||||
|
||||
@asyncfiltervariant(filters.do_select)
|
||||
async def do_select(*args, **kwargs):
|
||||
return async_select_or_reject(args, kwargs, lambda x: x, False)
|
||||
|
||||
|
||||
@asyncfiltervariant(filters.do_selectattr)
|
||||
async def do_selectattr(*args, **kwargs):
|
||||
return async_select_or_reject(args, kwargs, lambda x: x, True)
|
||||
|
||||
|
||||
@asyncfiltervariant(filters.do_map)
|
||||
async def do_map(*args, **kwargs):
|
||||
seq, func = filters.prepare_map(args, kwargs)
|
||||
if seq:
|
||||
async for item in auto_aiter(seq):
|
||||
yield func(item)
|
||||
|
||||
|
||||
@asyncfiltervariant(filters.do_sum)
|
||||
async def do_sum(environment, iterable, attribute=None, start=0):
|
||||
rv = start
|
||||
if attribute is not None:
|
||||
func = filters.make_attrgetter(environment, attribute)
|
||||
else:
|
||||
func = lambda x: x
|
||||
async for item in auto_aiter(iterable):
|
||||
rv += func(item)
|
||||
return rv
|
||||
|
||||
|
||||
@asyncfiltervariant(filters.do_slice)
|
||||
async def do_slice(value, slices, fill_with=None):
|
||||
return filters.do_slice(await auto_to_seq(value), slices, fill_with)
|
||||
|
||||
|
||||
ASYNC_FILTERS = {
|
||||
'first': do_first,
|
||||
'groupby': do_groupby,
|
||||
'join': do_join,
|
||||
'list': do_list,
|
||||
# we intentionally do not support do_last because that would be
|
||||
# ridiculous
|
||||
'reject': do_reject,
|
||||
'rejectattr': do_rejectattr,
|
||||
'map': do_map,
|
||||
'select': do_select,
|
||||
'selectattr': do_selectattr,
|
||||
'sum': do_sum,
|
||||
'slice': do_slice,
|
||||
}
|
||||
Vendored
-254
@@ -1,254 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.asyncsupport
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Has all the code for async support which is implemented as a patch
|
||||
for supported Python versions.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
import asyncio
|
||||
import inspect
|
||||
from functools import update_wrapper
|
||||
|
||||
from jinja2.utils import concat, internalcode, Markup
|
||||
from jinja2.environment import TemplateModule
|
||||
from jinja2.runtime import LoopContextBase, _last_iteration
|
||||
|
||||
|
||||
async def concat_async(async_gen):
|
||||
rv = []
|
||||
async def collect():
|
||||
async for event in async_gen:
|
||||
rv.append(event)
|
||||
await collect()
|
||||
return concat(rv)
|
||||
|
||||
|
||||
async def generate_async(self, *args, **kwargs):
|
||||
vars = dict(*args, **kwargs)
|
||||
try:
|
||||
async for event in self.root_render_func(self.new_context(vars)):
|
||||
yield event
|
||||
except Exception:
|
||||
exc_info = sys.exc_info()
|
||||
else:
|
||||
return
|
||||
yield self.environment.handle_exception(exc_info, True)
|
||||
|
||||
|
||||
def wrap_generate_func(original_generate):
|
||||
def _convert_generator(self, loop, args, kwargs):
|
||||
async_gen = self.generate_async(*args, **kwargs)
|
||||
try:
|
||||
while 1:
|
||||
yield loop.run_until_complete(async_gen.__anext__())
|
||||
except StopAsyncIteration:
|
||||
pass
|
||||
def generate(self, *args, **kwargs):
|
||||
if not self.environment.is_async:
|
||||
return original_generate(self, *args, **kwargs)
|
||||
return _convert_generator(self, asyncio.get_event_loop(), args, kwargs)
|
||||
return update_wrapper(generate, original_generate)
|
||||
|
||||
|
||||
async def render_async(self, *args, **kwargs):
|
||||
if not self.environment.is_async:
|
||||
raise RuntimeError('The environment was not created with async mode '
|
||||
'enabled.')
|
||||
|
||||
vars = dict(*args, **kwargs)
|
||||
ctx = self.new_context(vars)
|
||||
|
||||
try:
|
||||
return await concat_async(self.root_render_func(ctx))
|
||||
except Exception:
|
||||
exc_info = sys.exc_info()
|
||||
return self.environment.handle_exception(exc_info, True)
|
||||
|
||||
|
||||
def wrap_render_func(original_render):
|
||||
def render(self, *args, **kwargs):
|
||||
if not self.environment.is_async:
|
||||
return original_render(self, *args, **kwargs)
|
||||
loop = asyncio.get_event_loop()
|
||||
return loop.run_until_complete(self.render_async(*args, **kwargs))
|
||||
return update_wrapper(render, original_render)
|
||||
|
||||
|
||||
def wrap_block_reference_call(original_call):
|
||||
@internalcode
|
||||
async def async_call(self):
|
||||
rv = await concat_async(self._stack[self._depth](self._context))
|
||||
if self._context.eval_ctx.autoescape:
|
||||
rv = Markup(rv)
|
||||
return rv
|
||||
|
||||
@internalcode
|
||||
def __call__(self):
|
||||
if not self._context.environment.is_async:
|
||||
return original_call(self)
|
||||
return async_call(self)
|
||||
|
||||
return update_wrapper(__call__, original_call)
|
||||
|
||||
|
||||
def wrap_macro_invoke(original_invoke):
|
||||
@internalcode
|
||||
async def async_invoke(self, arguments, autoescape):
|
||||
rv = await self._func(*arguments)
|
||||
if autoescape:
|
||||
rv = Markup(rv)
|
||||
return rv
|
||||
|
||||
@internalcode
|
||||
def _invoke(self, arguments, autoescape):
|
||||
if not self._environment.is_async:
|
||||
return original_invoke(self, arguments, autoescape)
|
||||
return async_invoke(self, arguments, autoescape)
|
||||
return update_wrapper(_invoke, original_invoke)
|
||||
|
||||
|
||||
@internalcode
|
||||
async def get_default_module_async(self):
|
||||
if self._module is not None:
|
||||
return self._module
|
||||
self._module = rv = await self.make_module_async()
|
||||
return rv
|
||||
|
||||
|
||||
def wrap_default_module(original_default_module):
|
||||
@internalcode
|
||||
def _get_default_module(self):
|
||||
if self.environment.is_async:
|
||||
raise RuntimeError('Template module attribute is unavailable '
|
||||
'in async mode')
|
||||
return original_default_module(self)
|
||||
return _get_default_module
|
||||
|
||||
|
||||
async def make_module_async(self, vars=None, shared=False, locals=None):
|
||||
context = self.new_context(vars, shared, locals)
|
||||
body_stream = []
|
||||
async for item in self.root_render_func(context):
|
||||
body_stream.append(item)
|
||||
return TemplateModule(self, context, body_stream)
|
||||
|
||||
|
||||
def patch_template():
|
||||
from jinja2 import Template
|
||||
Template.generate = wrap_generate_func(Template.generate)
|
||||
Template.generate_async = update_wrapper(
|
||||
generate_async, Template.generate_async)
|
||||
Template.render_async = update_wrapper(
|
||||
render_async, Template.render_async)
|
||||
Template.render = wrap_render_func(Template.render)
|
||||
Template._get_default_module = wrap_default_module(
|
||||
Template._get_default_module)
|
||||
Template._get_default_module_async = get_default_module_async
|
||||
Template.make_module_async = update_wrapper(
|
||||
make_module_async, Template.make_module_async)
|
||||
|
||||
|
||||
def patch_runtime():
|
||||
from jinja2.runtime import BlockReference, Macro
|
||||
BlockReference.__call__ = wrap_block_reference_call(
|
||||
BlockReference.__call__)
|
||||
Macro._invoke = wrap_macro_invoke(Macro._invoke)
|
||||
|
||||
|
||||
def patch_filters():
|
||||
from jinja2.filters import FILTERS
|
||||
from jinja2.asyncfilters import ASYNC_FILTERS
|
||||
FILTERS.update(ASYNC_FILTERS)
|
||||
|
||||
|
||||
def patch_all():
|
||||
patch_template()
|
||||
patch_runtime()
|
||||
patch_filters()
|
||||
|
||||
|
||||
async def auto_await(value):
|
||||
if inspect.isawaitable(value):
|
||||
return await value
|
||||
return value
|
||||
|
||||
|
||||
async def auto_aiter(iterable):
|
||||
if hasattr(iterable, '__aiter__'):
|
||||
async for item in iterable:
|
||||
yield item
|
||||
return
|
||||
for item in iterable:
|
||||
yield item
|
||||
|
||||
|
||||
class AsyncLoopContext(LoopContextBase):
|
||||
|
||||
def __init__(self, async_iterator, after, length, recurse=None,
|
||||
depth0=0):
|
||||
LoopContextBase.__init__(self, recurse, depth0)
|
||||
self._async_iterator = async_iterator
|
||||
self._after = after
|
||||
self._length = length
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
if self._length is None:
|
||||
raise TypeError('Loop length for some iterators cannot be '
|
||||
'lazily calculated in async mode')
|
||||
return self._length
|
||||
|
||||
def __aiter__(self):
|
||||
return AsyncLoopContextIterator(self)
|
||||
|
||||
|
||||
class AsyncLoopContextIterator(object):
|
||||
__slots__ = ('context',)
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
|
||||
def __aiter__(self):
|
||||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
ctx = self.context
|
||||
ctx.index0 += 1
|
||||
if ctx._after is _last_iteration:
|
||||
raise StopAsyncIteration()
|
||||
next_elem = ctx._after
|
||||
try:
|
||||
ctx._after = await ctx._async_iterator.__anext__()
|
||||
except StopAsyncIteration:
|
||||
ctx._after = _last_iteration
|
||||
return next_elem, ctx
|
||||
|
||||
|
||||
async def make_async_loop_context(iterable, recurse=None, depth0=0):
|
||||
# Length is more complicated and less efficient in async mode. The
|
||||
# reason for this is that we cannot know if length will be used
|
||||
# upfront but because length is a property we cannot lazily execute it
|
||||
# later. This means that we need to buffer it up and measure :(
|
||||
#
|
||||
# We however only do this for actual iterators, not for async
|
||||
# iterators as blocking here does not seem like the best idea in the
|
||||
# world.
|
||||
try:
|
||||
length = len(iterable)
|
||||
except (TypeError, AttributeError):
|
||||
if not hasattr(iterable, '__aiter__'):
|
||||
iterable = tuple(iterable)
|
||||
length = len(iterable)
|
||||
else:
|
||||
length = None
|
||||
async_iterator = auto_aiter(iterable)
|
||||
try:
|
||||
after = await async_iterator.__anext__()
|
||||
except StopAsyncIteration:
|
||||
after = _last_iteration
|
||||
return AsyncLoopContext(async_iterator, after, length, recurse, depth0)
|
||||
Vendored
-362
@@ -1,362 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.bccache
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module implements the bytecode cache system Jinja is optionally
|
||||
using. This is useful if you have very complex template situations and
|
||||
the compiliation of all those templates slow down your application too
|
||||
much.
|
||||
|
||||
Situations where this is useful are often forking web applications that
|
||||
are initialized on the first request.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD.
|
||||
"""
|
||||
from os import path, listdir
|
||||
import os
|
||||
import sys
|
||||
import stat
|
||||
import errno
|
||||
import marshal
|
||||
import tempfile
|
||||
import fnmatch
|
||||
from hashlib import sha1
|
||||
from jinja2.utils import open_if_exists
|
||||
from jinja2._compat import BytesIO, pickle, PY2, text_type
|
||||
|
||||
|
||||
# marshal works better on 3.x, one hack less required
|
||||
if not PY2:
|
||||
marshal_dump = marshal.dump
|
||||
marshal_load = marshal.load
|
||||
else:
|
||||
|
||||
def marshal_dump(code, f):
|
||||
if isinstance(f, file):
|
||||
marshal.dump(code, f)
|
||||
else:
|
||||
f.write(marshal.dumps(code))
|
||||
|
||||
def marshal_load(f):
|
||||
if isinstance(f, file):
|
||||
return marshal.load(f)
|
||||
return marshal.loads(f.read())
|
||||
|
||||
|
||||
bc_version = 3
|
||||
|
||||
# magic version used to only change with new jinja versions. With 2.6
|
||||
# we change this to also take Python version changes into account. The
|
||||
# reason for this is that Python tends to segfault if fed earlier bytecode
|
||||
# versions because someone thought it would be a good idea to reuse opcodes
|
||||
# or make Python incompatible with earlier versions.
|
||||
bc_magic = 'j2'.encode('ascii') + \
|
||||
pickle.dumps(bc_version, 2) + \
|
||||
pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1])
|
||||
|
||||
|
||||
class Bucket(object):
|
||||
"""Buckets are used to store the bytecode for one template. It's created
|
||||
and initialized by the bytecode cache and passed to the loading functions.
|
||||
|
||||
The buckets get an internal checksum from the cache assigned and use this
|
||||
to automatically reject outdated cache material. Individual bytecode
|
||||
cache subclasses don't have to care about cache invalidation.
|
||||
"""
|
||||
|
||||
def __init__(self, environment, key, checksum):
|
||||
self.environment = environment
|
||||
self.key = key
|
||||
self.checksum = checksum
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
"""Resets the bucket (unloads the bytecode)."""
|
||||
self.code = None
|
||||
|
||||
def load_bytecode(self, f):
|
||||
"""Loads bytecode from a file or file like object."""
|
||||
# make sure the magic header is correct
|
||||
magic = f.read(len(bc_magic))
|
||||
if magic != bc_magic:
|
||||
self.reset()
|
||||
return
|
||||
# the source code of the file changed, we need to reload
|
||||
checksum = pickle.load(f)
|
||||
if self.checksum != checksum:
|
||||
self.reset()
|
||||
return
|
||||
# if marshal_load fails then we need to reload
|
||||
try:
|
||||
self.code = marshal_load(f)
|
||||
except (EOFError, ValueError, TypeError):
|
||||
self.reset()
|
||||
return
|
||||
|
||||
def write_bytecode(self, f):
|
||||
"""Dump the bytecode into the file or file like object passed."""
|
||||
if self.code is None:
|
||||
raise TypeError('can\'t write empty bucket')
|
||||
f.write(bc_magic)
|
||||
pickle.dump(self.checksum, f, 2)
|
||||
marshal_dump(self.code, f)
|
||||
|
||||
def bytecode_from_string(self, string):
|
||||
"""Load bytecode from a string."""
|
||||
self.load_bytecode(BytesIO(string))
|
||||
|
||||
def bytecode_to_string(self):
|
||||
"""Return the bytecode as string."""
|
||||
out = BytesIO()
|
||||
self.write_bytecode(out)
|
||||
return out.getvalue()
|
||||
|
||||
|
||||
class BytecodeCache(object):
|
||||
"""To implement your own bytecode cache you have to subclass this class
|
||||
and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
|
||||
these methods are passed a :class:`~jinja2.bccache.Bucket`.
|
||||
|
||||
A very basic bytecode cache that saves the bytecode on the file system::
|
||||
|
||||
from os import path
|
||||
|
||||
class MyCache(BytecodeCache):
|
||||
|
||||
def __init__(self, directory):
|
||||
self.directory = directory
|
||||
|
||||
def load_bytecode(self, bucket):
|
||||
filename = path.join(self.directory, bucket.key)
|
||||
if path.exists(filename):
|
||||
with open(filename, 'rb') as f:
|
||||
bucket.load_bytecode(f)
|
||||
|
||||
def dump_bytecode(self, bucket):
|
||||
filename = path.join(self.directory, bucket.key)
|
||||
with open(filename, 'wb') as f:
|
||||
bucket.write_bytecode(f)
|
||||
|
||||
A more advanced version of a filesystem based bytecode cache is part of
|
||||
Jinja2.
|
||||
"""
|
||||
|
||||
def load_bytecode(self, bucket):
|
||||
"""Subclasses have to override this method to load bytecode into a
|
||||
bucket. If they are not able to find code in the cache for the
|
||||
bucket, it must not do anything.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def dump_bytecode(self, bucket):
|
||||
"""Subclasses have to override this method to write the bytecode
|
||||
from a bucket back to the cache. If it unable to do so it must not
|
||||
fail silently but raise an exception.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def clear(self):
|
||||
"""Clears the cache. This method is not used by Jinja2 but should be
|
||||
implemented to allow applications to clear the bytecode cache used
|
||||
by a particular environment.
|
||||
"""
|
||||
|
||||
def get_cache_key(self, name, filename=None):
|
||||
"""Returns the unique hash key for this template name."""
|
||||
hash = sha1(name.encode('utf-8'))
|
||||
if filename is not None:
|
||||
filename = '|' + filename
|
||||
if isinstance(filename, text_type):
|
||||
filename = filename.encode('utf-8')
|
||||
hash.update(filename)
|
||||
return hash.hexdigest()
|
||||
|
||||
def get_source_checksum(self, source):
|
||||
"""Returns a checksum for the source."""
|
||||
return sha1(source.encode('utf-8')).hexdigest()
|
||||
|
||||
def get_bucket(self, environment, name, filename, source):
|
||||
"""Return a cache bucket for the given template. All arguments are
|
||||
mandatory but filename may be `None`.
|
||||
"""
|
||||
key = self.get_cache_key(name, filename)
|
||||
checksum = self.get_source_checksum(source)
|
||||
bucket = Bucket(environment, key, checksum)
|
||||
self.load_bytecode(bucket)
|
||||
return bucket
|
||||
|
||||
def set_bucket(self, bucket):
|
||||
"""Put the bucket into the cache."""
|
||||
self.dump_bytecode(bucket)
|
||||
|
||||
|
||||
class FileSystemBytecodeCache(BytecodeCache):
|
||||
"""A bytecode cache that stores bytecode on the filesystem. It accepts
|
||||
two arguments: The directory where the cache items are stored and a
|
||||
pattern string that is used to build the filename.
|
||||
|
||||
If no directory is specified a default cache directory is selected. On
|
||||
Windows the user's temp directory is used, on UNIX systems a directory
|
||||
is created for the user in the system temp directory.
|
||||
|
||||
The pattern can be used to have multiple separate caches operate on the
|
||||
same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
|
||||
is replaced with the cache key.
|
||||
|
||||
>>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
|
||||
|
||||
This bytecode cache supports clearing of the cache using the clear method.
|
||||
"""
|
||||
|
||||
def __init__(self, directory=None, pattern='__jinja2_%s.cache'):
|
||||
if directory is None:
|
||||
directory = self._get_default_cache_dir()
|
||||
self.directory = directory
|
||||
self.pattern = pattern
|
||||
|
||||
def _get_default_cache_dir(self):
|
||||
def _unsafe_dir():
|
||||
raise RuntimeError('Cannot determine safe temp directory. You '
|
||||
'need to explicitly provide one.')
|
||||
|
||||
tmpdir = tempfile.gettempdir()
|
||||
|
||||
# On windows the temporary directory is used specific unless
|
||||
# explicitly forced otherwise. We can just use that.
|
||||
if os.name == 'nt':
|
||||
return tmpdir
|
||||
if not hasattr(os, 'getuid'):
|
||||
_unsafe_dir()
|
||||
|
||||
dirname = '_jinja2-cache-%d' % os.getuid()
|
||||
actual_dir = os.path.join(tmpdir, dirname)
|
||||
|
||||
try:
|
||||
os.mkdir(actual_dir, stat.S_IRWXU)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
try:
|
||||
os.chmod(actual_dir, stat.S_IRWXU)
|
||||
actual_dir_stat = os.lstat(actual_dir)
|
||||
if actual_dir_stat.st_uid != os.getuid() \
|
||||
or not stat.S_ISDIR(actual_dir_stat.st_mode) \
|
||||
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU:
|
||||
_unsafe_dir()
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
actual_dir_stat = os.lstat(actual_dir)
|
||||
if actual_dir_stat.st_uid != os.getuid() \
|
||||
or not stat.S_ISDIR(actual_dir_stat.st_mode) \
|
||||
or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU:
|
||||
_unsafe_dir()
|
||||
|
||||
return actual_dir
|
||||
|
||||
def _get_cache_filename(self, bucket):
|
||||
return path.join(self.directory, self.pattern % bucket.key)
|
||||
|
||||
def load_bytecode(self, bucket):
|
||||
f = open_if_exists(self._get_cache_filename(bucket), 'rb')
|
||||
if f is not None:
|
||||
try:
|
||||
bucket.load_bytecode(f)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
def dump_bytecode(self, bucket):
|
||||
f = open(self._get_cache_filename(bucket), 'wb')
|
||||
try:
|
||||
bucket.write_bytecode(f)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
def clear(self):
|
||||
# imported lazily here because google app-engine doesn't support
|
||||
# write access on the file system and the function does not exist
|
||||
# normally.
|
||||
from os import remove
|
||||
files = fnmatch.filter(listdir(self.directory), self.pattern % '*')
|
||||
for filename in files:
|
||||
try:
|
||||
remove(path.join(self.directory, filename))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
class MemcachedBytecodeCache(BytecodeCache):
|
||||
"""This class implements a bytecode cache that uses a memcache cache for
|
||||
storing the information. It does not enforce a specific memcache library
|
||||
(tummy's memcache or cmemcache) but will accept any class that provides
|
||||
the minimal interface required.
|
||||
|
||||
Libraries compatible with this class:
|
||||
|
||||
- `werkzeug <http://werkzeug.pocoo.org/>`_.contrib.cache
|
||||
- `python-memcached <http://www.tummy.com/Community/software/python-memcached/>`_
|
||||
- `cmemcache <http://gijsbert.org/cmemcache/>`_
|
||||
|
||||
(Unfortunately the django cache interface is not compatible because it
|
||||
does not support storing binary data, only unicode. You can however pass
|
||||
the underlying cache client to the bytecode cache which is available
|
||||
as `django.core.cache.cache._client`.)
|
||||
|
||||
The minimal interface for the client passed to the constructor is this:
|
||||
|
||||
.. class:: MinimalClientInterface
|
||||
|
||||
.. method:: set(key, value[, timeout])
|
||||
|
||||
Stores the bytecode in the cache. `value` is a string and
|
||||
`timeout` the timeout of the key. If timeout is not provided
|
||||
a default timeout or no timeout should be assumed, if it's
|
||||
provided it's an integer with the number of seconds the cache
|
||||
item should exist.
|
||||
|
||||
.. method:: get(key)
|
||||
|
||||
Returns the value for the cache key. If the item does not
|
||||
exist in the cache the return value must be `None`.
|
||||
|
||||
The other arguments to the constructor are the prefix for all keys that
|
||||
is added before the actual cache key and the timeout for the bytecode in
|
||||
the cache system. We recommend a high (or no) timeout.
|
||||
|
||||
This bytecode cache does not support clearing of used items in the cache.
|
||||
The clear method is a no-operation function.
|
||||
|
||||
.. versionadded:: 2.7
|
||||
Added support for ignoring memcache errors through the
|
||||
`ignore_memcache_errors` parameter.
|
||||
"""
|
||||
|
||||
def __init__(self, client, prefix='jinja2/bytecode/', timeout=None,
|
||||
ignore_memcache_errors=True):
|
||||
self.client = client
|
||||
self.prefix = prefix
|
||||
self.timeout = timeout
|
||||
self.ignore_memcache_errors = ignore_memcache_errors
|
||||
|
||||
def load_bytecode(self, bucket):
|
||||
try:
|
||||
code = self.client.get(self.prefix + bucket.key)
|
||||
except Exception:
|
||||
if not self.ignore_memcache_errors:
|
||||
raise
|
||||
code = None
|
||||
if code is not None:
|
||||
bucket.bytecode_from_string(code)
|
||||
|
||||
def dump_bytecode(self, bucket):
|
||||
args = (self.prefix + bucket.key, bucket.bytecode_to_string())
|
||||
if self.timeout is not None:
|
||||
args += (self.timeout,)
|
||||
try:
|
||||
self.client.set(*args)
|
||||
except Exception:
|
||||
if not self.ignore_memcache_errors:
|
||||
raise
|
||||
Vendored
-1653
File diff suppressed because it is too large
Load Diff
Vendored
-32
@@ -1,32 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja.constants
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Various constants.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
#: list of lorem ipsum words used by the lipsum() helper function
|
||||
LOREM_IPSUM_WORDS = u'''\
|
||||
a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at
|
||||
auctor augue bibendum blandit class commodo condimentum congue consectetuer
|
||||
consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus
|
||||
diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend
|
||||
elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames
|
||||
faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac
|
||||
hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum
|
||||
justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem
|
||||
luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie
|
||||
mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non
|
||||
nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque
|
||||
penatibus per pharetra phasellus placerat platea porta porttitor posuere
|
||||
potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus
|
||||
ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit
|
||||
sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor
|
||||
tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
|
||||
ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
|
||||
viverra volutpat vulputate'''
|
||||
Vendored
-372
@@ -1,372 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.debug
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Implements the debug interface for Jinja. This module does some pretty
|
||||
ugly stuff with the Python traceback system in order to achieve tracebacks
|
||||
with correct line numbers, locals and contents.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
import traceback
|
||||
from types import TracebackType, CodeType
|
||||
from jinja2.utils import missing, internal_code
|
||||
from jinja2.exceptions import TemplateSyntaxError
|
||||
from jinja2._compat import iteritems, reraise, PY2
|
||||
|
||||
# on pypy we can take advantage of transparent proxies
|
||||
try:
|
||||
from __pypy__ import tproxy
|
||||
except ImportError:
|
||||
tproxy = None
|
||||
|
||||
|
||||
# how does the raise helper look like?
|
||||
try:
|
||||
exec("raise TypeError, 'foo'")
|
||||
except SyntaxError:
|
||||
raise_helper = 'raise __jinja_exception__[1]'
|
||||
except TypeError:
|
||||
raise_helper = 'raise __jinja_exception__[0], __jinja_exception__[1]'
|
||||
|
||||
|
||||
class TracebackFrameProxy(object):
|
||||
"""Proxies a traceback frame."""
|
||||
|
||||
def __init__(self, tb):
|
||||
self.tb = tb
|
||||
self._tb_next = None
|
||||
|
||||
@property
|
||||
def tb_next(self):
|
||||
return self._tb_next
|
||||
|
||||
def set_next(self, next):
|
||||
if tb_set_next is not None:
|
||||
try:
|
||||
tb_set_next(self.tb, next and next.tb or None)
|
||||
except Exception:
|
||||
# this function can fail due to all the hackery it does
|
||||
# on various python implementations. We just catch errors
|
||||
# down and ignore them if necessary.
|
||||
pass
|
||||
self._tb_next = next
|
||||
|
||||
@property
|
||||
def is_jinja_frame(self):
|
||||
return '__jinja_template__' in self.tb.tb_frame.f_globals
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.tb, name)
|
||||
|
||||
|
||||
def make_frame_proxy(frame):
|
||||
proxy = TracebackFrameProxy(frame)
|
||||
if tproxy is None:
|
||||
return proxy
|
||||
def operation_handler(operation, *args, **kwargs):
|
||||
if operation in ('__getattribute__', '__getattr__'):
|
||||
return getattr(proxy, args[0])
|
||||
elif operation == '__setattr__':
|
||||
proxy.__setattr__(*args, **kwargs)
|
||||
else:
|
||||
return getattr(proxy, operation)(*args, **kwargs)
|
||||
return tproxy(TracebackType, operation_handler)
|
||||
|
||||
|
||||
class ProcessedTraceback(object):
|
||||
"""Holds a Jinja preprocessed traceback for printing or reraising."""
|
||||
|
||||
def __init__(self, exc_type, exc_value, frames):
|
||||
assert frames, 'no frames for this traceback?'
|
||||
self.exc_type = exc_type
|
||||
self.exc_value = exc_value
|
||||
self.frames = frames
|
||||
|
||||
# newly concatenate the frames (which are proxies)
|
||||
prev_tb = None
|
||||
for tb in self.frames:
|
||||
if prev_tb is not None:
|
||||
prev_tb.set_next(tb)
|
||||
prev_tb = tb
|
||||
prev_tb.set_next(None)
|
||||
|
||||
def render_as_text(self, limit=None):
|
||||
"""Return a string with the traceback."""
|
||||
lines = traceback.format_exception(self.exc_type, self.exc_value,
|
||||
self.frames[0], limit=limit)
|
||||
return ''.join(lines).rstrip()
|
||||
|
||||
def render_as_html(self, full=False):
|
||||
"""Return a unicode string with the traceback as rendered HTML."""
|
||||
from jinja2.debugrenderer import render_traceback
|
||||
return u'%s\n\n<!--\n%s\n-->' % (
|
||||
render_traceback(self, full=full),
|
||||
self.render_as_text().decode('utf-8', 'replace')
|
||||
)
|
||||
|
||||
@property
|
||||
def is_template_syntax_error(self):
|
||||
"""`True` if this is a template syntax error."""
|
||||
return isinstance(self.exc_value, TemplateSyntaxError)
|
||||
|
||||
@property
|
||||
def exc_info(self):
|
||||
"""Exception info tuple with a proxy around the frame objects."""
|
||||
return self.exc_type, self.exc_value, self.frames[0]
|
||||
|
||||
@property
|
||||
def standard_exc_info(self):
|
||||
"""Standard python exc_info for re-raising"""
|
||||
tb = self.frames[0]
|
||||
# the frame will be an actual traceback (or transparent proxy) if
|
||||
# we are on pypy or a python implementation with support for tproxy
|
||||
if type(tb) is not TracebackType:
|
||||
tb = tb.tb
|
||||
return self.exc_type, self.exc_value, tb
|
||||
|
||||
|
||||
def make_traceback(exc_info, source_hint=None):
|
||||
"""Creates a processed traceback object from the exc_info."""
|
||||
exc_type, exc_value, tb = exc_info
|
||||
if isinstance(exc_value, TemplateSyntaxError):
|
||||
exc_info = translate_syntax_error(exc_value, source_hint)
|
||||
initial_skip = 0
|
||||
else:
|
||||
initial_skip = 1
|
||||
return translate_exception(exc_info, initial_skip)
|
||||
|
||||
|
||||
def translate_syntax_error(error, source=None):
|
||||
"""Rewrites a syntax error to please traceback systems."""
|
||||
error.source = source
|
||||
error.translated = True
|
||||
exc_info = (error.__class__, error, None)
|
||||
filename = error.filename
|
||||
if filename is None:
|
||||
filename = '<unknown>'
|
||||
return fake_exc_info(exc_info, filename, error.lineno)
|
||||
|
||||
|
||||
def translate_exception(exc_info, initial_skip=0):
|
||||
"""If passed an exc_info it will automatically rewrite the exceptions
|
||||
all the way down to the correct line numbers and frames.
|
||||
"""
|
||||
tb = exc_info[2]
|
||||
frames = []
|
||||
|
||||
# skip some internal frames if wanted
|
||||
for x in range(initial_skip):
|
||||
if tb is not None:
|
||||
tb = tb.tb_next
|
||||
initial_tb = tb
|
||||
|
||||
while tb is not None:
|
||||
# skip frames decorated with @internalcode. These are internal
|
||||
# calls we can't avoid and that are useless in template debugging
|
||||
# output.
|
||||
if tb.tb_frame.f_code in internal_code:
|
||||
tb = tb.tb_next
|
||||
continue
|
||||
|
||||
# save a reference to the next frame if we override the current
|
||||
# one with a faked one.
|
||||
next = tb.tb_next
|
||||
|
||||
# fake template exceptions
|
||||
template = tb.tb_frame.f_globals.get('__jinja_template__')
|
||||
if template is not None:
|
||||
lineno = template.get_corresponding_lineno(tb.tb_lineno)
|
||||
tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
|
||||
lineno)[2]
|
||||
|
||||
frames.append(make_frame_proxy(tb))
|
||||
tb = next
|
||||
|
||||
# if we don't have any exceptions in the frames left, we have to
|
||||
# reraise it unchanged.
|
||||
# XXX: can we backup here? when could this happen?
|
||||
if not frames:
|
||||
reraise(exc_info[0], exc_info[1], exc_info[2])
|
||||
|
||||
return ProcessedTraceback(exc_info[0], exc_info[1], frames)
|
||||
|
||||
|
||||
def get_jinja_locals(real_locals):
|
||||
ctx = real_locals.get('context')
|
||||
if ctx:
|
||||
locals = ctx.get_all()
|
||||
else:
|
||||
locals = {}
|
||||
|
||||
local_overrides = {}
|
||||
|
||||
for name, value in iteritems(real_locals):
|
||||
if not name.startswith('l_') or value is missing:
|
||||
continue
|
||||
try:
|
||||
_, depth, name = name.split('_', 2)
|
||||
depth = int(depth)
|
||||
except ValueError:
|
||||
continue
|
||||
cur_depth = local_overrides.get(name, (-1,))[0]
|
||||
if cur_depth < depth:
|
||||
local_overrides[name] = (depth, value)
|
||||
|
||||
for name, (_, value) in iteritems(local_overrides):
|
||||
if value is missing:
|
||||
locals.pop(name, None)
|
||||
else:
|
||||
locals[name] = value
|
||||
|
||||
return locals
|
||||
|
||||
|
||||
def fake_exc_info(exc_info, filename, lineno):
|
||||
"""Helper for `translate_exception`."""
|
||||
exc_type, exc_value, tb = exc_info
|
||||
|
||||
# figure the real context out
|
||||
if tb is not None:
|
||||
locals = get_jinja_locals(tb.tb_frame.f_locals)
|
||||
|
||||
# if there is a local called __jinja_exception__, we get
|
||||
# rid of it to not break the debug functionality.
|
||||
locals.pop('__jinja_exception__', None)
|
||||
else:
|
||||
locals = {}
|
||||
|
||||
# assamble fake globals we need
|
||||
globals = {
|
||||
'__name__': filename,
|
||||
'__file__': filename,
|
||||
'__jinja_exception__': exc_info[:2],
|
||||
|
||||
# we don't want to keep the reference to the template around
|
||||
# to not cause circular dependencies, but we mark it as Jinja
|
||||
# frame for the ProcessedTraceback
|
||||
'__jinja_template__': None
|
||||
}
|
||||
|
||||
# and fake the exception
|
||||
code = compile('\n' * (lineno - 1) + raise_helper, filename, 'exec')
|
||||
|
||||
# if it's possible, change the name of the code. This won't work
|
||||
# on some python environments such as google appengine
|
||||
try:
|
||||
if tb is None:
|
||||
location = 'template'
|
||||
else:
|
||||
function = tb.tb_frame.f_code.co_name
|
||||
if function == 'root':
|
||||
location = 'top-level template code'
|
||||
elif function.startswith('block_'):
|
||||
location = 'block "%s"' % function[6:]
|
||||
else:
|
||||
location = 'template'
|
||||
|
||||
if PY2:
|
||||
code = CodeType(0, code.co_nlocals, code.co_stacksize,
|
||||
code.co_flags, code.co_code, code.co_consts,
|
||||
code.co_names, code.co_varnames, filename,
|
||||
location, code.co_firstlineno,
|
||||
code.co_lnotab, (), ())
|
||||
else:
|
||||
code = CodeType(0, code.co_kwonlyargcount,
|
||||
code.co_nlocals, code.co_stacksize,
|
||||
code.co_flags, code.co_code, code.co_consts,
|
||||
code.co_names, code.co_varnames, filename,
|
||||
location, code.co_firstlineno,
|
||||
code.co_lnotab, (), ())
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
# execute the code and catch the new traceback
|
||||
try:
|
||||
exec(code, globals, locals)
|
||||
except:
|
||||
exc_info = sys.exc_info()
|
||||
new_tb = exc_info[2].tb_next
|
||||
|
||||
# return without this frame
|
||||
return exc_info[:2] + (new_tb,)
|
||||
|
||||
|
||||
def _init_ugly_crap():
|
||||
"""This function implements a few ugly things so that we can patch the
|
||||
traceback objects. The function returned allows resetting `tb_next` on
|
||||
any python traceback object. Do not attempt to use this on non cpython
|
||||
interpreters
|
||||
"""
|
||||
import ctypes
|
||||
from types import TracebackType
|
||||
|
||||
if PY2:
|
||||
# figure out size of _Py_ssize_t for Python 2:
|
||||
if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'):
|
||||
_Py_ssize_t = ctypes.c_int64
|
||||
else:
|
||||
_Py_ssize_t = ctypes.c_int
|
||||
else:
|
||||
# platform ssize_t on Python 3
|
||||
_Py_ssize_t = ctypes.c_ssize_t
|
||||
|
||||
# regular python
|
||||
class _PyObject(ctypes.Structure):
|
||||
pass
|
||||
_PyObject._fields_ = [
|
||||
('ob_refcnt', _Py_ssize_t),
|
||||
('ob_type', ctypes.POINTER(_PyObject))
|
||||
]
|
||||
|
||||
# python with trace
|
||||
if hasattr(sys, 'getobjects'):
|
||||
class _PyObject(ctypes.Structure):
|
||||
pass
|
||||
_PyObject._fields_ = [
|
||||
('_ob_next', ctypes.POINTER(_PyObject)),
|
||||
('_ob_prev', ctypes.POINTER(_PyObject)),
|
||||
('ob_refcnt', _Py_ssize_t),
|
||||
('ob_type', ctypes.POINTER(_PyObject))
|
||||
]
|
||||
|
||||
class _Traceback(_PyObject):
|
||||
pass
|
||||
_Traceback._fields_ = [
|
||||
('tb_next', ctypes.POINTER(_Traceback)),
|
||||
('tb_frame', ctypes.POINTER(_PyObject)),
|
||||
('tb_lasti', ctypes.c_int),
|
||||
('tb_lineno', ctypes.c_int)
|
||||
]
|
||||
|
||||
def tb_set_next(tb, next):
|
||||
"""Set the tb_next attribute of a traceback object."""
|
||||
if not (isinstance(tb, TracebackType) and
|
||||
(next is None or isinstance(next, TracebackType))):
|
||||
raise TypeError('tb_set_next arguments must be traceback objects')
|
||||
obj = _Traceback.from_address(id(tb))
|
||||
if tb.tb_next is not None:
|
||||
old = _Traceback.from_address(id(tb.tb_next))
|
||||
old.ob_refcnt -= 1
|
||||
if next is None:
|
||||
obj.tb_next = ctypes.POINTER(_Traceback)()
|
||||
else:
|
||||
next = _Traceback.from_address(id(next))
|
||||
next.ob_refcnt += 1
|
||||
obj.tb_next = ctypes.pointer(next)
|
||||
|
||||
return tb_set_next
|
||||
|
||||
|
||||
# try to get a tb_set_next implementation if we don't have transparent
|
||||
# proxies.
|
||||
tb_set_next = None
|
||||
if tproxy is None:
|
||||
try:
|
||||
tb_set_next = _init_ugly_crap()
|
||||
except:
|
||||
pass
|
||||
del _init_ugly_crap
|
||||
Vendored
-54
@@ -1,54 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.defaults
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Jinja default filters and tags.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from jinja2._compat import range_type
|
||||
from jinja2.utils import generate_lorem_ipsum, Cycler, Joiner
|
||||
|
||||
|
||||
# defaults for the parser / lexer
|
||||
BLOCK_START_STRING = '{%'
|
||||
BLOCK_END_STRING = '%}'
|
||||
VARIABLE_START_STRING = '{{'
|
||||
VARIABLE_END_STRING = '}}'
|
||||
COMMENT_START_STRING = '{#'
|
||||
COMMENT_END_STRING = '#}'
|
||||
LINE_STATEMENT_PREFIX = None
|
||||
LINE_COMMENT_PREFIX = None
|
||||
TRIM_BLOCKS = False
|
||||
LSTRIP_BLOCKS = False
|
||||
NEWLINE_SEQUENCE = '\n'
|
||||
KEEP_TRAILING_NEWLINE = False
|
||||
|
||||
|
||||
# default filters, tests and namespace
|
||||
from jinja2.filters import FILTERS as DEFAULT_FILTERS
|
||||
from jinja2.tests import TESTS as DEFAULT_TESTS
|
||||
DEFAULT_NAMESPACE = {
|
||||
'range': range_type,
|
||||
'dict': dict,
|
||||
'lipsum': generate_lorem_ipsum,
|
||||
'cycler': Cycler,
|
||||
'joiner': Joiner
|
||||
}
|
||||
|
||||
|
||||
# default policies
|
||||
DEFAULT_POLICIES = {
|
||||
'compiler.ascii_str': True,
|
||||
'urlize.rel': 'noopener',
|
||||
'urlize.target': None,
|
||||
'truncate.leeway': 5,
|
||||
'json.dumps_function': None,
|
||||
'json.dumps_kwargs': {'sort_keys': True},
|
||||
}
|
||||
|
||||
|
||||
# export all constants
|
||||
__all__ = tuple(x for x in locals().keys() if x.isupper())
|
||||
Vendored
-1276
File diff suppressed because it is too large
Load Diff
Vendored
-146
@@ -1,146 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.exceptions
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Jinja exceptions.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from jinja2._compat import imap, text_type, PY2, implements_to_string
|
||||
|
||||
|
||||
class TemplateError(Exception):
|
||||
"""Baseclass for all template errors."""
|
||||
|
||||
if PY2:
|
||||
def __init__(self, message=None):
|
||||
if message is not None:
|
||||
message = text_type(message).encode('utf-8')
|
||||
Exception.__init__(self, message)
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
if self.args:
|
||||
message = self.args[0]
|
||||
if message is not None:
|
||||
return message.decode('utf-8', 'replace')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.message or u''
|
||||
else:
|
||||
def __init__(self, message=None):
|
||||
Exception.__init__(self, message)
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
if self.args:
|
||||
message = self.args[0]
|
||||
if message is not None:
|
||||
return message
|
||||
|
||||
|
||||
@implements_to_string
|
||||
class TemplateNotFound(IOError, LookupError, TemplateError):
|
||||
"""Raised if a template does not exist."""
|
||||
|
||||
# looks weird, but removes the warning descriptor that just
|
||||
# bogusly warns us about message being deprecated
|
||||
message = None
|
||||
|
||||
def __init__(self, name, message=None):
|
||||
IOError.__init__(self)
|
||||
if message is None:
|
||||
message = name
|
||||
self.message = message
|
||||
self.name = name
|
||||
self.templates = [name]
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
|
||||
class TemplatesNotFound(TemplateNotFound):
|
||||
"""Like :class:`TemplateNotFound` but raised if multiple templates
|
||||
are selected. This is a subclass of :class:`TemplateNotFound`
|
||||
exception, so just catching the base exception will catch both.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
"""
|
||||
|
||||
def __init__(self, names=(), message=None):
|
||||
if message is None:
|
||||
message = u'none of the templates given were found: ' + \
|
||||
u', '.join(imap(text_type, names))
|
||||
TemplateNotFound.__init__(self, names and names[-1] or None, message)
|
||||
self.templates = list(names)
|
||||
|
||||
|
||||
@implements_to_string
|
||||
class TemplateSyntaxError(TemplateError):
|
||||
"""Raised to tell the user that there is a problem with the template."""
|
||||
|
||||
def __init__(self, message, lineno, name=None, filename=None):
|
||||
TemplateError.__init__(self, message)
|
||||
self.lineno = lineno
|
||||
self.name = name
|
||||
self.filename = filename
|
||||
self.source = None
|
||||
|
||||
# this is set to True if the debug.translate_syntax_error
|
||||
# function translated the syntax error into a new traceback
|
||||
self.translated = False
|
||||
|
||||
def __str__(self):
|
||||
# for translated errors we only return the message
|
||||
if self.translated:
|
||||
return self.message
|
||||
|
||||
# otherwise attach some stuff
|
||||
location = 'line %d' % self.lineno
|
||||
name = self.filename or self.name
|
||||
if name:
|
||||
location = 'File "%s", %s' % (name, location)
|
||||
lines = [self.message, ' ' + location]
|
||||
|
||||
# if the source is set, add the line to the output
|
||||
if self.source is not None:
|
||||
try:
|
||||
line = self.source.splitlines()[self.lineno - 1]
|
||||
except IndexError:
|
||||
line = None
|
||||
if line:
|
||||
lines.append(' ' + line.strip())
|
||||
|
||||
return u'\n'.join(lines)
|
||||
|
||||
|
||||
class TemplateAssertionError(TemplateSyntaxError):
|
||||
"""Like a template syntax error, but covers cases where something in the
|
||||
template caused an error at compile time that wasn't necessarily caused
|
||||
by a syntax error. However it's a direct subclass of
|
||||
:exc:`TemplateSyntaxError` and has the same attributes.
|
||||
"""
|
||||
|
||||
|
||||
class TemplateRuntimeError(TemplateError):
|
||||
"""A generic runtime error in the template engine. Under some situations
|
||||
Jinja may raise this exception.
|
||||
"""
|
||||
|
||||
|
||||
class UndefinedError(TemplateRuntimeError):
|
||||
"""Raised if a template tries to operate on :class:`Undefined`."""
|
||||
|
||||
|
||||
class SecurityError(TemplateRuntimeError):
|
||||
"""Raised if a template tries to do something insecure if the
|
||||
sandbox is enabled.
|
||||
"""
|
||||
|
||||
|
||||
class FilterArgumentError(TemplateRuntimeError):
|
||||
"""This error is raised if a filter was called with inappropriate
|
||||
arguments
|
||||
"""
|
||||
Vendored
-609
@@ -1,609 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.ext
|
||||
~~~~~~~~~~
|
||||
|
||||
Jinja extensions allow to add custom tags similar to the way django custom
|
||||
tags work. By default two example extensions exist: an i18n and a cache
|
||||
extension.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD.
|
||||
"""
|
||||
from jinja2 import nodes
|
||||
from jinja2.defaults import BLOCK_START_STRING, \
|
||||
BLOCK_END_STRING, VARIABLE_START_STRING, VARIABLE_END_STRING, \
|
||||
COMMENT_START_STRING, COMMENT_END_STRING, LINE_STATEMENT_PREFIX, \
|
||||
LINE_COMMENT_PREFIX, TRIM_BLOCKS, NEWLINE_SEQUENCE, \
|
||||
KEEP_TRAILING_NEWLINE, LSTRIP_BLOCKS
|
||||
from jinja2.environment import Environment
|
||||
from jinja2.runtime import concat
|
||||
from jinja2.exceptions import TemplateAssertionError, TemplateSyntaxError
|
||||
from jinja2.utils import contextfunction, import_string, Markup
|
||||
from jinja2._compat import with_metaclass, string_types, iteritems
|
||||
|
||||
|
||||
# the only real useful gettext functions for a Jinja template. Note
|
||||
# that ugettext must be assigned to gettext as Jinja doesn't support
|
||||
# non unicode strings.
|
||||
GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
|
||||
|
||||
|
||||
class ExtensionRegistry(type):
|
||||
"""Gives the extension an unique identifier."""
|
||||
|
||||
def __new__(cls, name, bases, d):
|
||||
rv = type.__new__(cls, name, bases, d)
|
||||
rv.identifier = rv.__module__ + '.' + rv.__name__
|
||||
return rv
|
||||
|
||||
|
||||
class Extension(with_metaclass(ExtensionRegistry, object)):
|
||||
"""Extensions can be used to add extra functionality to the Jinja template
|
||||
system at the parser level. Custom extensions are bound to an environment
|
||||
but may not store environment specific data on `self`. The reason for
|
||||
this is that an extension can be bound to another environment (for
|
||||
overlays) by creating a copy and reassigning the `environment` attribute.
|
||||
|
||||
As extensions are created by the environment they cannot accept any
|
||||
arguments for configuration. One may want to work around that by using
|
||||
a factory function, but that is not possible as extensions are identified
|
||||
by their import name. The correct way to configure the extension is
|
||||
storing the configuration values on the environment. Because this way the
|
||||
environment ends up acting as central configuration storage the
|
||||
attributes may clash which is why extensions have to ensure that the names
|
||||
they choose for configuration are not too generic. ``prefix`` for example
|
||||
is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
|
||||
name as includes the name of the extension (fragment cache).
|
||||
"""
|
||||
|
||||
#: if this extension parses this is the list of tags it's listening to.
|
||||
tags = set()
|
||||
|
||||
#: the priority of that extension. This is especially useful for
|
||||
#: extensions that preprocess values. A lower value means higher
|
||||
#: priority.
|
||||
#:
|
||||
#: .. versionadded:: 2.4
|
||||
priority = 100
|
||||
|
||||
def __init__(self, environment):
|
||||
self.environment = environment
|
||||
|
||||
def bind(self, environment):
|
||||
"""Create a copy of this extension bound to another environment."""
|
||||
rv = object.__new__(self.__class__)
|
||||
rv.__dict__.update(self.__dict__)
|
||||
rv.environment = environment
|
||||
return rv
|
||||
|
||||
def preprocess(self, source, name, filename=None):
|
||||
"""This method is called before the actual lexing and can be used to
|
||||
preprocess the source. The `filename` is optional. The return value
|
||||
must be the preprocessed source.
|
||||
"""
|
||||
return source
|
||||
|
||||
def filter_stream(self, stream):
|
||||
"""It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
|
||||
to filter tokens returned. This method has to return an iterable of
|
||||
:class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a
|
||||
:class:`~jinja2.lexer.TokenStream`.
|
||||
|
||||
In the `ext` folder of the Jinja2 source distribution there is a file
|
||||
called `inlinegettext.py` which implements a filter that utilizes this
|
||||
method.
|
||||
"""
|
||||
return stream
|
||||
|
||||
def parse(self, parser):
|
||||
"""If any of the :attr:`tags` matched this method is called with the
|
||||
parser as first argument. The token the parser stream is pointing at
|
||||
is the name token that matched. This method has to return one or a
|
||||
list of multiple nodes.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def attr(self, name, lineno=None):
|
||||
"""Return an attribute node for the current extension. This is useful
|
||||
to pass constants on extensions to generated template code.
|
||||
|
||||
::
|
||||
|
||||
self.attr('_my_attribute', lineno=lineno)
|
||||
"""
|
||||
return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
|
||||
|
||||
def call_method(self, name, args=None, kwargs=None, dyn_args=None,
|
||||
dyn_kwargs=None, lineno=None):
|
||||
"""Call a method of the extension. This is a shortcut for
|
||||
:meth:`attr` + :class:`jinja2.nodes.Call`.
|
||||
"""
|
||||
if args is None:
|
||||
args = []
|
||||
if kwargs is None:
|
||||
kwargs = []
|
||||
return nodes.Call(self.attr(name, lineno=lineno), args, kwargs,
|
||||
dyn_args, dyn_kwargs, lineno=lineno)
|
||||
|
||||
|
||||
@contextfunction
|
||||
def _gettext_alias(__context, *args, **kwargs):
|
||||
return __context.call(__context.resolve('gettext'), *args, **kwargs)
|
||||
|
||||
|
||||
def _make_new_gettext(func):
|
||||
@contextfunction
|
||||
def gettext(__context, __string, **variables):
|
||||
rv = __context.call(func, __string)
|
||||
if __context.eval_ctx.autoescape:
|
||||
rv = Markup(rv)
|
||||
return rv % variables
|
||||
return gettext
|
||||
|
||||
|
||||
def _make_new_ngettext(func):
|
||||
@contextfunction
|
||||
def ngettext(__context, __singular, __plural, __num, **variables):
|
||||
variables.setdefault('num', __num)
|
||||
rv = __context.call(func, __singular, __plural, __num)
|
||||
if __context.eval_ctx.autoescape:
|
||||
rv = Markup(rv)
|
||||
return rv % variables
|
||||
return ngettext
|
||||
|
||||
|
||||
class InternationalizationExtension(Extension):
|
||||
"""This extension adds gettext support to Jinja2."""
|
||||
tags = set(['trans'])
|
||||
|
||||
# TODO: the i18n extension is currently reevaluating values in a few
|
||||
# situations. Take this example:
|
||||
# {% trans count=something() %}{{ count }} foo{% pluralize
|
||||
# %}{{ count }} fooss{% endtrans %}
|
||||
# something is called twice here. One time for the gettext value and
|
||||
# the other time for the n-parameter of the ngettext function.
|
||||
|
||||
def __init__(self, environment):
|
||||
Extension.__init__(self, environment)
|
||||
environment.globals['_'] = _gettext_alias
|
||||
environment.extend(
|
||||
install_gettext_translations=self._install,
|
||||
install_null_translations=self._install_null,
|
||||
install_gettext_callables=self._install_callables,
|
||||
uninstall_gettext_translations=self._uninstall,
|
||||
extract_translations=self._extract,
|
||||
newstyle_gettext=False
|
||||
)
|
||||
|
||||
def _install(self, translations, newstyle=None):
|
||||
gettext = getattr(translations, 'ugettext', None)
|
||||
if gettext is None:
|
||||
gettext = translations.gettext
|
||||
ngettext = getattr(translations, 'ungettext', None)
|
||||
if ngettext is None:
|
||||
ngettext = translations.ngettext
|
||||
self._install_callables(gettext, ngettext, newstyle)
|
||||
|
||||
def _install_null(self, newstyle=None):
|
||||
self._install_callables(
|
||||
lambda x: x,
|
||||
lambda s, p, n: (n != 1 and (p,) or (s,))[0],
|
||||
newstyle
|
||||
)
|
||||
|
||||
def _install_callables(self, gettext, ngettext, newstyle=None):
|
||||
if newstyle is not None:
|
||||
self.environment.newstyle_gettext = newstyle
|
||||
if self.environment.newstyle_gettext:
|
||||
gettext = _make_new_gettext(gettext)
|
||||
ngettext = _make_new_ngettext(ngettext)
|
||||
self.environment.globals.update(
|
||||
gettext=gettext,
|
||||
ngettext=ngettext
|
||||
)
|
||||
|
||||
def _uninstall(self, translations):
|
||||
for key in 'gettext', 'ngettext':
|
||||
self.environment.globals.pop(key, None)
|
||||
|
||||
def _extract(self, source, gettext_functions=GETTEXT_FUNCTIONS):
|
||||
if isinstance(source, string_types):
|
||||
source = self.environment.parse(source)
|
||||
return extract_from_ast(source, gettext_functions)
|
||||
|
||||
def parse(self, parser):
|
||||
"""Parse a translatable tag."""
|
||||
lineno = next(parser.stream).lineno
|
||||
num_called_num = False
|
||||
|
||||
# find all the variables referenced. Additionally a variable can be
|
||||
# defined in the body of the trans block too, but this is checked at
|
||||
# a later state.
|
||||
plural_expr = None
|
||||
plural_expr_assignment = None
|
||||
variables = {}
|
||||
while parser.stream.current.type != 'block_end':
|
||||
if variables:
|
||||
parser.stream.expect('comma')
|
||||
|
||||
# skip colon for python compatibility
|
||||
if parser.stream.skip_if('colon'):
|
||||
break
|
||||
|
||||
name = parser.stream.expect('name')
|
||||
if name.value in variables:
|
||||
parser.fail('translatable variable %r defined twice.' %
|
||||
name.value, name.lineno,
|
||||
exc=TemplateAssertionError)
|
||||
|
||||
# expressions
|
||||
if parser.stream.current.type == 'assign':
|
||||
next(parser.stream)
|
||||
variables[name.value] = var = parser.parse_expression()
|
||||
else:
|
||||
variables[name.value] = var = nodes.Name(name.value, 'load')
|
||||
|
||||
if plural_expr is None:
|
||||
if isinstance(var, nodes.Call):
|
||||
plural_expr = nodes.Name('_trans', 'load')
|
||||
variables[name.value] = plural_expr
|
||||
plural_expr_assignment = nodes.Assign(
|
||||
nodes.Name('_trans', 'store'), var)
|
||||
else:
|
||||
plural_expr = var
|
||||
num_called_num = name.value == 'num'
|
||||
|
||||
parser.stream.expect('block_end')
|
||||
|
||||
plural = plural_names = None
|
||||
have_plural = False
|
||||
referenced = set()
|
||||
|
||||
# now parse until endtrans or pluralize
|
||||
singular_names, singular = self._parse_block(parser, True)
|
||||
if singular_names:
|
||||
referenced.update(singular_names)
|
||||
if plural_expr is None:
|
||||
plural_expr = nodes.Name(singular_names[0], 'load')
|
||||
num_called_num = singular_names[0] == 'num'
|
||||
|
||||
# if we have a pluralize block, we parse that too
|
||||
if parser.stream.current.test('name:pluralize'):
|
||||
have_plural = True
|
||||
next(parser.stream)
|
||||
if parser.stream.current.type != 'block_end':
|
||||
name = parser.stream.expect('name')
|
||||
if name.value not in variables:
|
||||
parser.fail('unknown variable %r for pluralization' %
|
||||
name.value, name.lineno,
|
||||
exc=TemplateAssertionError)
|
||||
plural_expr = variables[name.value]
|
||||
num_called_num = name.value == 'num'
|
||||
parser.stream.expect('block_end')
|
||||
plural_names, plural = self._parse_block(parser, False)
|
||||
next(parser.stream)
|
||||
referenced.update(plural_names)
|
||||
else:
|
||||
next(parser.stream)
|
||||
|
||||
# register free names as simple name expressions
|
||||
for var in referenced:
|
||||
if var not in variables:
|
||||
variables[var] = nodes.Name(var, 'load')
|
||||
|
||||
if not have_plural:
|
||||
plural_expr = None
|
||||
elif plural_expr is None:
|
||||
parser.fail('pluralize without variables', lineno)
|
||||
|
||||
node = self._make_node(singular, plural, variables, plural_expr,
|
||||
bool(referenced),
|
||||
num_called_num and have_plural)
|
||||
node.set_lineno(lineno)
|
||||
if plural_expr_assignment is not None:
|
||||
return [plural_expr_assignment, node]
|
||||
else:
|
||||
return node
|
||||
|
||||
def _parse_block(self, parser, allow_pluralize):
|
||||
"""Parse until the next block tag with a given name."""
|
||||
referenced = []
|
||||
buf = []
|
||||
while 1:
|
||||
if parser.stream.current.type == 'data':
|
||||
buf.append(parser.stream.current.value.replace('%', '%%'))
|
||||
next(parser.stream)
|
||||
elif parser.stream.current.type == 'variable_begin':
|
||||
next(parser.stream)
|
||||
name = parser.stream.expect('name').value
|
||||
referenced.append(name)
|
||||
buf.append('%%(%s)s' % name)
|
||||
parser.stream.expect('variable_end')
|
||||
elif parser.stream.current.type == 'block_begin':
|
||||
next(parser.stream)
|
||||
if parser.stream.current.test('name:endtrans'):
|
||||
break
|
||||
elif parser.stream.current.test('name:pluralize'):
|
||||
if allow_pluralize:
|
||||
break
|
||||
parser.fail('a translatable section can have only one '
|
||||
'pluralize section')
|
||||
parser.fail('control structures in translatable sections are '
|
||||
'not allowed')
|
||||
elif parser.stream.eos:
|
||||
parser.fail('unclosed translation block')
|
||||
else:
|
||||
assert False, 'internal parser error'
|
||||
|
||||
return referenced, concat(buf)
|
||||
|
||||
def _make_node(self, singular, plural, variables, plural_expr,
|
||||
vars_referenced, num_called_num):
|
||||
"""Generates a useful node from the data provided."""
|
||||
# no variables referenced? no need to escape for old style
|
||||
# gettext invocations only if there are vars.
|
||||
if not vars_referenced and not self.environment.newstyle_gettext:
|
||||
singular = singular.replace('%%', '%')
|
||||
if plural:
|
||||
plural = plural.replace('%%', '%')
|
||||
|
||||
# singular only:
|
||||
if plural_expr is None:
|
||||
gettext = nodes.Name('gettext', 'load')
|
||||
node = nodes.Call(gettext, [nodes.Const(singular)],
|
||||
[], None, None)
|
||||
|
||||
# singular and plural
|
||||
else:
|
||||
ngettext = nodes.Name('ngettext', 'load')
|
||||
node = nodes.Call(ngettext, [
|
||||
nodes.Const(singular),
|
||||
nodes.Const(plural),
|
||||
plural_expr
|
||||
], [], None, None)
|
||||
|
||||
# in case newstyle gettext is used, the method is powerful
|
||||
# enough to handle the variable expansion and autoescape
|
||||
# handling itself
|
||||
if self.environment.newstyle_gettext:
|
||||
for key, value in iteritems(variables):
|
||||
# the function adds that later anyways in case num was
|
||||
# called num, so just skip it.
|
||||
if num_called_num and key == 'num':
|
||||
continue
|
||||
node.kwargs.append(nodes.Keyword(key, value))
|
||||
|
||||
# otherwise do that here
|
||||
else:
|
||||
# mark the return value as safe if we are in an
|
||||
# environment with autoescaping turned on
|
||||
node = nodes.MarkSafeIfAutoescape(node)
|
||||
if variables:
|
||||
node = nodes.Mod(node, nodes.Dict([
|
||||
nodes.Pair(nodes.Const(key), value)
|
||||
for key, value in variables.items()
|
||||
]))
|
||||
return nodes.Output([node])
|
||||
|
||||
|
||||
class ExprStmtExtension(Extension):
|
||||
"""Adds a `do` tag to Jinja2 that works like the print statement just
|
||||
that it doesn't print the return value.
|
||||
"""
|
||||
tags = set(['do'])
|
||||
|
||||
def parse(self, parser):
|
||||
node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
|
||||
node.node = parser.parse_tuple()
|
||||
return node
|
||||
|
||||
|
||||
class LoopControlExtension(Extension):
|
||||
"""Adds break and continue to the template engine."""
|
||||
tags = set(['break', 'continue'])
|
||||
|
||||
def parse(self, parser):
|
||||
token = next(parser.stream)
|
||||
if token.value == 'break':
|
||||
return nodes.Break(lineno=token.lineno)
|
||||
return nodes.Continue(lineno=token.lineno)
|
||||
|
||||
|
||||
class WithExtension(Extension):
|
||||
pass
|
||||
|
||||
|
||||
class AutoEscapeExtension(Extension):
|
||||
pass
|
||||
|
||||
|
||||
def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS,
|
||||
babel_style=True):
|
||||
"""Extract localizable strings from the given template node. Per
|
||||
default this function returns matches in babel style that means non string
|
||||
parameters as well as keyword arguments are returned as `None`. This
|
||||
allows Babel to figure out what you really meant if you are using
|
||||
gettext functions that allow keyword arguments for placeholder expansion.
|
||||
If you don't want that behavior set the `babel_style` parameter to `False`
|
||||
which causes only strings to be returned and parameters are always stored
|
||||
in tuples. As a consequence invalid gettext calls (calls without a single
|
||||
string parameter or string parameters after non-string parameters) are
|
||||
skipped.
|
||||
|
||||
This example explains the behavior:
|
||||
|
||||
>>> from jinja2 import Environment
|
||||
>>> env = Environment()
|
||||
>>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
|
||||
>>> list(extract_from_ast(node))
|
||||
[(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
|
||||
>>> list(extract_from_ast(node, babel_style=False))
|
||||
[(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
|
||||
|
||||
For every string found this function yields a ``(lineno, function,
|
||||
message)`` tuple, where:
|
||||
|
||||
* ``lineno`` is the number of the line on which the string was found,
|
||||
* ``function`` is the name of the ``gettext`` function used (if the
|
||||
string was extracted from embedded Python code), and
|
||||
* ``message`` is the string itself (a ``unicode`` object, or a tuple
|
||||
of ``unicode`` objects for functions with multiple string arguments).
|
||||
|
||||
This extraction function operates on the AST and is because of that unable
|
||||
to extract any comments. For comment support you have to use the babel
|
||||
extraction interface or extract comments yourself.
|
||||
"""
|
||||
for node in node.find_all(nodes.Call):
|
||||
if not isinstance(node.node, nodes.Name) or \
|
||||
node.node.name not in gettext_functions:
|
||||
continue
|
||||
|
||||
strings = []
|
||||
for arg in node.args:
|
||||
if isinstance(arg, nodes.Const) and \
|
||||
isinstance(arg.value, string_types):
|
||||
strings.append(arg.value)
|
||||
else:
|
||||
strings.append(None)
|
||||
|
||||
for arg in node.kwargs:
|
||||
strings.append(None)
|
||||
if node.dyn_args is not None:
|
||||
strings.append(None)
|
||||
if node.dyn_kwargs is not None:
|
||||
strings.append(None)
|
||||
|
||||
if not babel_style:
|
||||
strings = tuple(x for x in strings if x is not None)
|
||||
if not strings:
|
||||
continue
|
||||
else:
|
||||
if len(strings) == 1:
|
||||
strings = strings[0]
|
||||
else:
|
||||
strings = tuple(strings)
|
||||
yield node.lineno, node.node.name, strings
|
||||
|
||||
|
||||
class _CommentFinder(object):
|
||||
"""Helper class to find comments in a token stream. Can only
|
||||
find comments for gettext calls forwards. Once the comment
|
||||
from line 4 is found, a comment for line 1 will not return a
|
||||
usable value.
|
||||
"""
|
||||
|
||||
def __init__(self, tokens, comment_tags):
|
||||
self.tokens = tokens
|
||||
self.comment_tags = comment_tags
|
||||
self.offset = 0
|
||||
self.last_lineno = 0
|
||||
|
||||
def find_backwards(self, offset):
|
||||
try:
|
||||
for _, token_type, token_value in \
|
||||
reversed(self.tokens[self.offset:offset]):
|
||||
if token_type in ('comment', 'linecomment'):
|
||||
try:
|
||||
prefix, comment = token_value.split(None, 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if prefix in self.comment_tags:
|
||||
return [comment.rstrip()]
|
||||
return []
|
||||
finally:
|
||||
self.offset = offset
|
||||
|
||||
def find_comments(self, lineno):
|
||||
if not self.comment_tags or self.last_lineno > lineno:
|
||||
return []
|
||||
for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset:]):
|
||||
if token_lineno > lineno:
|
||||
return self.find_backwards(self.offset + idx)
|
||||
return self.find_backwards(len(self.tokens))
|
||||
|
||||
|
||||
def babel_extract(fileobj, keywords, comment_tags, options):
|
||||
"""Babel extraction method for Jinja templates.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Basic support for translation comments was added. If `comment_tags`
|
||||
is now set to a list of keywords for extraction, the extractor will
|
||||
try to find the best preceeding comment that begins with one of the
|
||||
keywords. For best results, make sure to not have more than one
|
||||
gettext call in one line of code and the matching comment in the
|
||||
same line or the line before.
|
||||
|
||||
.. versionchanged:: 2.5.1
|
||||
The `newstyle_gettext` flag can be set to `True` to enable newstyle
|
||||
gettext calls.
|
||||
|
||||
.. versionchanged:: 2.7
|
||||
A `silent` option can now be provided. If set to `False` template
|
||||
syntax errors are propagated instead of being ignored.
|
||||
|
||||
:param fileobj: the file-like object the messages should be extracted from
|
||||
:param keywords: a list of keywords (i.e. function names) that should be
|
||||
recognized as translation functions
|
||||
:param comment_tags: a list of translator tags to search for and include
|
||||
in the results.
|
||||
:param options: a dictionary of additional options (optional)
|
||||
:return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
|
||||
(comments will be empty currently)
|
||||
"""
|
||||
extensions = set()
|
||||
for extension in options.get('extensions', '').split(','):
|
||||
extension = extension.strip()
|
||||
if not extension:
|
||||
continue
|
||||
extensions.add(import_string(extension))
|
||||
if InternationalizationExtension not in extensions:
|
||||
extensions.add(InternationalizationExtension)
|
||||
|
||||
def getbool(options, key, default=False):
|
||||
return options.get(key, str(default)).lower() in \
|
||||
('1', 'on', 'yes', 'true')
|
||||
|
||||
silent = getbool(options, 'silent', True)
|
||||
environment = Environment(
|
||||
options.get('block_start_string', BLOCK_START_STRING),
|
||||
options.get('block_end_string', BLOCK_END_STRING),
|
||||
options.get('variable_start_string', VARIABLE_START_STRING),
|
||||
options.get('variable_end_string', VARIABLE_END_STRING),
|
||||
options.get('comment_start_string', COMMENT_START_STRING),
|
||||
options.get('comment_end_string', COMMENT_END_STRING),
|
||||
options.get('line_statement_prefix') or LINE_STATEMENT_PREFIX,
|
||||
options.get('line_comment_prefix') or LINE_COMMENT_PREFIX,
|
||||
getbool(options, 'trim_blocks', TRIM_BLOCKS),
|
||||
getbool(options, 'lstrip_blocks', LSTRIP_BLOCKS),
|
||||
NEWLINE_SEQUENCE,
|
||||
getbool(options, 'keep_trailing_newline', KEEP_TRAILING_NEWLINE),
|
||||
frozenset(extensions),
|
||||
cache_size=0,
|
||||
auto_reload=False
|
||||
)
|
||||
|
||||
if getbool(options, 'newstyle_gettext'):
|
||||
environment.newstyle_gettext = True
|
||||
|
||||
source = fileobj.read().decode(options.get('encoding', 'utf-8'))
|
||||
try:
|
||||
node = environment.parse(source)
|
||||
tokens = list(environment.lex(environment.preprocess(source)))
|
||||
except TemplateSyntaxError as e:
|
||||
if not silent:
|
||||
raise
|
||||
# skip templates with syntax errors
|
||||
return
|
||||
|
||||
finder = _CommentFinder(tokens, comment_tags)
|
||||
for lineno, func, message in extract_from_ast(node, keywords):
|
||||
yield lineno, func, message, finder.find_comments(lineno)
|
||||
|
||||
|
||||
#: nicer import names
|
||||
i18n = InternationalizationExtension
|
||||
do = ExprStmtExtension
|
||||
loopcontrols = LoopControlExtension
|
||||
with_ = WithExtension
|
||||
autoescape = AutoEscapeExtension
|
||||
Vendored
-1073
File diff suppressed because it is too large
Load Diff
Vendored
-273
@@ -1,273 +0,0 @@
|
||||
from jinja2.visitor import NodeVisitor
|
||||
from jinja2._compat import iteritems
|
||||
|
||||
|
||||
VAR_LOAD_PARAMETER = 'param'
|
||||
VAR_LOAD_RESOLVE = 'resolve'
|
||||
VAR_LOAD_ALIAS = 'alias'
|
||||
VAR_LOAD_UNDEFINED = 'undefined'
|
||||
|
||||
|
||||
def find_symbols(nodes, parent_symbols=None):
|
||||
sym = Symbols(parent=parent_symbols)
|
||||
visitor = FrameSymbolVisitor(sym)
|
||||
for node in nodes:
|
||||
visitor.visit(node)
|
||||
return sym
|
||||
|
||||
|
||||
def symbols_for_node(node, parent_symbols=None):
|
||||
sym = Symbols(parent=parent_symbols)
|
||||
sym.analyze_node(node)
|
||||
return sym
|
||||
|
||||
|
||||
class Symbols(object):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
if parent is None:
|
||||
self.level = 0
|
||||
else:
|
||||
self.level = parent.level + 1
|
||||
self.parent = parent
|
||||
self.refs = {}
|
||||
self.loads = {}
|
||||
self.stores = set()
|
||||
|
||||
def analyze_node(self, node, **kwargs):
|
||||
visitor = RootVisitor(self)
|
||||
visitor.visit(node, **kwargs)
|
||||
|
||||
def _define_ref(self, name, load=None):
|
||||
ident = 'l_%d_%s' % (self.level, name)
|
||||
self.refs[name] = ident
|
||||
if load is not None:
|
||||
self.loads[ident] = load
|
||||
return ident
|
||||
|
||||
def find_load(self, target):
|
||||
if target in self.loads:
|
||||
return self.loads[target]
|
||||
if self.parent is not None:
|
||||
return self.parent.find_load(target)
|
||||
|
||||
def find_ref(self, name):
|
||||
if name in self.refs:
|
||||
return self.refs[name]
|
||||
if self.parent is not None:
|
||||
return self.parent.find_ref(name)
|
||||
|
||||
def ref(self, name):
|
||||
rv = self.find_ref(name)
|
||||
if rv is None:
|
||||
raise AssertionError('Tried to resolve a name to a reference that '
|
||||
'was unknown to the frame (%r)' % name)
|
||||
return rv
|
||||
|
||||
def copy(self):
|
||||
rv = object.__new__(self.__class__)
|
||||
rv.__dict__.update(self.__dict__)
|
||||
rv.refs = self.refs.copy()
|
||||
rv.loads = self.loads.copy()
|
||||
rv.stores = self.stores.copy()
|
||||
return rv
|
||||
|
||||
def store(self, name):
|
||||
self.stores.add(name)
|
||||
|
||||
# If we have not see the name referenced yet, we need to figure
|
||||
# out what to set it to.
|
||||
if name not in self.refs:
|
||||
# If there is a parent scope we check if the name has a
|
||||
# reference there. If it does it means we might have to alias
|
||||
# to a variable there.
|
||||
if self.parent is not None:
|
||||
outer_ref = self.parent.find_ref(name)
|
||||
if outer_ref is not None:
|
||||
self._define_ref(name, load=(VAR_LOAD_ALIAS, outer_ref))
|
||||
return
|
||||
|
||||
# Otherwise we can just set it to undefined.
|
||||
self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None))
|
||||
|
||||
def declare_parameter(self, name):
|
||||
self.stores.add(name)
|
||||
return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None))
|
||||
|
||||
def load(self, name):
|
||||
target = self.find_ref(name)
|
||||
if target is None:
|
||||
self._define_ref(name, load=(VAR_LOAD_RESOLVE, name))
|
||||
|
||||
def branch_update(self, branch_symbols):
|
||||
stores = {}
|
||||
for branch in branch_symbols:
|
||||
for target in branch.stores:
|
||||
if target in self.stores:
|
||||
continue
|
||||
stores[target] = stores.get(target, 0) + 1
|
||||
|
||||
for sym in branch_symbols:
|
||||
self.refs.update(sym.refs)
|
||||
self.loads.update(sym.loads)
|
||||
self.stores.update(sym.stores)
|
||||
|
||||
for name, branch_count in iteritems(stores):
|
||||
if branch_count == len(branch_symbols):
|
||||
continue
|
||||
target = self.find_ref(name)
|
||||
assert target is not None, 'should not happen'
|
||||
|
||||
if self.parent is not None:
|
||||
outer_target = self.parent.find_ref(name)
|
||||
if outer_target is not None:
|
||||
self.loads[target] = (VAR_LOAD_ALIAS, outer_target)
|
||||
continue
|
||||
self.loads[target] = (VAR_LOAD_RESOLVE, name)
|
||||
|
||||
def dump_stores(self):
|
||||
rv = {}
|
||||
node = self
|
||||
while node is not None:
|
||||
for name in node.stores:
|
||||
if name not in rv:
|
||||
rv[name] = self.find_ref(name)
|
||||
node = node.parent
|
||||
return rv
|
||||
|
||||
def dump_param_targets(self):
|
||||
rv = set()
|
||||
node = self
|
||||
while node is not None:
|
||||
for target, (instr, _) in iteritems(self.loads):
|
||||
if instr == VAR_LOAD_PARAMETER:
|
||||
rv.add(target)
|
||||
node = node.parent
|
||||
return rv
|
||||
|
||||
|
||||
class RootVisitor(NodeVisitor):
|
||||
|
||||
def __init__(self, symbols):
|
||||
self.sym_visitor = FrameSymbolVisitor(symbols)
|
||||
|
||||
def _simple_visit(self, node, **kwargs):
|
||||
for child in node.iter_child_nodes():
|
||||
self.sym_visitor.visit(child)
|
||||
|
||||
visit_Template = visit_Block = visit_Macro = visit_FilterBlock = \
|
||||
visit_Scope = visit_If = visit_ScopedEvalContextModifier = \
|
||||
_simple_visit
|
||||
|
||||
def visit_AssignBlock(self, node, **kwargs):
|
||||
for child in node.body:
|
||||
self.sym_visitor.visit(child)
|
||||
|
||||
def visit_CallBlock(self, node, **kwargs):
|
||||
for child in node.iter_child_nodes(exclude=('call',)):
|
||||
self.sym_visitor.visit(child)
|
||||
|
||||
def visit_For(self, node, for_branch='body', **kwargs):
|
||||
if for_branch == 'body':
|
||||
self.sym_visitor.visit(node.target, store_as_param=True)
|
||||
branch = node.body
|
||||
elif for_branch == 'else':
|
||||
branch = node.else_
|
||||
elif for_branch == 'test':
|
||||
self.sym_visitor.visit(node.target, store_as_param=True)
|
||||
if node.test is not None:
|
||||
self.sym_visitor.visit(node.test)
|
||||
return
|
||||
else:
|
||||
raise RuntimeError('Unknown for branch')
|
||||
for item in branch or ():
|
||||
self.sym_visitor.visit(item)
|
||||
|
||||
def visit_With(self, node, **kwargs):
|
||||
for target in node.targets:
|
||||
self.sym_visitor.visit(target)
|
||||
for child in node.body:
|
||||
self.sym_visitor.visit(child)
|
||||
|
||||
def generic_visit(self, node, *args, **kwargs):
|
||||
raise NotImplementedError('Cannot find symbols for %r' %
|
||||
node.__class__.__name__)
|
||||
|
||||
|
||||
class FrameSymbolVisitor(NodeVisitor):
|
||||
"""A visitor for `Frame.inspect`."""
|
||||
|
||||
def __init__(self, symbols):
|
||||
self.symbols = symbols
|
||||
|
||||
def visit_Name(self, node, store_as_param=False, **kwargs):
|
||||
"""All assignments to names go through this function."""
|
||||
if store_as_param or node.ctx == 'param':
|
||||
self.symbols.declare_parameter(node.name)
|
||||
elif node.ctx == 'store':
|
||||
self.symbols.store(node.name)
|
||||
elif node.ctx == 'load':
|
||||
self.symbols.load(node.name)
|
||||
|
||||
def visit_If(self, node, **kwargs):
|
||||
self.visit(node.test, **kwargs)
|
||||
|
||||
original_symbols = self.symbols
|
||||
|
||||
def inner_visit(nodes):
|
||||
self.symbols = rv = original_symbols.copy()
|
||||
for subnode in nodes:
|
||||
self.visit(subnode, **kwargs)
|
||||
self.symbols = original_symbols
|
||||
return rv
|
||||
|
||||
body_symbols = inner_visit(node.body)
|
||||
else_symbols = inner_visit(node.else_ or ())
|
||||
|
||||
self.symbols.branch_update([body_symbols, else_symbols])
|
||||
|
||||
def visit_Macro(self, node, **kwargs):
|
||||
self.symbols.store(node.name)
|
||||
|
||||
def visit_Import(self, node, **kwargs):
|
||||
self.generic_visit(node, **kwargs)
|
||||
self.symbols.store(node.target)
|
||||
|
||||
def visit_FromImport(self, node, **kwargs):
|
||||
self.generic_visit(node, **kwargs)
|
||||
for name in node.names:
|
||||
if isinstance(name, tuple):
|
||||
self.symbols.store(name[1])
|
||||
else:
|
||||
self.symbols.store(name)
|
||||
|
||||
def visit_Assign(self, node, **kwargs):
|
||||
"""Visit assignments in the correct order."""
|
||||
self.visit(node.node, **kwargs)
|
||||
self.visit(node.target, **kwargs)
|
||||
|
||||
def visit_For(self, node, **kwargs):
|
||||
"""Visiting stops at for blocks. However the block sequence
|
||||
is visited as part of the outer scope.
|
||||
"""
|
||||
self.visit(node.iter, **kwargs)
|
||||
|
||||
def visit_CallBlock(self, node, **kwargs):
|
||||
self.visit(node.call, **kwargs)
|
||||
|
||||
def visit_FilterBlock(self, node, **kwargs):
|
||||
self.visit(node.filter, **kwargs)
|
||||
|
||||
def visit_With(self, node, **kwargs):
|
||||
for target in node.values:
|
||||
self.visit(target)
|
||||
|
||||
def visit_AssignBlock(self, node, **kwargs):
|
||||
"""Stop visiting at block assigns."""
|
||||
self.visit(node.target, **kwargs)
|
||||
|
||||
def visit_Scope(self, node, **kwargs):
|
||||
"""Stop visiting at scopes."""
|
||||
|
||||
def visit_Block(self, node, **kwargs):
|
||||
"""Stop visiting at blocks."""
|
||||
Vendored
-737
@@ -1,737 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.lexer
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This module implements a Jinja / Python combination lexer. The
|
||||
`Lexer` class provided by this module is used to do some preprocessing
|
||||
for Jinja.
|
||||
|
||||
On the one hand it filters out invalid operators like the bitshift
|
||||
operators we don't allow in templates. On the other hand it separates
|
||||
template code and python code in expressions.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
|
||||
from operator import itemgetter
|
||||
from collections import deque
|
||||
from jinja2.exceptions import TemplateSyntaxError
|
||||
from jinja2.utils import LRUCache
|
||||
from jinja2._compat import iteritems, implements_iterator, text_type, intern
|
||||
|
||||
|
||||
# cache for the lexers. Exists in order to be able to have multiple
|
||||
# environments with the same lexer
|
||||
_lexer_cache = LRUCache(50)
|
||||
|
||||
# static regular expressions
|
||||
whitespace_re = re.compile(r'\s+', re.U)
|
||||
string_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
|
||||
r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
|
||||
integer_re = re.compile(r'\d+')
|
||||
|
||||
def _make_name_re():
|
||||
try:
|
||||
compile('föö', '<unknown>', 'eval')
|
||||
except SyntaxError:
|
||||
return re.compile(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b')
|
||||
|
||||
import jinja2
|
||||
from jinja2 import _stringdefs
|
||||
name_re = re.compile(r'[%s][%s]*' % (_stringdefs.xid_start,
|
||||
_stringdefs.xid_continue))
|
||||
|
||||
# Save some memory here
|
||||
sys.modules.pop('jinja2._stringdefs')
|
||||
del _stringdefs
|
||||
del jinja2._stringdefs
|
||||
|
||||
return name_re
|
||||
|
||||
# we use the unicode identifier rule if this python version is able
|
||||
# to handle unicode identifiers, otherwise the standard ASCII one.
|
||||
name_re = _make_name_re()
|
||||
del _make_name_re
|
||||
|
||||
float_re = re.compile(r'(?<!\.)\d+\.\d+')
|
||||
newline_re = re.compile(r'(\r\n|\r|\n)')
|
||||
|
||||
# internal the tokens and keep references to them
|
||||
TOKEN_ADD = intern('add')
|
||||
TOKEN_ASSIGN = intern('assign')
|
||||
TOKEN_COLON = intern('colon')
|
||||
TOKEN_COMMA = intern('comma')
|
||||
TOKEN_DIV = intern('div')
|
||||
TOKEN_DOT = intern('dot')
|
||||
TOKEN_EQ = intern('eq')
|
||||
TOKEN_FLOORDIV = intern('floordiv')
|
||||
TOKEN_GT = intern('gt')
|
||||
TOKEN_GTEQ = intern('gteq')
|
||||
TOKEN_LBRACE = intern('lbrace')
|
||||
TOKEN_LBRACKET = intern('lbracket')
|
||||
TOKEN_LPAREN = intern('lparen')
|
||||
TOKEN_LT = intern('lt')
|
||||
TOKEN_LTEQ = intern('lteq')
|
||||
TOKEN_MOD = intern('mod')
|
||||
TOKEN_MUL = intern('mul')
|
||||
TOKEN_NE = intern('ne')
|
||||
TOKEN_PIPE = intern('pipe')
|
||||
TOKEN_POW = intern('pow')
|
||||
TOKEN_RBRACE = intern('rbrace')
|
||||
TOKEN_RBRACKET = intern('rbracket')
|
||||
TOKEN_RPAREN = intern('rparen')
|
||||
TOKEN_SEMICOLON = intern('semicolon')
|
||||
TOKEN_SUB = intern('sub')
|
||||
TOKEN_TILDE = intern('tilde')
|
||||
TOKEN_WHITESPACE = intern('whitespace')
|
||||
TOKEN_FLOAT = intern('float')
|
||||
TOKEN_INTEGER = intern('integer')
|
||||
TOKEN_NAME = intern('name')
|
||||
TOKEN_STRING = intern('string')
|
||||
TOKEN_OPERATOR = intern('operator')
|
||||
TOKEN_BLOCK_BEGIN = intern('block_begin')
|
||||
TOKEN_BLOCK_END = intern('block_end')
|
||||
TOKEN_VARIABLE_BEGIN = intern('variable_begin')
|
||||
TOKEN_VARIABLE_END = intern('variable_end')
|
||||
TOKEN_RAW_BEGIN = intern('raw_begin')
|
||||
TOKEN_RAW_END = intern('raw_end')
|
||||
TOKEN_COMMENT_BEGIN = intern('comment_begin')
|
||||
TOKEN_COMMENT_END = intern('comment_end')
|
||||
TOKEN_COMMENT = intern('comment')
|
||||
TOKEN_LINESTATEMENT_BEGIN = intern('linestatement_begin')
|
||||
TOKEN_LINESTATEMENT_END = intern('linestatement_end')
|
||||
TOKEN_LINECOMMENT_BEGIN = intern('linecomment_begin')
|
||||
TOKEN_LINECOMMENT_END = intern('linecomment_end')
|
||||
TOKEN_LINECOMMENT = intern('linecomment')
|
||||
TOKEN_DATA = intern('data')
|
||||
TOKEN_INITIAL = intern('initial')
|
||||
TOKEN_EOF = intern('eof')
|
||||
|
||||
# bind operators to token types
|
||||
operators = {
|
||||
'+': TOKEN_ADD,
|
||||
'-': TOKEN_SUB,
|
||||
'/': TOKEN_DIV,
|
||||
'//': TOKEN_FLOORDIV,
|
||||
'*': TOKEN_MUL,
|
||||
'%': TOKEN_MOD,
|
||||
'**': TOKEN_POW,
|
||||
'~': TOKEN_TILDE,
|
||||
'[': TOKEN_LBRACKET,
|
||||
']': TOKEN_RBRACKET,
|
||||
'(': TOKEN_LPAREN,
|
||||
')': TOKEN_RPAREN,
|
||||
'{': TOKEN_LBRACE,
|
||||
'}': TOKEN_RBRACE,
|
||||
'==': TOKEN_EQ,
|
||||
'!=': TOKEN_NE,
|
||||
'>': TOKEN_GT,
|
||||
'>=': TOKEN_GTEQ,
|
||||
'<': TOKEN_LT,
|
||||
'<=': TOKEN_LTEQ,
|
||||
'=': TOKEN_ASSIGN,
|
||||
'.': TOKEN_DOT,
|
||||
':': TOKEN_COLON,
|
||||
'|': TOKEN_PIPE,
|
||||
',': TOKEN_COMMA,
|
||||
';': TOKEN_SEMICOLON
|
||||
}
|
||||
|
||||
reverse_operators = dict([(v, k) for k, v in iteritems(operators)])
|
||||
assert len(operators) == len(reverse_operators), 'operators dropped'
|
||||
operator_re = re.compile('(%s)' % '|'.join(re.escape(x) for x in
|
||||
sorted(operators, key=lambda x: -len(x))))
|
||||
|
||||
ignored_tokens = frozenset([TOKEN_COMMENT_BEGIN, TOKEN_COMMENT,
|
||||
TOKEN_COMMENT_END, TOKEN_WHITESPACE,
|
||||
TOKEN_LINECOMMENT_BEGIN, TOKEN_LINECOMMENT_END,
|
||||
TOKEN_LINECOMMENT])
|
||||
ignore_if_empty = frozenset([TOKEN_WHITESPACE, TOKEN_DATA,
|
||||
TOKEN_COMMENT, TOKEN_LINECOMMENT])
|
||||
|
||||
|
||||
def _describe_token_type(token_type):
|
||||
if token_type in reverse_operators:
|
||||
return reverse_operators[token_type]
|
||||
return {
|
||||
TOKEN_COMMENT_BEGIN: 'begin of comment',
|
||||
TOKEN_COMMENT_END: 'end of comment',
|
||||
TOKEN_COMMENT: 'comment',
|
||||
TOKEN_LINECOMMENT: 'comment',
|
||||
TOKEN_BLOCK_BEGIN: 'begin of statement block',
|
||||
TOKEN_BLOCK_END: 'end of statement block',
|
||||
TOKEN_VARIABLE_BEGIN: 'begin of print statement',
|
||||
TOKEN_VARIABLE_END: 'end of print statement',
|
||||
TOKEN_LINESTATEMENT_BEGIN: 'begin of line statement',
|
||||
TOKEN_LINESTATEMENT_END: 'end of line statement',
|
||||
TOKEN_DATA: 'template data / text',
|
||||
TOKEN_EOF: 'end of template'
|
||||
}.get(token_type, token_type)
|
||||
|
||||
|
||||
def describe_token(token):
|
||||
"""Returns a description of the token."""
|
||||
if token.type == 'name':
|
||||
return token.value
|
||||
return _describe_token_type(token.type)
|
||||
|
||||
|
||||
def describe_token_expr(expr):
|
||||
"""Like `describe_token` but for token expressions."""
|
||||
if ':' in expr:
|
||||
type, value = expr.split(':', 1)
|
||||
if type == 'name':
|
||||
return value
|
||||
else:
|
||||
type = expr
|
||||
return _describe_token_type(type)
|
||||
|
||||
|
||||
def count_newlines(value):
|
||||
"""Count the number of newline characters in the string. This is
|
||||
useful for extensions that filter a stream.
|
||||
"""
|
||||
return len(newline_re.findall(value))
|
||||
|
||||
|
||||
def compile_rules(environment):
|
||||
"""Compiles all the rules from the environment into a list of rules."""
|
||||
e = re.escape
|
||||
rules = [
|
||||
(len(environment.comment_start_string), 'comment',
|
||||
e(environment.comment_start_string)),
|
||||
(len(environment.block_start_string), 'block',
|
||||
e(environment.block_start_string)),
|
||||
(len(environment.variable_start_string), 'variable',
|
||||
e(environment.variable_start_string))
|
||||
]
|
||||
|
||||
if environment.line_statement_prefix is not None:
|
||||
rules.append((len(environment.line_statement_prefix), 'linestatement',
|
||||
r'^[ \t\v]*' + e(environment.line_statement_prefix)))
|
||||
if environment.line_comment_prefix is not None:
|
||||
rules.append((len(environment.line_comment_prefix), 'linecomment',
|
||||
r'(?:^|(?<=\S))[^\S\r\n]*' +
|
||||
e(environment.line_comment_prefix)))
|
||||
|
||||
return [x[1:] for x in sorted(rules, reverse=True)]
|
||||
|
||||
|
||||
class Failure(object):
|
||||
"""Class that raises a `TemplateSyntaxError` if called.
|
||||
Used by the `Lexer` to specify known errors.
|
||||
"""
|
||||
|
||||
def __init__(self, message, cls=TemplateSyntaxError):
|
||||
self.message = message
|
||||
self.error_class = cls
|
||||
|
||||
def __call__(self, lineno, filename):
|
||||
raise self.error_class(self.message, lineno, filename)
|
||||
|
||||
|
||||
class Token(tuple):
|
||||
"""Token class."""
|
||||
__slots__ = ()
|
||||
lineno, type, value = (property(itemgetter(x)) for x in range(3))
|
||||
|
||||
def __new__(cls, lineno, type, value):
|
||||
return tuple.__new__(cls, (lineno, intern(str(type)), value))
|
||||
|
||||
def __str__(self):
|
||||
if self.type in reverse_operators:
|
||||
return reverse_operators[self.type]
|
||||
elif self.type == 'name':
|
||||
return self.value
|
||||
return self.type
|
||||
|
||||
def test(self, expr):
|
||||
"""Test a token against a token expression. This can either be a
|
||||
token type or ``'token_type:token_value'``. This can only test
|
||||
against string values and types.
|
||||
"""
|
||||
# here we do a regular string equality check as test_any is usually
|
||||
# passed an iterable of not interned strings.
|
||||
if self.type == expr:
|
||||
return True
|
||||
elif ':' in expr:
|
||||
return expr.split(':', 1) == [self.type, self.value]
|
||||
return False
|
||||
|
||||
def test_any(self, *iterable):
|
||||
"""Test against multiple token expressions."""
|
||||
for expr in iterable:
|
||||
if self.test(expr):
|
||||
return True
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return 'Token(%r, %r, %r)' % (
|
||||
self.lineno,
|
||||
self.type,
|
||||
self.value
|
||||
)
|
||||
|
||||
|
||||
@implements_iterator
|
||||
class TokenStreamIterator(object):
|
||||
"""The iterator for tokenstreams. Iterate over the stream
|
||||
until the eof token is reached.
|
||||
"""
|
||||
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
token = self.stream.current
|
||||
if token.type is TOKEN_EOF:
|
||||
self.stream.close()
|
||||
raise StopIteration()
|
||||
next(self.stream)
|
||||
return token
|
||||
|
||||
|
||||
@implements_iterator
|
||||
class TokenStream(object):
|
||||
"""A token stream is an iterable that yields :class:`Token`\\s. The
|
||||
parser however does not iterate over it but calls :meth:`next` to go
|
||||
one token ahead. The current active token is stored as :attr:`current`.
|
||||
"""
|
||||
|
||||
def __init__(self, generator, name, filename):
|
||||
self._iter = iter(generator)
|
||||
self._pushed = deque()
|
||||
self.name = name
|
||||
self.filename = filename
|
||||
self.closed = False
|
||||
self.current = Token(1, TOKEN_INITIAL, '')
|
||||
next(self)
|
||||
|
||||
def __iter__(self):
|
||||
return TokenStreamIterator(self)
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self._pushed) or self.current.type is not TOKEN_EOF
|
||||
__nonzero__ = __bool__ # py2
|
||||
|
||||
eos = property(lambda x: not x, doc="Are we at the end of the stream?")
|
||||
|
||||
def push(self, token):
|
||||
"""Push a token back to the stream."""
|
||||
self._pushed.append(token)
|
||||
|
||||
def look(self):
|
||||
"""Look at the next token."""
|
||||
old_token = next(self)
|
||||
result = self.current
|
||||
self.push(result)
|
||||
self.current = old_token
|
||||
return result
|
||||
|
||||
def skip(self, n=1):
|
||||
"""Got n tokens ahead."""
|
||||
for x in range(n):
|
||||
next(self)
|
||||
|
||||
def next_if(self, expr):
|
||||
"""Perform the token test and return the token if it matched.
|
||||
Otherwise the return value is `None`.
|
||||
"""
|
||||
if self.current.test(expr):
|
||||
return next(self)
|
||||
|
||||
def skip_if(self, expr):
|
||||
"""Like :meth:`next_if` but only returns `True` or `False`."""
|
||||
return self.next_if(expr) is not None
|
||||
|
||||
def __next__(self):
|
||||
"""Go one token ahead and return the old one"""
|
||||
rv = self.current
|
||||
if self._pushed:
|
||||
self.current = self._pushed.popleft()
|
||||
elif self.current.type is not TOKEN_EOF:
|
||||
try:
|
||||
self.current = next(self._iter)
|
||||
except StopIteration:
|
||||
self.close()
|
||||
return rv
|
||||
|
||||
def close(self):
|
||||
"""Close the stream."""
|
||||
self.current = Token(self.current.lineno, TOKEN_EOF, '')
|
||||
self._iter = None
|
||||
self.closed = True
|
||||
|
||||
def expect(self, expr):
|
||||
"""Expect a given token type and return it. This accepts the same
|
||||
argument as :meth:`jinja2.lexer.Token.test`.
|
||||
"""
|
||||
if not self.current.test(expr):
|
||||
expr = describe_token_expr(expr)
|
||||
if self.current.type is TOKEN_EOF:
|
||||
raise TemplateSyntaxError('unexpected end of template, '
|
||||
'expected %r.' % expr,
|
||||
self.current.lineno,
|
||||
self.name, self.filename)
|
||||
raise TemplateSyntaxError("expected token %r, got %r" %
|
||||
(expr, describe_token(self.current)),
|
||||
self.current.lineno,
|
||||
self.name, self.filename)
|
||||
try:
|
||||
return self.current
|
||||
finally:
|
||||
next(self)
|
||||
|
||||
|
||||
def get_lexer(environment):
|
||||
"""Return a lexer which is probably cached."""
|
||||
key = (environment.block_start_string,
|
||||
environment.block_end_string,
|
||||
environment.variable_start_string,
|
||||
environment.variable_end_string,
|
||||
environment.comment_start_string,
|
||||
environment.comment_end_string,
|
||||
environment.line_statement_prefix,
|
||||
environment.line_comment_prefix,
|
||||
environment.trim_blocks,
|
||||
environment.lstrip_blocks,
|
||||
environment.newline_sequence,
|
||||
environment.keep_trailing_newline)
|
||||
lexer = _lexer_cache.get(key)
|
||||
if lexer is None:
|
||||
lexer = Lexer(environment)
|
||||
_lexer_cache[key] = lexer
|
||||
return lexer
|
||||
|
||||
|
||||
class Lexer(object):
|
||||
"""Class that implements a lexer for a given environment. Automatically
|
||||
created by the environment class, usually you don't have to do that.
|
||||
|
||||
Note that the lexer is not automatically bound to an environment.
|
||||
Multiple environments can share the same lexer.
|
||||
"""
|
||||
|
||||
def __init__(self, environment):
|
||||
# shortcuts
|
||||
c = lambda x: re.compile(x, re.M | re.S)
|
||||
e = re.escape
|
||||
|
||||
# lexing rules for tags
|
||||
tag_rules = [
|
||||
(whitespace_re, TOKEN_WHITESPACE, None),
|
||||
(float_re, TOKEN_FLOAT, None),
|
||||
(integer_re, TOKEN_INTEGER, None),
|
||||
(name_re, TOKEN_NAME, None),
|
||||
(string_re, TOKEN_STRING, None),
|
||||
(operator_re, TOKEN_OPERATOR, None)
|
||||
]
|
||||
|
||||
# assemble the root lexing rule. because "|" is ungreedy
|
||||
# we have to sort by length so that the lexer continues working
|
||||
# as expected when we have parsing rules like <% for block and
|
||||
# <%= for variables. (if someone wants asp like syntax)
|
||||
# variables are just part of the rules if variable processing
|
||||
# is required.
|
||||
root_tag_rules = compile_rules(environment)
|
||||
|
||||
# block suffix if trimming is enabled
|
||||
block_suffix_re = environment.trim_blocks and '\\n?' or ''
|
||||
|
||||
# strip leading spaces if lstrip_blocks is enabled
|
||||
prefix_re = {}
|
||||
if environment.lstrip_blocks:
|
||||
# use '{%+' to manually disable lstrip_blocks behavior
|
||||
no_lstrip_re = e('+')
|
||||
# detect overlap between block and variable or comment strings
|
||||
block_diff = c(r'^%s(.*)' % e(environment.block_start_string))
|
||||
# make sure we don't mistake a block for a variable or a comment
|
||||
m = block_diff.match(environment.comment_start_string)
|
||||
no_lstrip_re += m and r'|%s' % e(m.group(1)) or ''
|
||||
m = block_diff.match(environment.variable_start_string)
|
||||
no_lstrip_re += m and r'|%s' % e(m.group(1)) or ''
|
||||
|
||||
# detect overlap between comment and variable strings
|
||||
comment_diff = c(r'^%s(.*)' % e(environment.comment_start_string))
|
||||
m = comment_diff.match(environment.variable_start_string)
|
||||
no_variable_re = m and r'(?!%s)' % e(m.group(1)) or ''
|
||||
|
||||
lstrip_re = r'^[ \t]*'
|
||||
block_prefix_re = r'%s%s(?!%s)|%s\+?' % (
|
||||
lstrip_re,
|
||||
e(environment.block_start_string),
|
||||
no_lstrip_re,
|
||||
e(environment.block_start_string),
|
||||
)
|
||||
comment_prefix_re = r'%s%s%s|%s\+?' % (
|
||||
lstrip_re,
|
||||
e(environment.comment_start_string),
|
||||
no_variable_re,
|
||||
e(environment.comment_start_string),
|
||||
)
|
||||
prefix_re['block'] = block_prefix_re
|
||||
prefix_re['comment'] = comment_prefix_re
|
||||
else:
|
||||
block_prefix_re = '%s' % e(environment.block_start_string)
|
||||
|
||||
self.newline_sequence = environment.newline_sequence
|
||||
self.keep_trailing_newline = environment.keep_trailing_newline
|
||||
|
||||
# global lexing rules
|
||||
self.rules = {
|
||||
'root': [
|
||||
# directives
|
||||
(c('(.*?)(?:%s)' % '|'.join(
|
||||
[r'(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*(?:\-%s\s*|%s))' % (
|
||||
e(environment.block_start_string),
|
||||
block_prefix_re,
|
||||
e(environment.block_end_string),
|
||||
e(environment.block_end_string)
|
||||
)] + [
|
||||
r'(?P<%s_begin>\s*%s\-|%s)' % (n, r, prefix_re.get(n,r))
|
||||
for n, r in root_tag_rules
|
||||
])), (TOKEN_DATA, '#bygroup'), '#bygroup'),
|
||||
# data
|
||||
(c('.+'), TOKEN_DATA, None)
|
||||
],
|
||||
# comments
|
||||
TOKEN_COMMENT_BEGIN: [
|
||||
(c(r'(.*?)((?:\-%s\s*|%s)%s)' % (
|
||||
e(environment.comment_end_string),
|
||||
e(environment.comment_end_string),
|
||||
block_suffix_re
|
||||
)), (TOKEN_COMMENT, TOKEN_COMMENT_END), '#pop'),
|
||||
(c('(.)'), (Failure('Missing end of comment tag'),), None)
|
||||
],
|
||||
# blocks
|
||||
TOKEN_BLOCK_BEGIN: [
|
||||
(c(r'(?:\-%s\s*|%s)%s' % (
|
||||
e(environment.block_end_string),
|
||||
e(environment.block_end_string),
|
||||
block_suffix_re
|
||||
)), TOKEN_BLOCK_END, '#pop'),
|
||||
] + tag_rules,
|
||||
# variables
|
||||
TOKEN_VARIABLE_BEGIN: [
|
||||
(c(r'\-%s\s*|%s' % (
|
||||
e(environment.variable_end_string),
|
||||
e(environment.variable_end_string)
|
||||
)), TOKEN_VARIABLE_END, '#pop')
|
||||
] + tag_rules,
|
||||
# raw block
|
||||
TOKEN_RAW_BEGIN: [
|
||||
(c(r'(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % (
|
||||
e(environment.block_start_string),
|
||||
block_prefix_re,
|
||||
e(environment.block_end_string),
|
||||
e(environment.block_end_string),
|
||||
block_suffix_re
|
||||
)), (TOKEN_DATA, TOKEN_RAW_END), '#pop'),
|
||||
(c('(.)'), (Failure('Missing end of raw directive'),), None)
|
||||
],
|
||||
# line statements
|
||||
TOKEN_LINESTATEMENT_BEGIN: [
|
||||
(c(r'\s*(\n|$)'), TOKEN_LINESTATEMENT_END, '#pop')
|
||||
] + tag_rules,
|
||||
# line comments
|
||||
TOKEN_LINECOMMENT_BEGIN: [
|
||||
(c(r'(.*?)()(?=\n|$)'), (TOKEN_LINECOMMENT,
|
||||
TOKEN_LINECOMMENT_END), '#pop')
|
||||
]
|
||||
}
|
||||
|
||||
def _normalize_newlines(self, value):
|
||||
"""Called for strings and template data to normalize it to unicode."""
|
||||
return newline_re.sub(self.newline_sequence, value)
|
||||
|
||||
def tokenize(self, source, name=None, filename=None, state=None):
|
||||
"""Calls tokeniter + tokenize and wraps it in a token stream.
|
||||
"""
|
||||
stream = self.tokeniter(source, name, filename, state)
|
||||
return TokenStream(self.wrap(stream, name, filename), name, filename)
|
||||
|
||||
def wrap(self, stream, name=None, filename=None):
|
||||
"""This is called with the stream as returned by `tokenize` and wraps
|
||||
every token in a :class:`Token` and converts the value.
|
||||
"""
|
||||
for lineno, token, value in stream:
|
||||
if token in ignored_tokens:
|
||||
continue
|
||||
elif token == 'linestatement_begin':
|
||||
token = 'block_begin'
|
||||
elif token == 'linestatement_end':
|
||||
token = 'block_end'
|
||||
# we are not interested in those tokens in the parser
|
||||
elif token in ('raw_begin', 'raw_end'):
|
||||
continue
|
||||
elif token == 'data':
|
||||
value = self._normalize_newlines(value)
|
||||
elif token == 'keyword':
|
||||
token = value
|
||||
elif token == 'name':
|
||||
value = str(value)
|
||||
elif token == 'string':
|
||||
# try to unescape string
|
||||
try:
|
||||
value = self._normalize_newlines(value[1:-1]) \
|
||||
.encode('ascii', 'backslashreplace') \
|
||||
.decode('unicode-escape')
|
||||
except Exception as e:
|
||||
msg = str(e).split(':')[-1].strip()
|
||||
raise TemplateSyntaxError(msg, lineno, name, filename)
|
||||
elif token == 'integer':
|
||||
value = int(value)
|
||||
elif token == 'float':
|
||||
value = float(value)
|
||||
elif token == 'operator':
|
||||
token = operators[value]
|
||||
yield Token(lineno, token, value)
|
||||
|
||||
def tokeniter(self, source, name, filename=None, state=None):
|
||||
"""This method tokenizes the text and returns the tokens in a
|
||||
generator. Use this method if you just want to tokenize a template.
|
||||
"""
|
||||
source = text_type(source)
|
||||
lines = source.splitlines()
|
||||
if self.keep_trailing_newline and source:
|
||||
for newline in ('\r\n', '\r', '\n'):
|
||||
if source.endswith(newline):
|
||||
lines.append('')
|
||||
break
|
||||
source = '\n'.join(lines)
|
||||
pos = 0
|
||||
lineno = 1
|
||||
stack = ['root']
|
||||
if state is not None and state != 'root':
|
||||
assert state in ('variable', 'block'), 'invalid state'
|
||||
stack.append(state + '_begin')
|
||||
else:
|
||||
state = 'root'
|
||||
statetokens = self.rules[stack[-1]]
|
||||
source_length = len(source)
|
||||
|
||||
balancing_stack = []
|
||||
|
||||
while 1:
|
||||
# tokenizer loop
|
||||
for regex, tokens, new_state in statetokens:
|
||||
m = regex.match(source, pos)
|
||||
# if no match we try again with the next rule
|
||||
if m is None:
|
||||
continue
|
||||
|
||||
# we only match blocks and variables if braces / parentheses
|
||||
# are balanced. continue parsing with the lower rule which
|
||||
# is the operator rule. do this only if the end tags look
|
||||
# like operators
|
||||
if balancing_stack and \
|
||||
tokens in ('variable_end', 'block_end',
|
||||
'linestatement_end'):
|
||||
continue
|
||||
|
||||
# tuples support more options
|
||||
if isinstance(tokens, tuple):
|
||||
for idx, token in enumerate(tokens):
|
||||
# failure group
|
||||
if token.__class__ is Failure:
|
||||
raise token(lineno, filename)
|
||||
# bygroup is a bit more complex, in that case we
|
||||
# yield for the current token the first named
|
||||
# group that matched
|
||||
elif token == '#bygroup':
|
||||
for key, value in iteritems(m.groupdict()):
|
||||
if value is not None:
|
||||
yield lineno, key, value
|
||||
lineno += value.count('\n')
|
||||
break
|
||||
else:
|
||||
raise RuntimeError('%r wanted to resolve '
|
||||
'the token dynamically'
|
||||
' but no group matched'
|
||||
% regex)
|
||||
# normal group
|
||||
else:
|
||||
data = m.group(idx + 1)
|
||||
if data or token not in ignore_if_empty:
|
||||
yield lineno, token, data
|
||||
lineno += data.count('\n')
|
||||
|
||||
# strings as token just are yielded as it.
|
||||
else:
|
||||
data = m.group()
|
||||
# update brace/parentheses balance
|
||||
if tokens == 'operator':
|
||||
if data == '{':
|
||||
balancing_stack.append('}')
|
||||
elif data == '(':
|
||||
balancing_stack.append(')')
|
||||
elif data == '[':
|
||||
balancing_stack.append(']')
|
||||
elif data in ('}', ')', ']'):
|
||||
if not balancing_stack:
|
||||
raise TemplateSyntaxError('unexpected \'%s\'' %
|
||||
data, lineno, name,
|
||||
filename)
|
||||
expected_op = balancing_stack.pop()
|
||||
if expected_op != data:
|
||||
raise TemplateSyntaxError('unexpected \'%s\', '
|
||||
'expected \'%s\'' %
|
||||
(data, expected_op),
|
||||
lineno, name,
|
||||
filename)
|
||||
# yield items
|
||||
if data or tokens not in ignore_if_empty:
|
||||
yield lineno, tokens, data
|
||||
lineno += data.count('\n')
|
||||
|
||||
# fetch new position into new variable so that we can check
|
||||
# if there is a internal parsing error which would result
|
||||
# in an infinite loop
|
||||
pos2 = m.end()
|
||||
|
||||
# handle state changes
|
||||
if new_state is not None:
|
||||
# remove the uppermost state
|
||||
if new_state == '#pop':
|
||||
stack.pop()
|
||||
# resolve the new state by group checking
|
||||
elif new_state == '#bygroup':
|
||||
for key, value in iteritems(m.groupdict()):
|
||||
if value is not None:
|
||||
stack.append(key)
|
||||
break
|
||||
else:
|
||||
raise RuntimeError('%r wanted to resolve the '
|
||||
'new state dynamically but'
|
||||
' no group matched' %
|
||||
regex)
|
||||
# direct state name given
|
||||
else:
|
||||
stack.append(new_state)
|
||||
statetokens = self.rules[stack[-1]]
|
||||
# we are still at the same position and no stack change.
|
||||
# this means a loop without break condition, avoid that and
|
||||
# raise error
|
||||
elif pos2 == pos:
|
||||
raise RuntimeError('%r yielded empty string without '
|
||||
'stack change' % regex)
|
||||
# publish new function and start again
|
||||
pos = pos2
|
||||
break
|
||||
# if loop terminated without break we haven't found a single match
|
||||
# either we are at the end of the file or we have a problem
|
||||
else:
|
||||
# end of text
|
||||
if pos >= source_length:
|
||||
return
|
||||
# something went wrong
|
||||
raise TemplateSyntaxError('unexpected char %r at %d' %
|
||||
(source[pos], pos), lineno,
|
||||
name, filename)
|
||||
Vendored
-481
@@ -1,481 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.loaders
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Jinja loader classes.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import weakref
|
||||
from types import ModuleType
|
||||
from os import path
|
||||
from hashlib import sha1
|
||||
from jinja2.exceptions import TemplateNotFound
|
||||
from jinja2.utils import open_if_exists, internalcode
|
||||
from jinja2._compat import string_types, iteritems
|
||||
|
||||
|
||||
def split_template_path(template):
|
||||
"""Split a path into segments and perform a sanity check. If it detects
|
||||
'..' in the path it will raise a `TemplateNotFound` error.
|
||||
"""
|
||||
pieces = []
|
||||
for piece in template.split('/'):
|
||||
if path.sep in piece \
|
||||
or (path.altsep and path.altsep in piece) or \
|
||||
piece == path.pardir:
|
||||
raise TemplateNotFound(template)
|
||||
elif piece and piece != '.':
|
||||
pieces.append(piece)
|
||||
return pieces
|
||||
|
||||
|
||||
class BaseLoader(object):
|
||||
"""Baseclass for all loaders. Subclass this and override `get_source` to
|
||||
implement a custom loading mechanism. The environment provides a
|
||||
`get_template` method that calls the loader's `load` method to get the
|
||||
:class:`Template` object.
|
||||
|
||||
A very basic example for a loader that looks up templates on the file
|
||||
system could look like this::
|
||||
|
||||
from jinja2 import BaseLoader, TemplateNotFound
|
||||
from os.path import join, exists, getmtime
|
||||
|
||||
class MyLoader(BaseLoader):
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
def get_source(self, environment, template):
|
||||
path = join(self.path, template)
|
||||
if not exists(path):
|
||||
raise TemplateNotFound(template)
|
||||
mtime = getmtime(path)
|
||||
with file(path) as f:
|
||||
source = f.read().decode('utf-8')
|
||||
return source, path, lambda: mtime == getmtime(path)
|
||||
"""
|
||||
|
||||
#: if set to `False` it indicates that the loader cannot provide access
|
||||
#: to the source of templates.
|
||||
#:
|
||||
#: .. versionadded:: 2.4
|
||||
has_source_access = True
|
||||
|
||||
def get_source(self, environment, template):
|
||||
"""Get the template source, filename and reload helper for a template.
|
||||
It's passed the environment and template name and has to return a
|
||||
tuple in the form ``(source, filename, uptodate)`` or raise a
|
||||
`TemplateNotFound` error if it can't locate the template.
|
||||
|
||||
The source part of the returned tuple must be the source of the
|
||||
template as unicode string or a ASCII bytestring. The filename should
|
||||
be the name of the file on the filesystem if it was loaded from there,
|
||||
otherwise `None`. The filename is used by python for the tracebacks
|
||||
if no loader extension is used.
|
||||
|
||||
The last item in the tuple is the `uptodate` function. If auto
|
||||
reloading is enabled it's always called to check if the template
|
||||
changed. No arguments are passed so the function must store the
|
||||
old state somewhere (for example in a closure). If it returns `False`
|
||||
the template will be reloaded.
|
||||
"""
|
||||
if not self.has_source_access:
|
||||
raise RuntimeError('%s cannot provide access to the source' %
|
||||
self.__class__.__name__)
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
def list_templates(self):
|
||||
"""Iterates over all templates. If the loader does not support that
|
||||
it should raise a :exc:`TypeError` which is the default behavior.
|
||||
"""
|
||||
raise TypeError('this loader cannot iterate over all templates')
|
||||
|
||||
@internalcode
|
||||
def load(self, environment, name, globals=None):
|
||||
"""Loads a template. This method looks up the template in the cache
|
||||
or loads one by calling :meth:`get_source`. Subclasses should not
|
||||
override this method as loaders working on collections of other
|
||||
loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
|
||||
will not call this method but `get_source` directly.
|
||||
"""
|
||||
code = None
|
||||
if globals is None:
|
||||
globals = {}
|
||||
|
||||
# first we try to get the source for this template together
|
||||
# with the filename and the uptodate function.
|
||||
source, filename, uptodate = self.get_source(environment, name)
|
||||
|
||||
# try to load the code from the bytecode cache if there is a
|
||||
# bytecode cache configured.
|
||||
bcc = environment.bytecode_cache
|
||||
if bcc is not None:
|
||||
bucket = bcc.get_bucket(environment, name, filename, source)
|
||||
code = bucket.code
|
||||
|
||||
# if we don't have code so far (not cached, no longer up to
|
||||
# date) etc. we compile the template
|
||||
if code is None:
|
||||
code = environment.compile(source, name, filename)
|
||||
|
||||
# if the bytecode cache is available and the bucket doesn't
|
||||
# have a code so far, we give the bucket the new code and put
|
||||
# it back to the bytecode cache.
|
||||
if bcc is not None and bucket.code is None:
|
||||
bucket.code = code
|
||||
bcc.set_bucket(bucket)
|
||||
|
||||
return environment.template_class.from_code(environment, code,
|
||||
globals, uptodate)
|
||||
|
||||
|
||||
class FileSystemLoader(BaseLoader):
|
||||
"""Loads templates from the file system. This loader can find templates
|
||||
in folders on the file system and is the preferred way to load them.
|
||||
|
||||
The loader takes the path to the templates as string, or if multiple
|
||||
locations are wanted a list of them which is then looked up in the
|
||||
given order::
|
||||
|
||||
>>> loader = FileSystemLoader('/path/to/templates')
|
||||
>>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
|
||||
|
||||
Per default the template encoding is ``'utf-8'`` which can be changed
|
||||
by setting the `encoding` parameter to something else.
|
||||
|
||||
To follow symbolic links, set the *followlinks* parameter to ``True``::
|
||||
|
||||
>>> loader = FileSystemLoader('/path/to/templates', followlinks=True)
|
||||
|
||||
.. versionchanged:: 2.8+
|
||||
The *followlinks* parameter was added.
|
||||
"""
|
||||
|
||||
def __init__(self, searchpath, encoding='utf-8', followlinks=False):
|
||||
if isinstance(searchpath, string_types):
|
||||
searchpath = [searchpath]
|
||||
self.searchpath = list(searchpath)
|
||||
self.encoding = encoding
|
||||
self.followlinks = followlinks
|
||||
|
||||
def get_source(self, environment, template):
|
||||
pieces = split_template_path(template)
|
||||
for searchpath in self.searchpath:
|
||||
filename = path.join(searchpath, *pieces)
|
||||
f = open_if_exists(filename)
|
||||
if f is None:
|
||||
continue
|
||||
try:
|
||||
contents = f.read().decode(self.encoding)
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
mtime = path.getmtime(filename)
|
||||
|
||||
def uptodate():
|
||||
try:
|
||||
return path.getmtime(filename) == mtime
|
||||
except OSError:
|
||||
return False
|
||||
return contents, filename, uptodate
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
def list_templates(self):
|
||||
found = set()
|
||||
for searchpath in self.searchpath:
|
||||
walk_dir = os.walk(searchpath, followlinks=self.followlinks)
|
||||
for dirpath, dirnames, filenames in walk_dir:
|
||||
for filename in filenames:
|
||||
template = os.path.join(dirpath, filename) \
|
||||
[len(searchpath):].strip(os.path.sep) \
|
||||
.replace(os.path.sep, '/')
|
||||
if template[:2] == './':
|
||||
template = template[2:]
|
||||
if template not in found:
|
||||
found.add(template)
|
||||
return sorted(found)
|
||||
|
||||
|
||||
class PackageLoader(BaseLoader):
|
||||
"""Load templates from python eggs or packages. It is constructed with
|
||||
the name of the python package and the path to the templates in that
|
||||
package::
|
||||
|
||||
loader = PackageLoader('mypackage', 'views')
|
||||
|
||||
If the package path is not given, ``'templates'`` is assumed.
|
||||
|
||||
Per default the template encoding is ``'utf-8'`` which can be changed
|
||||
by setting the `encoding` parameter to something else. Due to the nature
|
||||
of eggs it's only possible to reload templates if the package was loaded
|
||||
from the file system and not a zip file.
|
||||
"""
|
||||
|
||||
def __init__(self, package_name, package_path='templates',
|
||||
encoding='utf-8'):
|
||||
from pkg_resources import DefaultProvider, ResourceManager, \
|
||||
get_provider
|
||||
provider = get_provider(package_name)
|
||||
self.encoding = encoding
|
||||
self.manager = ResourceManager()
|
||||
self.filesystem_bound = isinstance(provider, DefaultProvider)
|
||||
self.provider = provider
|
||||
self.package_path = package_path
|
||||
|
||||
def get_source(self, environment, template):
|
||||
pieces = split_template_path(template)
|
||||
p = '/'.join((self.package_path,) + tuple(pieces))
|
||||
if not self.provider.has_resource(p):
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
filename = uptodate = None
|
||||
if self.filesystem_bound:
|
||||
filename = self.provider.get_resource_filename(self.manager, p)
|
||||
mtime = path.getmtime(filename)
|
||||
def uptodate():
|
||||
try:
|
||||
return path.getmtime(filename) == mtime
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
source = self.provider.get_resource_string(self.manager, p)
|
||||
return source.decode(self.encoding), filename, uptodate
|
||||
|
||||
def list_templates(self):
|
||||
path = self.package_path
|
||||
if path[:2] == './':
|
||||
path = path[2:]
|
||||
elif path == '.':
|
||||
path = ''
|
||||
offset = len(path)
|
||||
results = []
|
||||
def _walk(path):
|
||||
for filename in self.provider.resource_listdir(path):
|
||||
fullname = path + '/' + filename
|
||||
if self.provider.resource_isdir(fullname):
|
||||
_walk(fullname)
|
||||
else:
|
||||
results.append(fullname[offset:].lstrip('/'))
|
||||
_walk(path)
|
||||
results.sort()
|
||||
return results
|
||||
|
||||
|
||||
class DictLoader(BaseLoader):
|
||||
"""Loads a template from a python dict. It's passed a dict of unicode
|
||||
strings bound to template names. This loader is useful for unittesting:
|
||||
|
||||
>>> loader = DictLoader({'index.html': 'source here'})
|
||||
|
||||
Because auto reloading is rarely useful this is disabled per default.
|
||||
"""
|
||||
|
||||
def __init__(self, mapping):
|
||||
self.mapping = mapping
|
||||
|
||||
def get_source(self, environment, template):
|
||||
if template in self.mapping:
|
||||
source = self.mapping[template]
|
||||
return source, None, lambda: source == self.mapping.get(template)
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
def list_templates(self):
|
||||
return sorted(self.mapping)
|
||||
|
||||
|
||||
class FunctionLoader(BaseLoader):
|
||||
"""A loader that is passed a function which does the loading. The
|
||||
function receives the name of the template and has to return either
|
||||
an unicode string with the template source, a tuple in the form ``(source,
|
||||
filename, uptodatefunc)`` or `None` if the template does not exist.
|
||||
|
||||
>>> def load_template(name):
|
||||
... if name == 'index.html':
|
||||
... return '...'
|
||||
...
|
||||
>>> loader = FunctionLoader(load_template)
|
||||
|
||||
The `uptodatefunc` is a function that is called if autoreload is enabled
|
||||
and has to return `True` if the template is still up to date. For more
|
||||
details have a look at :meth:`BaseLoader.get_source` which has the same
|
||||
return value.
|
||||
"""
|
||||
|
||||
def __init__(self, load_func):
|
||||
self.load_func = load_func
|
||||
|
||||
def get_source(self, environment, template):
|
||||
rv = self.load_func(template)
|
||||
if rv is None:
|
||||
raise TemplateNotFound(template)
|
||||
elif isinstance(rv, string_types):
|
||||
return rv, None, None
|
||||
return rv
|
||||
|
||||
|
||||
class PrefixLoader(BaseLoader):
|
||||
"""A loader that is passed a dict of loaders where each loader is bound
|
||||
to a prefix. The prefix is delimited from the template by a slash per
|
||||
default, which can be changed by setting the `delimiter` argument to
|
||||
something else::
|
||||
|
||||
loader = PrefixLoader({
|
||||
'app1': PackageLoader('mypackage.app1'),
|
||||
'app2': PackageLoader('mypackage.app2')
|
||||
})
|
||||
|
||||
By loading ``'app1/index.html'`` the file from the app1 package is loaded,
|
||||
by loading ``'app2/index.html'`` the file from the second.
|
||||
"""
|
||||
|
||||
def __init__(self, mapping, delimiter='/'):
|
||||
self.mapping = mapping
|
||||
self.delimiter = delimiter
|
||||
|
||||
def get_loader(self, template):
|
||||
try:
|
||||
prefix, name = template.split(self.delimiter, 1)
|
||||
loader = self.mapping[prefix]
|
||||
except (ValueError, KeyError):
|
||||
raise TemplateNotFound(template)
|
||||
return loader, name
|
||||
|
||||
def get_source(self, environment, template):
|
||||
loader, name = self.get_loader(template)
|
||||
try:
|
||||
return loader.get_source(environment, name)
|
||||
except TemplateNotFound:
|
||||
# re-raise the exception with the correct filename here.
|
||||
# (the one that includes the prefix)
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
@internalcode
|
||||
def load(self, environment, name, globals=None):
|
||||
loader, local_name = self.get_loader(name)
|
||||
try:
|
||||
return loader.load(environment, local_name, globals)
|
||||
except TemplateNotFound:
|
||||
# re-raise the exception with the correct filename here.
|
||||
# (the one that includes the prefix)
|
||||
raise TemplateNotFound(name)
|
||||
|
||||
def list_templates(self):
|
||||
result = []
|
||||
for prefix, loader in iteritems(self.mapping):
|
||||
for template in loader.list_templates():
|
||||
result.append(prefix + self.delimiter + template)
|
||||
return result
|
||||
|
||||
|
||||
class ChoiceLoader(BaseLoader):
|
||||
"""This loader works like the `PrefixLoader` just that no prefix is
|
||||
specified. If a template could not be found by one loader the next one
|
||||
is tried.
|
||||
|
||||
>>> loader = ChoiceLoader([
|
||||
... FileSystemLoader('/path/to/user/templates'),
|
||||
... FileSystemLoader('/path/to/system/templates')
|
||||
... ])
|
||||
|
||||
This is useful if you want to allow users to override builtin templates
|
||||
from a different location.
|
||||
"""
|
||||
|
||||
def __init__(self, loaders):
|
||||
self.loaders = loaders
|
||||
|
||||
def get_source(self, environment, template):
|
||||
for loader in self.loaders:
|
||||
try:
|
||||
return loader.get_source(environment, template)
|
||||
except TemplateNotFound:
|
||||
pass
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
@internalcode
|
||||
def load(self, environment, name, globals=None):
|
||||
for loader in self.loaders:
|
||||
try:
|
||||
return loader.load(environment, name, globals)
|
||||
except TemplateNotFound:
|
||||
pass
|
||||
raise TemplateNotFound(name)
|
||||
|
||||
def list_templates(self):
|
||||
found = set()
|
||||
for loader in self.loaders:
|
||||
found.update(loader.list_templates())
|
||||
return sorted(found)
|
||||
|
||||
|
||||
class _TemplateModule(ModuleType):
|
||||
"""Like a normal module but with support for weak references"""
|
||||
|
||||
|
||||
class ModuleLoader(BaseLoader):
|
||||
"""This loader loads templates from precompiled templates.
|
||||
|
||||
Example usage:
|
||||
|
||||
>>> loader = ChoiceLoader([
|
||||
... ModuleLoader('/path/to/compiled/templates'),
|
||||
... FileSystemLoader('/path/to/templates')
|
||||
... ])
|
||||
|
||||
Templates can be precompiled with :meth:`Environment.compile_templates`.
|
||||
"""
|
||||
|
||||
has_source_access = False
|
||||
|
||||
def __init__(self, path):
|
||||
package_name = '_jinja2_module_templates_%x' % id(self)
|
||||
|
||||
# create a fake module that looks for the templates in the
|
||||
# path given.
|
||||
mod = _TemplateModule(package_name)
|
||||
if isinstance(path, string_types):
|
||||
path = [path]
|
||||
else:
|
||||
path = list(path)
|
||||
mod.__path__ = path
|
||||
|
||||
sys.modules[package_name] = weakref.proxy(mod,
|
||||
lambda x: sys.modules.pop(package_name, None))
|
||||
|
||||
# the only strong reference, the sys.modules entry is weak
|
||||
# so that the garbage collector can remove it once the
|
||||
# loader that created it goes out of business.
|
||||
self.module = mod
|
||||
self.package_name = package_name
|
||||
|
||||
@staticmethod
|
||||
def get_template_key(name):
|
||||
return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def get_module_filename(name):
|
||||
return ModuleLoader.get_template_key(name) + '.py'
|
||||
|
||||
@internalcode
|
||||
def load(self, environment, name, globals=None):
|
||||
key = self.get_template_key(name)
|
||||
module = '%s.%s' % (self.package_name, key)
|
||||
mod = getattr(self.module, module, None)
|
||||
if mod is None:
|
||||
try:
|
||||
mod = __import__(module, None, None, ['root'])
|
||||
except ImportError:
|
||||
raise TemplateNotFound(name)
|
||||
|
||||
# remove the entry from sys.modules, we only want the attribute
|
||||
# on the module object we have stored on the loader.
|
||||
sys.modules.pop(module, None)
|
||||
|
||||
return environment.template_class.from_module_dict(
|
||||
environment, mod.__dict__, globals)
|
||||
Vendored
-106
@@ -1,106 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.meta
|
||||
~~~~~~~~~~~
|
||||
|
||||
This module implements various functions that exposes information about
|
||||
templates that might be interesting for various kinds of applications.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from jinja2 import nodes
|
||||
from jinja2.compiler import CodeGenerator
|
||||
from jinja2._compat import string_types, iteritems
|
||||
|
||||
|
||||
class TrackingCodeGenerator(CodeGenerator):
|
||||
"""We abuse the code generator for introspection."""
|
||||
|
||||
def __init__(self, environment):
|
||||
CodeGenerator.__init__(self, environment, '<introspection>',
|
||||
'<introspection>')
|
||||
self.undeclared_identifiers = set()
|
||||
|
||||
def write(self, x):
|
||||
"""Don't write."""
|
||||
|
||||
def enter_frame(self, frame):
|
||||
"""Remember all undeclared identifiers."""
|
||||
CodeGenerator.enter_frame(self, frame)
|
||||
for _, (action, param) in iteritems(frame.symbols.loads):
|
||||
if action == 'resolve':
|
||||
self.undeclared_identifiers.add(param)
|
||||
|
||||
|
||||
def find_undeclared_variables(ast):
|
||||
"""Returns a set of all variables in the AST that will be looked up from
|
||||
the context at runtime. Because at compile time it's not known which
|
||||
variables will be used depending on the path the execution takes at
|
||||
runtime, all variables are returned.
|
||||
|
||||
>>> from jinja2 import Environment, meta
|
||||
>>> env = Environment()
|
||||
>>> ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
|
||||
>>> meta.find_undeclared_variables(ast) == set(['bar'])
|
||||
True
|
||||
|
||||
.. admonition:: Implementation
|
||||
|
||||
Internally the code generator is used for finding undeclared variables.
|
||||
This is good to know because the code generator might raise a
|
||||
:exc:`TemplateAssertionError` during compilation and as a matter of
|
||||
fact this function can currently raise that exception as well.
|
||||
"""
|
||||
codegen = TrackingCodeGenerator(ast.environment)
|
||||
codegen.visit(ast)
|
||||
return codegen.undeclared_identifiers
|
||||
|
||||
|
||||
def find_referenced_templates(ast):
|
||||
"""Finds all the referenced templates from the AST. This will return an
|
||||
iterator over all the hardcoded template extensions, inclusions and
|
||||
imports. If dynamic inheritance or inclusion is used, `None` will be
|
||||
yielded.
|
||||
|
||||
>>> from jinja2 import Environment, meta
|
||||
>>> env = Environment()
|
||||
>>> ast = env.parse('{% extends "layout.html" %}{% include helper %}')
|
||||
>>> list(meta.find_referenced_templates(ast))
|
||||
['layout.html', None]
|
||||
|
||||
This function is useful for dependency tracking. For example if you want
|
||||
to rebuild parts of the website after a layout template has changed.
|
||||
"""
|
||||
for node in ast.find_all((nodes.Extends, nodes.FromImport, nodes.Import,
|
||||
nodes.Include)):
|
||||
if not isinstance(node.template, nodes.Const):
|
||||
# a tuple with some non consts in there
|
||||
if isinstance(node.template, (nodes.Tuple, nodes.List)):
|
||||
for template_name in node.template.items:
|
||||
# something const, only yield the strings and ignore
|
||||
# non-string consts that really just make no sense
|
||||
if isinstance(template_name, nodes.Const):
|
||||
if isinstance(template_name.value, string_types):
|
||||
yield template_name.value
|
||||
# something dynamic in there
|
||||
else:
|
||||
yield None
|
||||
# something dynamic we don't know about here
|
||||
else:
|
||||
yield None
|
||||
continue
|
||||
# constant is a basestring, direct template name
|
||||
if isinstance(node.template.value, string_types):
|
||||
yield node.template.value
|
||||
# a tuple or list (latter *should* not happen) made of consts,
|
||||
# yield the consts that are strings. We could warn here for
|
||||
# non string values
|
||||
elif isinstance(node, nodes.Include) and \
|
||||
isinstance(node.template.value, (tuple, list)):
|
||||
for template_name in node.template.value:
|
||||
if isinstance(template_name, string_types):
|
||||
yield template_name
|
||||
# something else we don't care about, we could warn here
|
||||
else:
|
||||
yield None
|
||||
Vendored
-939
@@ -1,939 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.nodes
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This module implements additional nodes derived from the ast base node.
|
||||
|
||||
It also provides some node tree helper functions like `in_lineno` and
|
||||
`get_nodes` used by the parser and translator in order to normalize
|
||||
python and jinja nodes.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import types
|
||||
import operator
|
||||
|
||||
from collections import deque
|
||||
from jinja2.utils import Markup
|
||||
from jinja2._compat import izip, with_metaclass, text_type, PY2
|
||||
|
||||
|
||||
#: the types we support for context functions
|
||||
_context_function_types = (types.FunctionType, types.MethodType)
|
||||
|
||||
|
||||
_binop_to_func = {
|
||||
'*': operator.mul,
|
||||
'/': operator.truediv,
|
||||
'//': operator.floordiv,
|
||||
'**': operator.pow,
|
||||
'%': operator.mod,
|
||||
'+': operator.add,
|
||||
'-': operator.sub
|
||||
}
|
||||
|
||||
_uaop_to_func = {
|
||||
'not': operator.not_,
|
||||
'+': operator.pos,
|
||||
'-': operator.neg
|
||||
}
|
||||
|
||||
_cmpop_to_func = {
|
||||
'eq': operator.eq,
|
||||
'ne': operator.ne,
|
||||
'gt': operator.gt,
|
||||
'gteq': operator.ge,
|
||||
'lt': operator.lt,
|
||||
'lteq': operator.le,
|
||||
'in': lambda a, b: a in b,
|
||||
'notin': lambda a, b: a not in b
|
||||
}
|
||||
|
||||
|
||||
class Impossible(Exception):
|
||||
"""Raised if the node could not perform a requested action."""
|
||||
|
||||
|
||||
class NodeType(type):
|
||||
"""A metaclass for nodes that handles the field and attribute
|
||||
inheritance. fields and attributes from the parent class are
|
||||
automatically forwarded to the child."""
|
||||
|
||||
def __new__(cls, name, bases, d):
|
||||
for attr in 'fields', 'attributes':
|
||||
storage = []
|
||||
storage.extend(getattr(bases[0], attr, ()))
|
||||
storage.extend(d.get(attr, ()))
|
||||
assert len(bases) == 1, 'multiple inheritance not allowed'
|
||||
assert len(storage) == len(set(storage)), 'layout conflict'
|
||||
d[attr] = tuple(storage)
|
||||
d.setdefault('abstract', False)
|
||||
return type.__new__(cls, name, bases, d)
|
||||
|
||||
|
||||
class EvalContext(object):
|
||||
"""Holds evaluation time information. Custom attributes can be attached
|
||||
to it in extensions.
|
||||
"""
|
||||
|
||||
def __init__(self, environment, template_name=None):
|
||||
self.environment = environment
|
||||
if callable(environment.autoescape):
|
||||
self.autoescape = environment.autoescape(template_name)
|
||||
else:
|
||||
self.autoescape = environment.autoescape
|
||||
self.volatile = False
|
||||
|
||||
def save(self):
|
||||
return self.__dict__.copy()
|
||||
|
||||
def revert(self, old):
|
||||
self.__dict__.clear()
|
||||
self.__dict__.update(old)
|
||||
|
||||
|
||||
def get_eval_context(node, ctx):
|
||||
if ctx is None:
|
||||
if node.environment is None:
|
||||
raise RuntimeError('if no eval context is passed, the '
|
||||
'node must have an attached '
|
||||
'environment.')
|
||||
return EvalContext(node.environment)
|
||||
return ctx
|
||||
|
||||
|
||||
class Node(with_metaclass(NodeType, object)):
|
||||
"""Baseclass for all Jinja2 nodes. There are a number of nodes available
|
||||
of different types. There are four major types:
|
||||
|
||||
- :class:`Stmt`: statements
|
||||
- :class:`Expr`: expressions
|
||||
- :class:`Helper`: helper nodes
|
||||
- :class:`Template`: the outermost wrapper node
|
||||
|
||||
All nodes have fields and attributes. Fields may be other nodes, lists,
|
||||
or arbitrary values. Fields are passed to the constructor as regular
|
||||
positional arguments, attributes as keyword arguments. Each node has
|
||||
two attributes: `lineno` (the line number of the node) and `environment`.
|
||||
The `environment` attribute is set at the end of the parsing process for
|
||||
all nodes automatically.
|
||||
"""
|
||||
fields = ()
|
||||
attributes = ('lineno', 'environment')
|
||||
abstract = True
|
||||
|
||||
def __init__(self, *fields, **attributes):
|
||||
if self.abstract:
|
||||
raise TypeError('abstract nodes are not instanciable')
|
||||
if fields:
|
||||
if len(fields) != len(self.fields):
|
||||
if not self.fields:
|
||||
raise TypeError('%r takes 0 arguments' %
|
||||
self.__class__.__name__)
|
||||
raise TypeError('%r takes 0 or %d argument%s' % (
|
||||
self.__class__.__name__,
|
||||
len(self.fields),
|
||||
len(self.fields) != 1 and 's' or ''
|
||||
))
|
||||
for name, arg in izip(self.fields, fields):
|
||||
setattr(self, name, arg)
|
||||
for attr in self.attributes:
|
||||
setattr(self, attr, attributes.pop(attr, None))
|
||||
if attributes:
|
||||
raise TypeError('unknown attribute %r' %
|
||||
next(iter(attributes)))
|
||||
|
||||
def iter_fields(self, exclude=None, only=None):
|
||||
"""This method iterates over all fields that are defined and yields
|
||||
``(key, value)`` tuples. Per default all fields are returned, but
|
||||
it's possible to limit that to some fields by providing the `only`
|
||||
parameter or to exclude some using the `exclude` parameter. Both
|
||||
should be sets or tuples of field names.
|
||||
"""
|
||||
for name in self.fields:
|
||||
if (exclude is only is None) or \
|
||||
(exclude is not None and name not in exclude) or \
|
||||
(only is not None and name in only):
|
||||
try:
|
||||
yield name, getattr(self, name)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def iter_child_nodes(self, exclude=None, only=None):
|
||||
"""Iterates over all direct child nodes of the node. This iterates
|
||||
over all fields and yields the values of they are nodes. If the value
|
||||
of a field is a list all the nodes in that list are returned.
|
||||
"""
|
||||
for field, item in self.iter_fields(exclude, only):
|
||||
if isinstance(item, list):
|
||||
for n in item:
|
||||
if isinstance(n, Node):
|
||||
yield n
|
||||
elif isinstance(item, Node):
|
||||
yield item
|
||||
|
||||
def find(self, node_type):
|
||||
"""Find the first node of a given type. If no such node exists the
|
||||
return value is `None`.
|
||||
"""
|
||||
for result in self.find_all(node_type):
|
||||
return result
|
||||
|
||||
def find_all(self, node_type):
|
||||
"""Find all the nodes of a given type. If the type is a tuple,
|
||||
the check is performed for any of the tuple items.
|
||||
"""
|
||||
for child in self.iter_child_nodes():
|
||||
if isinstance(child, node_type):
|
||||
yield child
|
||||
for result in child.find_all(node_type):
|
||||
yield result
|
||||
|
||||
def set_ctx(self, ctx):
|
||||
"""Reset the context of a node and all child nodes. Per default the
|
||||
parser will all generate nodes that have a 'load' context as it's the
|
||||
most common one. This method is used in the parser to set assignment
|
||||
targets and other nodes to a store context.
|
||||
"""
|
||||
todo = deque([self])
|
||||
while todo:
|
||||
node = todo.popleft()
|
||||
if 'ctx' in node.fields:
|
||||
node.ctx = ctx
|
||||
todo.extend(node.iter_child_nodes())
|
||||
return self
|
||||
|
||||
def set_lineno(self, lineno, override=False):
|
||||
"""Set the line numbers of the node and children."""
|
||||
todo = deque([self])
|
||||
while todo:
|
||||
node = todo.popleft()
|
||||
if 'lineno' in node.attributes:
|
||||
if node.lineno is None or override:
|
||||
node.lineno = lineno
|
||||
todo.extend(node.iter_child_nodes())
|
||||
return self
|
||||
|
||||
def set_environment(self, environment):
|
||||
"""Set the environment for all nodes."""
|
||||
todo = deque([self])
|
||||
while todo:
|
||||
node = todo.popleft()
|
||||
node.environment = environment
|
||||
todo.extend(node.iter_child_nodes())
|
||||
return self
|
||||
|
||||
def __eq__(self, other):
|
||||
return type(self) is type(other) and \
|
||||
tuple(self.iter_fields()) == tuple(other.iter_fields())
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
# Restore Python 2 hashing behavior on Python 3
|
||||
__hash__ = object.__hash__
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (
|
||||
self.__class__.__name__,
|
||||
', '.join('%s=%r' % (arg, getattr(self, arg, None)) for
|
||||
arg in self.fields)
|
||||
)
|
||||
|
||||
def dump(self):
|
||||
def _dump(node):
|
||||
if not isinstance(node, Node):
|
||||
buf.append(repr(node))
|
||||
return
|
||||
|
||||
buf.append('nodes.%s(' % node.__class__.__name__)
|
||||
if not node.fields:
|
||||
buf.append(')')
|
||||
return
|
||||
for idx, field in enumerate(node.fields):
|
||||
if idx:
|
||||
buf.append(', ')
|
||||
value = getattr(node, field)
|
||||
if isinstance(value, list):
|
||||
buf.append('[')
|
||||
for idx, item in enumerate(value):
|
||||
if idx:
|
||||
buf.append(', ')
|
||||
_dump(item)
|
||||
buf.append(']')
|
||||
else:
|
||||
_dump(value)
|
||||
buf.append(')')
|
||||
buf = []
|
||||
_dump(self)
|
||||
return ''.join(buf)
|
||||
|
||||
|
||||
|
||||
class Stmt(Node):
|
||||
"""Base node for all statements."""
|
||||
abstract = True
|
||||
|
||||
|
||||
class Helper(Node):
|
||||
"""Nodes that exist in a specific context only."""
|
||||
abstract = True
|
||||
|
||||
|
||||
class Template(Node):
|
||||
"""Node that represents a template. This must be the outermost node that
|
||||
is passed to the compiler.
|
||||
"""
|
||||
fields = ('body',)
|
||||
|
||||
|
||||
class Output(Stmt):
|
||||
"""A node that holds multiple expressions which are then printed out.
|
||||
This is used both for the `print` statement and the regular template data.
|
||||
"""
|
||||
fields = ('nodes',)
|
||||
|
||||
|
||||
class Extends(Stmt):
|
||||
"""Represents an extends statement."""
|
||||
fields = ('template',)
|
||||
|
||||
|
||||
class For(Stmt):
|
||||
"""The for loop. `target` is the target for the iteration (usually a
|
||||
:class:`Name` or :class:`Tuple`), `iter` the iterable. `body` is a list
|
||||
of nodes that are used as loop-body, and `else_` a list of nodes for the
|
||||
`else` block. If no else node exists it has to be an empty list.
|
||||
|
||||
For filtered nodes an expression can be stored as `test`, otherwise `None`.
|
||||
"""
|
||||
fields = ('target', 'iter', 'body', 'else_', 'test', 'recursive')
|
||||
|
||||
|
||||
class If(Stmt):
|
||||
"""If `test` is true, `body` is rendered, else `else_`."""
|
||||
fields = ('test', 'body', 'else_')
|
||||
|
||||
|
||||
class Macro(Stmt):
|
||||
"""A macro definition. `name` is the name of the macro, `args` a list of
|
||||
arguments and `defaults` a list of defaults if there are any. `body` is
|
||||
a list of nodes for the macro body.
|
||||
"""
|
||||
fields = ('name', 'args', 'defaults', 'body')
|
||||
|
||||
|
||||
class CallBlock(Stmt):
|
||||
"""Like a macro without a name but a call instead. `call` is called with
|
||||
the unnamed macro as `caller` argument this node holds.
|
||||
"""
|
||||
fields = ('call', 'args', 'defaults', 'body')
|
||||
|
||||
|
||||
class FilterBlock(Stmt):
|
||||
"""Node for filter sections."""
|
||||
fields = ('body', 'filter')
|
||||
|
||||
|
||||
class With(Stmt):
|
||||
"""Specific node for with statements. In older versions of Jinja the
|
||||
with statement was implemented on the base of the `Scope` node instead.
|
||||
|
||||
.. versionadded:: 2.9.3
|
||||
"""
|
||||
fields = ('targets', 'values', 'body')
|
||||
|
||||
|
||||
class Block(Stmt):
|
||||
"""A node that represents a block."""
|
||||
fields = ('name', 'body', 'scoped')
|
||||
|
||||
|
||||
class Include(Stmt):
|
||||
"""A node that represents the include tag."""
|
||||
fields = ('template', 'with_context', 'ignore_missing')
|
||||
|
||||
|
||||
class Import(Stmt):
|
||||
"""A node that represents the import tag."""
|
||||
fields = ('template', 'target', 'with_context')
|
||||
|
||||
|
||||
class FromImport(Stmt):
|
||||
"""A node that represents the from import tag. It's important to not
|
||||
pass unsafe names to the name attribute. The compiler translates the
|
||||
attribute lookups directly into getattr calls and does *not* use the
|
||||
subscript callback of the interface. As exported variables may not
|
||||
start with double underscores (which the parser asserts) this is not a
|
||||
problem for regular Jinja code, but if this node is used in an extension
|
||||
extra care must be taken.
|
||||
|
||||
The list of names may contain tuples if aliases are wanted.
|
||||
"""
|
||||
fields = ('template', 'names', 'with_context')
|
||||
|
||||
|
||||
class ExprStmt(Stmt):
|
||||
"""A statement that evaluates an expression and discards the result."""
|
||||
fields = ('node',)
|
||||
|
||||
|
||||
class Assign(Stmt):
|
||||
"""Assigns an expression to a target."""
|
||||
fields = ('target', 'node')
|
||||
|
||||
|
||||
class AssignBlock(Stmt):
|
||||
"""Assigns a block to a target."""
|
||||
fields = ('target', 'body')
|
||||
|
||||
|
||||
class Expr(Node):
|
||||
"""Baseclass for all expressions."""
|
||||
abstract = True
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
"""Return the value of the expression as constant or raise
|
||||
:exc:`Impossible` if this was not possible.
|
||||
|
||||
An :class:`EvalContext` can be provided, if none is given
|
||||
a default context is created which requires the nodes to have
|
||||
an attached environment.
|
||||
|
||||
.. versionchanged:: 2.4
|
||||
the `eval_ctx` parameter was added.
|
||||
"""
|
||||
raise Impossible()
|
||||
|
||||
def can_assign(self):
|
||||
"""Check if it's possible to assign something to this node."""
|
||||
return False
|
||||
|
||||
|
||||
class BinExpr(Expr):
|
||||
"""Baseclass for all binary expressions."""
|
||||
fields = ('left', 'right')
|
||||
operator = None
|
||||
abstract = True
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
# intercepted operators cannot be folded at compile time
|
||||
if self.environment.sandboxed and \
|
||||
self.operator in self.environment.intercepted_binops:
|
||||
raise Impossible()
|
||||
f = _binop_to_func[self.operator]
|
||||
try:
|
||||
return f(self.left.as_const(eval_ctx), self.right.as_const(eval_ctx))
|
||||
except Exception:
|
||||
raise Impossible()
|
||||
|
||||
|
||||
class UnaryExpr(Expr):
|
||||
"""Baseclass for all unary expressions."""
|
||||
fields = ('node',)
|
||||
operator = None
|
||||
abstract = True
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
# intercepted operators cannot be folded at compile time
|
||||
if self.environment.sandboxed and \
|
||||
self.operator in self.environment.intercepted_unops:
|
||||
raise Impossible()
|
||||
f = _uaop_to_func[self.operator]
|
||||
try:
|
||||
return f(self.node.as_const(eval_ctx))
|
||||
except Exception:
|
||||
raise Impossible()
|
||||
|
||||
|
||||
class Name(Expr):
|
||||
"""Looks up a name or stores a value in a name.
|
||||
The `ctx` of the node can be one of the following values:
|
||||
|
||||
- `store`: store a value in the name
|
||||
- `load`: load that name
|
||||
- `param`: like `store` but if the name was defined as function parameter.
|
||||
"""
|
||||
fields = ('name', 'ctx')
|
||||
|
||||
def can_assign(self):
|
||||
return self.name not in ('true', 'false', 'none',
|
||||
'True', 'False', 'None')
|
||||
|
||||
|
||||
class Literal(Expr):
|
||||
"""Baseclass for literals."""
|
||||
abstract = True
|
||||
|
||||
|
||||
class Const(Literal):
|
||||
"""All constant values. The parser will return this node for simple
|
||||
constants such as ``42`` or ``"foo"`` but it can be used to store more
|
||||
complex values such as lists too. Only constants with a safe
|
||||
representation (objects where ``eval(repr(x)) == x`` is true).
|
||||
"""
|
||||
fields = ('value',)
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
rv = self.value
|
||||
if PY2 and type(rv) is text_type and \
|
||||
self.environment.policies['compiler.ascii_str']:
|
||||
try:
|
||||
rv = rv.encode('ascii')
|
||||
except UnicodeError:
|
||||
pass
|
||||
return rv
|
||||
|
||||
@classmethod
|
||||
def from_untrusted(cls, value, lineno=None, environment=None):
|
||||
"""Return a const object if the value is representable as
|
||||
constant value in the generated code, otherwise it will raise
|
||||
an `Impossible` exception.
|
||||
"""
|
||||
from .compiler import has_safe_repr
|
||||
if not has_safe_repr(value):
|
||||
raise Impossible()
|
||||
return cls(value, lineno=lineno, environment=environment)
|
||||
|
||||
|
||||
class TemplateData(Literal):
|
||||
"""A constant template string."""
|
||||
fields = ('data',)
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
if eval_ctx.volatile:
|
||||
raise Impossible()
|
||||
if eval_ctx.autoescape:
|
||||
return Markup(self.data)
|
||||
return self.data
|
||||
|
||||
|
||||
class Tuple(Literal):
|
||||
"""For loop unpacking and some other things like multiple arguments
|
||||
for subscripts. Like for :class:`Name` `ctx` specifies if the tuple
|
||||
is used for loading the names or storing.
|
||||
"""
|
||||
fields = ('items', 'ctx')
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return tuple(x.as_const(eval_ctx) for x in self.items)
|
||||
|
||||
def can_assign(self):
|
||||
for item in self.items:
|
||||
if not item.can_assign():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class List(Literal):
|
||||
"""Any list literal such as ``[1, 2, 3]``"""
|
||||
fields = ('items',)
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return [x.as_const(eval_ctx) for x in self.items]
|
||||
|
||||
|
||||
class Dict(Literal):
|
||||
"""Any dict literal such as ``{1: 2, 3: 4}``. The items must be a list of
|
||||
:class:`Pair` nodes.
|
||||
"""
|
||||
fields = ('items',)
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return dict(x.as_const(eval_ctx) for x in self.items)
|
||||
|
||||
|
||||
class Pair(Helper):
|
||||
"""A key, value pair for dicts."""
|
||||
fields = ('key', 'value')
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return self.key.as_const(eval_ctx), self.value.as_const(eval_ctx)
|
||||
|
||||
|
||||
class Keyword(Helper):
|
||||
"""A key, value pair for keyword arguments where key is a string."""
|
||||
fields = ('key', 'value')
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return self.key, self.value.as_const(eval_ctx)
|
||||
|
||||
|
||||
class CondExpr(Expr):
|
||||
"""A conditional expression (inline if expression). (``{{
|
||||
foo if bar else baz }}``)
|
||||
"""
|
||||
fields = ('test', 'expr1', 'expr2')
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
if self.test.as_const(eval_ctx):
|
||||
return self.expr1.as_const(eval_ctx)
|
||||
|
||||
# if we evaluate to an undefined object, we better do that at runtime
|
||||
if self.expr2 is None:
|
||||
raise Impossible()
|
||||
|
||||
return self.expr2.as_const(eval_ctx)
|
||||
|
||||
|
||||
class Filter(Expr):
|
||||
"""This node applies a filter on an expression. `name` is the name of
|
||||
the filter, the rest of the fields are the same as for :class:`Call`.
|
||||
|
||||
If the `node` of a filter is `None` the contents of the last buffer are
|
||||
filtered. Buffers are created by macros and filter blocks.
|
||||
"""
|
||||
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
if eval_ctx.volatile or self.node is None:
|
||||
raise Impossible()
|
||||
# we have to be careful here because we call filter_ below.
|
||||
# if this variable would be called filter, 2to3 would wrap the
|
||||
# call in a list beause it is assuming we are talking about the
|
||||
# builtin filter function here which no longer returns a list in
|
||||
# python 3. because of that, do not rename filter_ to filter!
|
||||
filter_ = self.environment.filters.get(self.name)
|
||||
if filter_ is None or getattr(filter_, 'contextfilter', False):
|
||||
raise Impossible()
|
||||
|
||||
# We cannot constant handle async filters, so we need to make sure
|
||||
# to not go down this path.
|
||||
if eval_ctx.environment.is_async and \
|
||||
getattr(filter_, 'asyncfiltervariant', False):
|
||||
raise Impossible()
|
||||
|
||||
obj = self.node.as_const(eval_ctx)
|
||||
args = [obj] + [x.as_const(eval_ctx) for x in self.args]
|
||||
if getattr(filter_, 'evalcontextfilter', False):
|
||||
args.insert(0, eval_ctx)
|
||||
elif getattr(filter_, 'environmentfilter', False):
|
||||
args.insert(0, self.environment)
|
||||
kwargs = dict(x.as_const(eval_ctx) for x in self.kwargs)
|
||||
if self.dyn_args is not None:
|
||||
try:
|
||||
args.extend(self.dyn_args.as_const(eval_ctx))
|
||||
except Exception:
|
||||
raise Impossible()
|
||||
if self.dyn_kwargs is not None:
|
||||
try:
|
||||
kwargs.update(self.dyn_kwargs.as_const(eval_ctx))
|
||||
except Exception:
|
||||
raise Impossible()
|
||||
try:
|
||||
return filter_(*args, **kwargs)
|
||||
except Exception:
|
||||
raise Impossible()
|
||||
|
||||
|
||||
class Test(Expr):
|
||||
"""Applies a test on an expression. `name` is the name of the test, the
|
||||
rest of the fields are the same as for :class:`Call`.
|
||||
"""
|
||||
fields = ('node', 'name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
||||
|
||||
|
||||
class Call(Expr):
|
||||
"""Calls an expression. `args` is a list of arguments, `kwargs` a list
|
||||
of keyword arguments (list of :class:`Keyword` nodes), and `dyn_args`
|
||||
and `dyn_kwargs` has to be either `None` or a node that is used as
|
||||
node for dynamic positional (``*args``) or keyword (``**kwargs``)
|
||||
arguments.
|
||||
"""
|
||||
fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
|
||||
|
||||
|
||||
class Getitem(Expr):
|
||||
"""Get an attribute or item from an expression and prefer the item."""
|
||||
fields = ('node', 'arg', 'ctx')
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
if self.ctx != 'load':
|
||||
raise Impossible()
|
||||
try:
|
||||
return self.environment.getitem(self.node.as_const(eval_ctx),
|
||||
self.arg.as_const(eval_ctx))
|
||||
except Exception:
|
||||
raise Impossible()
|
||||
|
||||
def can_assign(self):
|
||||
return False
|
||||
|
||||
|
||||
class Getattr(Expr):
|
||||
"""Get an attribute or item from an expression that is a ascii-only
|
||||
bytestring and prefer the attribute.
|
||||
"""
|
||||
fields = ('node', 'attr', 'ctx')
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
if self.ctx != 'load':
|
||||
raise Impossible()
|
||||
try:
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return self.environment.getattr(self.node.as_const(eval_ctx),
|
||||
self.attr)
|
||||
except Exception:
|
||||
raise Impossible()
|
||||
|
||||
def can_assign(self):
|
||||
return False
|
||||
|
||||
|
||||
class Slice(Expr):
|
||||
"""Represents a slice object. This must only be used as argument for
|
||||
:class:`Subscript`.
|
||||
"""
|
||||
fields = ('start', 'stop', 'step')
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
def const(obj):
|
||||
if obj is None:
|
||||
return None
|
||||
return obj.as_const(eval_ctx)
|
||||
return slice(const(self.start), const(self.stop), const(self.step))
|
||||
|
||||
|
||||
class Concat(Expr):
|
||||
"""Concatenates the list of expressions provided after converting them to
|
||||
unicode.
|
||||
"""
|
||||
fields = ('nodes',)
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return ''.join(text_type(x.as_const(eval_ctx)) for x in self.nodes)
|
||||
|
||||
|
||||
class Compare(Expr):
|
||||
"""Compares an expression with some other expressions. `ops` must be a
|
||||
list of :class:`Operand`\\s.
|
||||
"""
|
||||
fields = ('expr', 'ops')
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
result = value = self.expr.as_const(eval_ctx)
|
||||
try:
|
||||
for op in self.ops:
|
||||
new_value = op.expr.as_const(eval_ctx)
|
||||
result = _cmpop_to_func[op.op](value, new_value)
|
||||
value = new_value
|
||||
except Exception:
|
||||
raise Impossible()
|
||||
return result
|
||||
|
||||
|
||||
class Operand(Helper):
|
||||
"""Holds an operator and an expression."""
|
||||
fields = ('op', 'expr')
|
||||
|
||||
if __debug__:
|
||||
Operand.__doc__ += '\nThe following operators are available: ' + \
|
||||
', '.join(sorted('``%s``' % x for x in set(_binop_to_func) |
|
||||
set(_uaop_to_func) | set(_cmpop_to_func)))
|
||||
|
||||
|
||||
class Mul(BinExpr):
|
||||
"""Multiplies the left with the right node."""
|
||||
operator = '*'
|
||||
|
||||
|
||||
class Div(BinExpr):
|
||||
"""Divides the left by the right node."""
|
||||
operator = '/'
|
||||
|
||||
|
||||
class FloorDiv(BinExpr):
|
||||
"""Divides the left by the right node and truncates conver the
|
||||
result into an integer by truncating.
|
||||
"""
|
||||
operator = '//'
|
||||
|
||||
|
||||
class Add(BinExpr):
|
||||
"""Add the left to the right node."""
|
||||
operator = '+'
|
||||
|
||||
|
||||
class Sub(BinExpr):
|
||||
"""Subtract the right from the left node."""
|
||||
operator = '-'
|
||||
|
||||
|
||||
class Mod(BinExpr):
|
||||
"""Left modulo right."""
|
||||
operator = '%'
|
||||
|
||||
|
||||
class Pow(BinExpr):
|
||||
"""Left to the power of right."""
|
||||
operator = '**'
|
||||
|
||||
|
||||
class And(BinExpr):
|
||||
"""Short circuited AND."""
|
||||
operator = 'and'
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return self.left.as_const(eval_ctx) and self.right.as_const(eval_ctx)
|
||||
|
||||
|
||||
class Or(BinExpr):
|
||||
"""Short circuited OR."""
|
||||
operator = 'or'
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return self.left.as_const(eval_ctx) or self.right.as_const(eval_ctx)
|
||||
|
||||
|
||||
class Not(UnaryExpr):
|
||||
"""Negate the expression."""
|
||||
operator = 'not'
|
||||
|
||||
|
||||
class Neg(UnaryExpr):
|
||||
"""Make the expression negative."""
|
||||
operator = '-'
|
||||
|
||||
|
||||
class Pos(UnaryExpr):
|
||||
"""Make the expression positive (noop for most expressions)"""
|
||||
operator = '+'
|
||||
|
||||
|
||||
# Helpers for extensions
|
||||
|
||||
|
||||
class EnvironmentAttribute(Expr):
|
||||
"""Loads an attribute from the environment object. This is useful for
|
||||
extensions that want to call a callback stored on the environment.
|
||||
"""
|
||||
fields = ('name',)
|
||||
|
||||
|
||||
class ExtensionAttribute(Expr):
|
||||
"""Returns the attribute of an extension bound to the environment.
|
||||
The identifier is the identifier of the :class:`Extension`.
|
||||
|
||||
This node is usually constructed by calling the
|
||||
:meth:`~jinja2.ext.Extension.attr` method on an extension.
|
||||
"""
|
||||
fields = ('identifier', 'name')
|
||||
|
||||
|
||||
class ImportedName(Expr):
|
||||
"""If created with an import name the import name is returned on node
|
||||
access. For example ``ImportedName('cgi.escape')`` returns the `escape`
|
||||
function from the cgi module on evaluation. Imports are optimized by the
|
||||
compiler so there is no need to assign them to local variables.
|
||||
"""
|
||||
fields = ('importname',)
|
||||
|
||||
|
||||
class InternalName(Expr):
|
||||
"""An internal name in the compiler. You cannot create these nodes
|
||||
yourself but the parser provides a
|
||||
:meth:`~jinja2.parser.Parser.free_identifier` method that creates
|
||||
a new identifier for you. This identifier is not available from the
|
||||
template and is not threated specially by the compiler.
|
||||
"""
|
||||
fields = ('name',)
|
||||
|
||||
def __init__(self):
|
||||
raise TypeError('Can\'t create internal names. Use the '
|
||||
'`free_identifier` method on a parser.')
|
||||
|
||||
|
||||
class MarkSafe(Expr):
|
||||
"""Mark the wrapped expression as safe (wrap it as `Markup`)."""
|
||||
fields = ('expr',)
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
return Markup(self.expr.as_const(eval_ctx))
|
||||
|
||||
|
||||
class MarkSafeIfAutoescape(Expr):
|
||||
"""Mark the wrapped expression as safe (wrap it as `Markup`) but
|
||||
only if autoescaping is active.
|
||||
|
||||
.. versionadded:: 2.5
|
||||
"""
|
||||
fields = ('expr',)
|
||||
|
||||
def as_const(self, eval_ctx=None):
|
||||
eval_ctx = get_eval_context(self, eval_ctx)
|
||||
if eval_ctx.volatile:
|
||||
raise Impossible()
|
||||
expr = self.expr.as_const(eval_ctx)
|
||||
if eval_ctx.autoescape:
|
||||
return Markup(expr)
|
||||
return expr
|
||||
|
||||
|
||||
class ContextReference(Expr):
|
||||
"""Returns the current template context. It can be used like a
|
||||
:class:`Name` node, with a ``'load'`` ctx and will return the
|
||||
current :class:`~jinja2.runtime.Context` object.
|
||||
|
||||
Here an example that assigns the current template name to a
|
||||
variable named `foo`::
|
||||
|
||||
Assign(Name('foo', ctx='store'),
|
||||
Getattr(ContextReference(), 'name'))
|
||||
"""
|
||||
|
||||
|
||||
class Continue(Stmt):
|
||||
"""Continue a loop."""
|
||||
|
||||
|
||||
class Break(Stmt):
|
||||
"""Break a loop."""
|
||||
|
||||
|
||||
class Scope(Stmt):
|
||||
"""An artificial scope."""
|
||||
fields = ('body',)
|
||||
|
||||
|
||||
class EvalContextModifier(Stmt):
|
||||
"""Modifies the eval context. For each option that should be modified,
|
||||
a :class:`Keyword` has to be added to the :attr:`options` list.
|
||||
|
||||
Example to change the `autoescape` setting::
|
||||
|
||||
EvalContextModifier(options=[Keyword('autoescape', Const(True))])
|
||||
"""
|
||||
fields = ('options',)
|
||||
|
||||
|
||||
class ScopedEvalContextModifier(EvalContextModifier):
|
||||
"""Modifies the eval context and reverts it later. Works exactly like
|
||||
:class:`EvalContextModifier` but will only modify the
|
||||
:class:`~jinja2.nodes.EvalContext` for nodes in the :attr:`body`.
|
||||
"""
|
||||
fields = ('body',)
|
||||
|
||||
|
||||
# make sure nobody creates custom nodes
|
||||
def _failing_new(*args, **kwargs):
|
||||
raise TypeError('can\'t create custom node types')
|
||||
NodeType.__new__ = staticmethod(_failing_new); del _failing_new
|
||||
Vendored
-49
@@ -1,49 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.optimizer
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The jinja optimizer is currently trying to constant fold a few expressions
|
||||
and modify the AST in place so that it should be easier to evaluate it.
|
||||
|
||||
Because the AST does not contain all the scoping information and the
|
||||
compiler has to find that out, we cannot do all the optimizations we
|
||||
want. For example loop unrolling doesn't work because unrolled loops would
|
||||
have a different scoping.
|
||||
|
||||
The solution would be a second syntax tree that has the scoping rules stored.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD.
|
||||
"""
|
||||
from jinja2 import nodes
|
||||
from jinja2.visitor import NodeTransformer
|
||||
|
||||
|
||||
def optimize(node, environment):
|
||||
"""The context hint can be used to perform an static optimization
|
||||
based on the context given."""
|
||||
optimizer = Optimizer(environment)
|
||||
return optimizer.visit(node)
|
||||
|
||||
|
||||
class Optimizer(NodeTransformer):
|
||||
|
||||
def __init__(self, environment):
|
||||
self.environment = environment
|
||||
|
||||
def fold(self, node, eval_ctx=None):
|
||||
"""Do constant folding."""
|
||||
node = self.generic_visit(node)
|
||||
try:
|
||||
return nodes.Const.from_untrusted(node.as_const(eval_ctx),
|
||||
lineno=node.lineno,
|
||||
environment=self.environment)
|
||||
except nodes.Impossible:
|
||||
return node
|
||||
|
||||
visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \
|
||||
visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \
|
||||
visit_Not = visit_Compare = visit_Getitem = visit_Getattr = visit_Call = \
|
||||
visit_Filter = visit_Test = visit_CondExpr = fold
|
||||
del fold
|
||||
Vendored
-898
@@ -1,898 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.parser
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Implements the template parser.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from jinja2 import nodes
|
||||
from jinja2.exceptions import TemplateSyntaxError, TemplateAssertionError
|
||||
from jinja2.lexer import describe_token, describe_token_expr
|
||||
from jinja2._compat import imap
|
||||
|
||||
|
||||
_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print',
|
||||
'macro', 'include', 'from', 'import',
|
||||
'set', 'with', 'autoescape'])
|
||||
_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq'])
|
||||
|
||||
_math_nodes = {
|
||||
'add': nodes.Add,
|
||||
'sub': nodes.Sub,
|
||||
'mul': nodes.Mul,
|
||||
'div': nodes.Div,
|
||||
'floordiv': nodes.FloorDiv,
|
||||
'mod': nodes.Mod,
|
||||
}
|
||||
|
||||
|
||||
class Parser(object):
|
||||
"""This is the central parsing class Jinja2 uses. It's passed to
|
||||
extensions and can be used to parse expressions or statements.
|
||||
"""
|
||||
|
||||
def __init__(self, environment, source, name=None, filename=None,
|
||||
state=None):
|
||||
self.environment = environment
|
||||
self.stream = environment._tokenize(source, name, filename, state)
|
||||
self.name = name
|
||||
self.filename = filename
|
||||
self.closed = False
|
||||
self.extensions = {}
|
||||
for extension in environment.iter_extensions():
|
||||
for tag in extension.tags:
|
||||
self.extensions[tag] = extension.parse
|
||||
self._last_identifier = 0
|
||||
self._tag_stack = []
|
||||
self._end_token_stack = []
|
||||
|
||||
def fail(self, msg, lineno=None, exc=TemplateSyntaxError):
|
||||
"""Convenience method that raises `exc` with the message, passed
|
||||
line number or last line number as well as the current name and
|
||||
filename.
|
||||
"""
|
||||
if lineno is None:
|
||||
lineno = self.stream.current.lineno
|
||||
raise exc(msg, lineno, self.name, self.filename)
|
||||
|
||||
def _fail_ut_eof(self, name, end_token_stack, lineno):
|
||||
expected = []
|
||||
for exprs in end_token_stack:
|
||||
expected.extend(imap(describe_token_expr, exprs))
|
||||
if end_token_stack:
|
||||
currently_looking = ' or '.join(
|
||||
"'%s'" % describe_token_expr(expr)
|
||||
for expr in end_token_stack[-1])
|
||||
else:
|
||||
currently_looking = None
|
||||
|
||||
if name is None:
|
||||
message = ['Unexpected end of template.']
|
||||
else:
|
||||
message = ['Encountered unknown tag \'%s\'.' % name]
|
||||
|
||||
if currently_looking:
|
||||
if name is not None and name in expected:
|
||||
message.append('You probably made a nesting mistake. Jinja '
|
||||
'is expecting this tag, but currently looking '
|
||||
'for %s.' % currently_looking)
|
||||
else:
|
||||
message.append('Jinja was looking for the following tags: '
|
||||
'%s.' % currently_looking)
|
||||
|
||||
if self._tag_stack:
|
||||
message.append('The innermost block that needs to be '
|
||||
'closed is \'%s\'.' % self._tag_stack[-1])
|
||||
|
||||
self.fail(' '.join(message), lineno)
|
||||
|
||||
def fail_unknown_tag(self, name, lineno=None):
|
||||
"""Called if the parser encounters an unknown tag. Tries to fail
|
||||
with a human readable error message that could help to identify
|
||||
the problem.
|
||||
"""
|
||||
return self._fail_ut_eof(name, self._end_token_stack, lineno)
|
||||
|
||||
def fail_eof(self, end_tokens=None, lineno=None):
|
||||
"""Like fail_unknown_tag but for end of template situations."""
|
||||
stack = list(self._end_token_stack)
|
||||
if end_tokens is not None:
|
||||
stack.append(end_tokens)
|
||||
return self._fail_ut_eof(None, stack, lineno)
|
||||
|
||||
def is_tuple_end(self, extra_end_rules=None):
|
||||
"""Are we at the end of a tuple?"""
|
||||
if self.stream.current.type in ('variable_end', 'block_end', 'rparen'):
|
||||
return True
|
||||
elif extra_end_rules is not None:
|
||||
return self.stream.current.test_any(extra_end_rules)
|
||||
return False
|
||||
|
||||
def free_identifier(self, lineno=None):
|
||||
"""Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
|
||||
self._last_identifier += 1
|
||||
rv = object.__new__(nodes.InternalName)
|
||||
nodes.Node.__init__(rv, 'fi%d' % self._last_identifier, lineno=lineno)
|
||||
return rv
|
||||
|
||||
def parse_statement(self):
|
||||
"""Parse a single statement."""
|
||||
token = self.stream.current
|
||||
if token.type != 'name':
|
||||
self.fail('tag name expected', token.lineno)
|
||||
self._tag_stack.append(token.value)
|
||||
pop_tag = True
|
||||
try:
|
||||
if token.value in _statement_keywords:
|
||||
return getattr(self, 'parse_' + self.stream.current.value)()
|
||||
if token.value == 'call':
|
||||
return self.parse_call_block()
|
||||
if token.value == 'filter':
|
||||
return self.parse_filter_block()
|
||||
ext = self.extensions.get(token.value)
|
||||
if ext is not None:
|
||||
return ext(self)
|
||||
|
||||
# did not work out, remove the token we pushed by accident
|
||||
# from the stack so that the unknown tag fail function can
|
||||
# produce a proper error message.
|
||||
self._tag_stack.pop()
|
||||
pop_tag = False
|
||||
self.fail_unknown_tag(token.value, token.lineno)
|
||||
finally:
|
||||
if pop_tag:
|
||||
self._tag_stack.pop()
|
||||
|
||||
def parse_statements(self, end_tokens, drop_needle=False):
|
||||
"""Parse multiple statements into a list until one of the end tokens
|
||||
is reached. This is used to parse the body of statements as it also
|
||||
parses template data if appropriate. The parser checks first if the
|
||||
current token is a colon and skips it if there is one. Then it checks
|
||||
for the block end and parses until if one of the `end_tokens` is
|
||||
reached. Per default the active token in the stream at the end of
|
||||
the call is the matched end token. If this is not wanted `drop_needle`
|
||||
can be set to `True` and the end token is removed.
|
||||
"""
|
||||
# the first token may be a colon for python compatibility
|
||||
self.stream.skip_if('colon')
|
||||
|
||||
# in the future it would be possible to add whole code sections
|
||||
# by adding some sort of end of statement token and parsing those here.
|
||||
self.stream.expect('block_end')
|
||||
result = self.subparse(end_tokens)
|
||||
|
||||
# we reached the end of the template too early, the subparser
|
||||
# does not check for this, so we do that now
|
||||
if self.stream.current.type == 'eof':
|
||||
self.fail_eof(end_tokens)
|
||||
|
||||
if drop_needle:
|
||||
next(self.stream)
|
||||
return result
|
||||
|
||||
def parse_set(self):
|
||||
"""Parse an assign statement."""
|
||||
lineno = next(self.stream).lineno
|
||||
target = self.parse_assign_target()
|
||||
if self.stream.skip_if('assign'):
|
||||
expr = self.parse_tuple()
|
||||
return nodes.Assign(target, expr, lineno=lineno)
|
||||
body = self.parse_statements(('name:endset',),
|
||||
drop_needle=True)
|
||||
return nodes.AssignBlock(target, body, lineno=lineno)
|
||||
|
||||
def parse_for(self):
|
||||
"""Parse a for loop."""
|
||||
lineno = self.stream.expect('name:for').lineno
|
||||
target = self.parse_assign_target(extra_end_rules=('name:in',))
|
||||
self.stream.expect('name:in')
|
||||
iter = self.parse_tuple(with_condexpr=False,
|
||||
extra_end_rules=('name:recursive',))
|
||||
test = None
|
||||
if self.stream.skip_if('name:if'):
|
||||
test = self.parse_expression()
|
||||
recursive = self.stream.skip_if('name:recursive')
|
||||
body = self.parse_statements(('name:endfor', 'name:else'))
|
||||
if next(self.stream).value == 'endfor':
|
||||
else_ = []
|
||||
else:
|
||||
else_ = self.parse_statements(('name:endfor',), drop_needle=True)
|
||||
return nodes.For(target, iter, body, else_, test,
|
||||
recursive, lineno=lineno)
|
||||
|
||||
def parse_if(self):
|
||||
"""Parse an if construct."""
|
||||
node = result = nodes.If(lineno=self.stream.expect('name:if').lineno)
|
||||
while 1:
|
||||
node.test = self.parse_tuple(with_condexpr=False)
|
||||
node.body = self.parse_statements(('name:elif', 'name:else',
|
||||
'name:endif'))
|
||||
token = next(self.stream)
|
||||
if token.test('name:elif'):
|
||||
new_node = nodes.If(lineno=self.stream.current.lineno)
|
||||
node.else_ = [new_node]
|
||||
node = new_node
|
||||
continue
|
||||
elif token.test('name:else'):
|
||||
node.else_ = self.parse_statements(('name:endif',),
|
||||
drop_needle=True)
|
||||
else:
|
||||
node.else_ = []
|
||||
break
|
||||
return result
|
||||
|
||||
def parse_with(self):
|
||||
node = nodes.With(lineno=next(self.stream).lineno)
|
||||
targets = []
|
||||
values = []
|
||||
while self.stream.current.type != 'block_end':
|
||||
lineno = self.stream.current.lineno
|
||||
if targets:
|
||||
self.stream.expect('comma')
|
||||
target = self.parse_assign_target()
|
||||
target.set_ctx('param')
|
||||
targets.append(target)
|
||||
self.stream.expect('assign')
|
||||
values.append(self.parse_expression())
|
||||
node.targets = targets
|
||||
node.values = values
|
||||
node.body = self.parse_statements(('name:endwith',),
|
||||
drop_needle=True)
|
||||
return node
|
||||
|
||||
def parse_autoescape(self):
|
||||
node = nodes.ScopedEvalContextModifier(lineno=next(self.stream).lineno)
|
||||
node.options = [
|
||||
nodes.Keyword('autoescape', self.parse_expression())
|
||||
]
|
||||
node.body = self.parse_statements(('name:endautoescape',),
|
||||
drop_needle=True)
|
||||
return nodes.Scope([node])
|
||||
|
||||
def parse_block(self):
|
||||
node = nodes.Block(lineno=next(self.stream).lineno)
|
||||
node.name = self.stream.expect('name').value
|
||||
node.scoped = self.stream.skip_if('name:scoped')
|
||||
|
||||
# common problem people encounter when switching from django
|
||||
# to jinja. we do not support hyphens in block names, so let's
|
||||
# raise a nicer error message in that case.
|
||||
if self.stream.current.type == 'sub':
|
||||
self.fail('Block names in Jinja have to be valid Python '
|
||||
'identifiers and may not contain hyphens, use an '
|
||||
'underscore instead.')
|
||||
|
||||
node.body = self.parse_statements(('name:endblock',), drop_needle=True)
|
||||
self.stream.skip_if('name:' + node.name)
|
||||
return node
|
||||
|
||||
def parse_extends(self):
|
||||
node = nodes.Extends(lineno=next(self.stream).lineno)
|
||||
node.template = self.parse_expression()
|
||||
return node
|
||||
|
||||
def parse_import_context(self, node, default):
|
||||
if self.stream.current.test_any('name:with', 'name:without') and \
|
||||
self.stream.look().test('name:context'):
|
||||
node.with_context = next(self.stream).value == 'with'
|
||||
self.stream.skip()
|
||||
else:
|
||||
node.with_context = default
|
||||
return node
|
||||
|
||||
def parse_include(self):
|
||||
node = nodes.Include(lineno=next(self.stream).lineno)
|
||||
node.template = self.parse_expression()
|
||||
if self.stream.current.test('name:ignore') and \
|
||||
self.stream.look().test('name:missing'):
|
||||
node.ignore_missing = True
|
||||
self.stream.skip(2)
|
||||
else:
|
||||
node.ignore_missing = False
|
||||
return self.parse_import_context(node, True)
|
||||
|
||||
def parse_import(self):
|
||||
node = nodes.Import(lineno=next(self.stream).lineno)
|
||||
node.template = self.parse_expression()
|
||||
self.stream.expect('name:as')
|
||||
node.target = self.parse_assign_target(name_only=True).name
|
||||
return self.parse_import_context(node, False)
|
||||
|
||||
def parse_from(self):
|
||||
node = nodes.FromImport(lineno=next(self.stream).lineno)
|
||||
node.template = self.parse_expression()
|
||||
self.stream.expect('name:import')
|
||||
node.names = []
|
||||
|
||||
def parse_context():
|
||||
if self.stream.current.value in ('with', 'without') and \
|
||||
self.stream.look().test('name:context'):
|
||||
node.with_context = next(self.stream).value == 'with'
|
||||
self.stream.skip()
|
||||
return True
|
||||
return False
|
||||
|
||||
while 1:
|
||||
if node.names:
|
||||
self.stream.expect('comma')
|
||||
if self.stream.current.type == 'name':
|
||||
if parse_context():
|
||||
break
|
||||
target = self.parse_assign_target(name_only=True)
|
||||
if target.name.startswith('_'):
|
||||
self.fail('names starting with an underline can not '
|
||||
'be imported', target.lineno,
|
||||
exc=TemplateAssertionError)
|
||||
if self.stream.skip_if('name:as'):
|
||||
alias = self.parse_assign_target(name_only=True)
|
||||
node.names.append((target.name, alias.name))
|
||||
else:
|
||||
node.names.append(target.name)
|
||||
if parse_context() or self.stream.current.type != 'comma':
|
||||
break
|
||||
else:
|
||||
break
|
||||
if not hasattr(node, 'with_context'):
|
||||
node.with_context = False
|
||||
self.stream.skip_if('comma')
|
||||
return node
|
||||
|
||||
def parse_signature(self, node):
|
||||
node.args = args = []
|
||||
node.defaults = defaults = []
|
||||
self.stream.expect('lparen')
|
||||
while self.stream.current.type != 'rparen':
|
||||
if args:
|
||||
self.stream.expect('comma')
|
||||
arg = self.parse_assign_target(name_only=True)
|
||||
arg.set_ctx('param')
|
||||
if self.stream.skip_if('assign'):
|
||||
defaults.append(self.parse_expression())
|
||||
elif defaults:
|
||||
self.fail('non-default argument follows default argument')
|
||||
args.append(arg)
|
||||
self.stream.expect('rparen')
|
||||
|
||||
def parse_call_block(self):
|
||||
node = nodes.CallBlock(lineno=next(self.stream).lineno)
|
||||
if self.stream.current.type == 'lparen':
|
||||
self.parse_signature(node)
|
||||
else:
|
||||
node.args = []
|
||||
node.defaults = []
|
||||
|
||||
node.call = self.parse_expression()
|
||||
if not isinstance(node.call, nodes.Call):
|
||||
self.fail('expected call', node.lineno)
|
||||
node.body = self.parse_statements(('name:endcall',), drop_needle=True)
|
||||
return node
|
||||
|
||||
def parse_filter_block(self):
|
||||
node = nodes.FilterBlock(lineno=next(self.stream).lineno)
|
||||
node.filter = self.parse_filter(None, start_inline=True)
|
||||
node.body = self.parse_statements(('name:endfilter',),
|
||||
drop_needle=True)
|
||||
return node
|
||||
|
||||
def parse_macro(self):
|
||||
node = nodes.Macro(lineno=next(self.stream).lineno)
|
||||
node.name = self.parse_assign_target(name_only=True).name
|
||||
self.parse_signature(node)
|
||||
node.body = self.parse_statements(('name:endmacro',),
|
||||
drop_needle=True)
|
||||
return node
|
||||
|
||||
def parse_print(self):
|
||||
node = nodes.Output(lineno=next(self.stream).lineno)
|
||||
node.nodes = []
|
||||
while self.stream.current.type != 'block_end':
|
||||
if node.nodes:
|
||||
self.stream.expect('comma')
|
||||
node.nodes.append(self.parse_expression())
|
||||
return node
|
||||
|
||||
def parse_assign_target(self, with_tuple=True, name_only=False,
|
||||
extra_end_rules=None):
|
||||
"""Parse an assignment target. As Jinja2 allows assignments to
|
||||
tuples, this function can parse all allowed assignment targets. Per
|
||||
default assignments to tuples are parsed, that can be disable however
|
||||
by setting `with_tuple` to `False`. If only assignments to names are
|
||||
wanted `name_only` can be set to `True`. The `extra_end_rules`
|
||||
parameter is forwarded to the tuple parsing function.
|
||||
"""
|
||||
if name_only:
|
||||
token = self.stream.expect('name')
|
||||
target = nodes.Name(token.value, 'store', lineno=token.lineno)
|
||||
else:
|
||||
if with_tuple:
|
||||
target = self.parse_tuple(simplified=True,
|
||||
extra_end_rules=extra_end_rules)
|
||||
else:
|
||||
target = self.parse_primary()
|
||||
target.set_ctx('store')
|
||||
if not target.can_assign():
|
||||
self.fail('can\'t assign to %r' % target.__class__.
|
||||
__name__.lower(), target.lineno)
|
||||
return target
|
||||
|
||||
def parse_expression(self, with_condexpr=True):
|
||||
"""Parse an expression. Per default all expressions are parsed, if
|
||||
the optional `with_condexpr` parameter is set to `False` conditional
|
||||
expressions are not parsed.
|
||||
"""
|
||||
if with_condexpr:
|
||||
return self.parse_condexpr()
|
||||
return self.parse_or()
|
||||
|
||||
def parse_condexpr(self):
|
||||
lineno = self.stream.current.lineno
|
||||
expr1 = self.parse_or()
|
||||
while self.stream.skip_if('name:if'):
|
||||
expr2 = self.parse_or()
|
||||
if self.stream.skip_if('name:else'):
|
||||
expr3 = self.parse_condexpr()
|
||||
else:
|
||||
expr3 = None
|
||||
expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
|
||||
lineno = self.stream.current.lineno
|
||||
return expr1
|
||||
|
||||
def parse_or(self):
|
||||
lineno = self.stream.current.lineno
|
||||
left = self.parse_and()
|
||||
while self.stream.skip_if('name:or'):
|
||||
right = self.parse_and()
|
||||
left = nodes.Or(left, right, lineno=lineno)
|
||||
lineno = self.stream.current.lineno
|
||||
return left
|
||||
|
||||
def parse_and(self):
|
||||
lineno = self.stream.current.lineno
|
||||
left = self.parse_not()
|
||||
while self.stream.skip_if('name:and'):
|
||||
right = self.parse_not()
|
||||
left = nodes.And(left, right, lineno=lineno)
|
||||
lineno = self.stream.current.lineno
|
||||
return left
|
||||
|
||||
def parse_not(self):
|
||||
if self.stream.current.test('name:not'):
|
||||
lineno = next(self.stream).lineno
|
||||
return nodes.Not(self.parse_not(), lineno=lineno)
|
||||
return self.parse_compare()
|
||||
|
||||
def parse_compare(self):
|
||||
lineno = self.stream.current.lineno
|
||||
expr = self.parse_math1()
|
||||
ops = []
|
||||
while 1:
|
||||
token_type = self.stream.current.type
|
||||
if token_type in _compare_operators:
|
||||
next(self.stream)
|
||||
ops.append(nodes.Operand(token_type, self.parse_math1()))
|
||||
elif self.stream.skip_if('name:in'):
|
||||
ops.append(nodes.Operand('in', self.parse_math1()))
|
||||
elif (self.stream.current.test('name:not') and
|
||||
self.stream.look().test('name:in')):
|
||||
self.stream.skip(2)
|
||||
ops.append(nodes.Operand('notin', self.parse_math1()))
|
||||
else:
|
||||
break
|
||||
lineno = self.stream.current.lineno
|
||||
if not ops:
|
||||
return expr
|
||||
return nodes.Compare(expr, ops, lineno=lineno)
|
||||
|
||||
def parse_math1(self):
|
||||
lineno = self.stream.current.lineno
|
||||
left = self.parse_concat()
|
||||
while self.stream.current.type in ('add', 'sub'):
|
||||
cls = _math_nodes[self.stream.current.type]
|
||||
next(self.stream)
|
||||
right = self.parse_concat()
|
||||
left = cls(left, right, lineno=lineno)
|
||||
lineno = self.stream.current.lineno
|
||||
return left
|
||||
|
||||
def parse_concat(self):
|
||||
lineno = self.stream.current.lineno
|
||||
args = [self.parse_math2()]
|
||||
while self.stream.current.type == 'tilde':
|
||||
next(self.stream)
|
||||
args.append(self.parse_math2())
|
||||
if len(args) == 1:
|
||||
return args[0]
|
||||
return nodes.Concat(args, lineno=lineno)
|
||||
|
||||
def parse_math2(self):
|
||||
lineno = self.stream.current.lineno
|
||||
left = self.parse_pow()
|
||||
while self.stream.current.type in ('mul', 'div', 'floordiv', 'mod'):
|
||||
cls = _math_nodes[self.stream.current.type]
|
||||
next(self.stream)
|
||||
right = self.parse_pow()
|
||||
left = cls(left, right, lineno=lineno)
|
||||
lineno = self.stream.current.lineno
|
||||
return left
|
||||
|
||||
def parse_pow(self):
|
||||
lineno = self.stream.current.lineno
|
||||
left = self.parse_unary()
|
||||
while self.stream.current.type == 'pow':
|
||||
next(self.stream)
|
||||
right = self.parse_unary()
|
||||
left = nodes.Pow(left, right, lineno=lineno)
|
||||
lineno = self.stream.current.lineno
|
||||
return left
|
||||
|
||||
def parse_unary(self, with_filter=True):
|
||||
token_type = self.stream.current.type
|
||||
lineno = self.stream.current.lineno
|
||||
if token_type == 'sub':
|
||||
next(self.stream)
|
||||
node = nodes.Neg(self.parse_unary(False), lineno=lineno)
|
||||
elif token_type == 'add':
|
||||
next(self.stream)
|
||||
node = nodes.Pos(self.parse_unary(False), lineno=lineno)
|
||||
else:
|
||||
node = self.parse_primary()
|
||||
node = self.parse_postfix(node)
|
||||
if with_filter:
|
||||
node = self.parse_filter_expr(node)
|
||||
return node
|
||||
|
||||
def parse_primary(self):
|
||||
token = self.stream.current
|
||||
if token.type == 'name':
|
||||
if token.value in ('true', 'false', 'True', 'False'):
|
||||
node = nodes.Const(token.value in ('true', 'True'),
|
||||
lineno=token.lineno)
|
||||
elif token.value in ('none', 'None'):
|
||||
node = nodes.Const(None, lineno=token.lineno)
|
||||
else:
|
||||
node = nodes.Name(token.value, 'load', lineno=token.lineno)
|
||||
next(self.stream)
|
||||
elif token.type == 'string':
|
||||
next(self.stream)
|
||||
buf = [token.value]
|
||||
lineno = token.lineno
|
||||
while self.stream.current.type == 'string':
|
||||
buf.append(self.stream.current.value)
|
||||
next(self.stream)
|
||||
node = nodes.Const(''.join(buf), lineno=lineno)
|
||||
elif token.type in ('integer', 'float'):
|
||||
next(self.stream)
|
||||
node = nodes.Const(token.value, lineno=token.lineno)
|
||||
elif token.type == 'lparen':
|
||||
next(self.stream)
|
||||
node = self.parse_tuple(explicit_parentheses=True)
|
||||
self.stream.expect('rparen')
|
||||
elif token.type == 'lbracket':
|
||||
node = self.parse_list()
|
||||
elif token.type == 'lbrace':
|
||||
node = self.parse_dict()
|
||||
else:
|
||||
self.fail("unexpected '%s'" % describe_token(token), token.lineno)
|
||||
return node
|
||||
|
||||
def parse_tuple(self, simplified=False, with_condexpr=True,
|
||||
extra_end_rules=None, explicit_parentheses=False):
|
||||
"""Works like `parse_expression` but if multiple expressions are
|
||||
delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
|
||||
This method could also return a regular expression instead of a tuple
|
||||
if no commas where found.
|
||||
|
||||
The default parsing mode is a full tuple. If `simplified` is `True`
|
||||
only names and literals are parsed. The `no_condexpr` parameter is
|
||||
forwarded to :meth:`parse_expression`.
|
||||
|
||||
Because tuples do not require delimiters and may end in a bogus comma
|
||||
an extra hint is needed that marks the end of a tuple. For example
|
||||
for loops support tuples between `for` and `in`. In that case the
|
||||
`extra_end_rules` is set to ``['name:in']``.
|
||||
|
||||
`explicit_parentheses` is true if the parsing was triggered by an
|
||||
expression in parentheses. This is used to figure out if an empty
|
||||
tuple is a valid expression or not.
|
||||
"""
|
||||
lineno = self.stream.current.lineno
|
||||
if simplified:
|
||||
parse = self.parse_primary
|
||||
elif with_condexpr:
|
||||
parse = self.parse_expression
|
||||
else:
|
||||
parse = lambda: self.parse_expression(with_condexpr=False)
|
||||
args = []
|
||||
is_tuple = False
|
||||
while 1:
|
||||
if args:
|
||||
self.stream.expect('comma')
|
||||
if self.is_tuple_end(extra_end_rules):
|
||||
break
|
||||
args.append(parse())
|
||||
if self.stream.current.type == 'comma':
|
||||
is_tuple = True
|
||||
else:
|
||||
break
|
||||
lineno = self.stream.current.lineno
|
||||
|
||||
if not is_tuple:
|
||||
if args:
|
||||
return args[0]
|
||||
|
||||
# if we don't have explicit parentheses, an empty tuple is
|
||||
# not a valid expression. This would mean nothing (literally
|
||||
# nothing) in the spot of an expression would be an empty
|
||||
# tuple.
|
||||
if not explicit_parentheses:
|
||||
self.fail('Expected an expression, got \'%s\'' %
|
||||
describe_token(self.stream.current))
|
||||
|
||||
return nodes.Tuple(args, 'load', lineno=lineno)
|
||||
|
||||
def parse_list(self):
|
||||
token = self.stream.expect('lbracket')
|
||||
items = []
|
||||
while self.stream.current.type != 'rbracket':
|
||||
if items:
|
||||
self.stream.expect('comma')
|
||||
if self.stream.current.type == 'rbracket':
|
||||
break
|
||||
items.append(self.parse_expression())
|
||||
self.stream.expect('rbracket')
|
||||
return nodes.List(items, lineno=token.lineno)
|
||||
|
||||
def parse_dict(self):
|
||||
token = self.stream.expect('lbrace')
|
||||
items = []
|
||||
while self.stream.current.type != 'rbrace':
|
||||
if items:
|
||||
self.stream.expect('comma')
|
||||
if self.stream.current.type == 'rbrace':
|
||||
break
|
||||
key = self.parse_expression()
|
||||
self.stream.expect('colon')
|
||||
value = self.parse_expression()
|
||||
items.append(nodes.Pair(key, value, lineno=key.lineno))
|
||||
self.stream.expect('rbrace')
|
||||
return nodes.Dict(items, lineno=token.lineno)
|
||||
|
||||
def parse_postfix(self, node):
|
||||
while 1:
|
||||
token_type = self.stream.current.type
|
||||
if token_type == 'dot' or token_type == 'lbracket':
|
||||
node = self.parse_subscript(node)
|
||||
# calls are valid both after postfix expressions (getattr
|
||||
# and getitem) as well as filters and tests
|
||||
elif token_type == 'lparen':
|
||||
node = self.parse_call(node)
|
||||
else:
|
||||
break
|
||||
return node
|
||||
|
||||
def parse_filter_expr(self, node):
|
||||
while 1:
|
||||
token_type = self.stream.current.type
|
||||
if token_type == 'pipe':
|
||||
node = self.parse_filter(node)
|
||||
elif token_type == 'name' and self.stream.current.value == 'is':
|
||||
node = self.parse_test(node)
|
||||
# calls are valid both after postfix expressions (getattr
|
||||
# and getitem) as well as filters and tests
|
||||
elif token_type == 'lparen':
|
||||
node = self.parse_call(node)
|
||||
else:
|
||||
break
|
||||
return node
|
||||
|
||||
def parse_subscript(self, node):
|
||||
token = next(self.stream)
|
||||
if token.type == 'dot':
|
||||
attr_token = self.stream.current
|
||||
next(self.stream)
|
||||
if attr_token.type == 'name':
|
||||
return nodes.Getattr(node, attr_token.value, 'load',
|
||||
lineno=token.lineno)
|
||||
elif attr_token.type != 'integer':
|
||||
self.fail('expected name or number', attr_token.lineno)
|
||||
arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
|
||||
return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
|
||||
if token.type == 'lbracket':
|
||||
args = []
|
||||
while self.stream.current.type != 'rbracket':
|
||||
if args:
|
||||
self.stream.expect('comma')
|
||||
args.append(self.parse_subscribed())
|
||||
self.stream.expect('rbracket')
|
||||
if len(args) == 1:
|
||||
arg = args[0]
|
||||
else:
|
||||
arg = nodes.Tuple(args, 'load', lineno=token.lineno)
|
||||
return nodes.Getitem(node, arg, 'load', lineno=token.lineno)
|
||||
self.fail('expected subscript expression', self.lineno)
|
||||
|
||||
def parse_subscribed(self):
|
||||
lineno = self.stream.current.lineno
|
||||
|
||||
if self.stream.current.type == 'colon':
|
||||
next(self.stream)
|
||||
args = [None]
|
||||
else:
|
||||
node = self.parse_expression()
|
||||
if self.stream.current.type != 'colon':
|
||||
return node
|
||||
next(self.stream)
|
||||
args = [node]
|
||||
|
||||
if self.stream.current.type == 'colon':
|
||||
args.append(None)
|
||||
elif self.stream.current.type not in ('rbracket', 'comma'):
|
||||
args.append(self.parse_expression())
|
||||
else:
|
||||
args.append(None)
|
||||
|
||||
if self.stream.current.type == 'colon':
|
||||
next(self.stream)
|
||||
if self.stream.current.type not in ('rbracket', 'comma'):
|
||||
args.append(self.parse_expression())
|
||||
else:
|
||||
args.append(None)
|
||||
else:
|
||||
args.append(None)
|
||||
|
||||
return nodes.Slice(lineno=lineno, *args)
|
||||
|
||||
def parse_call(self, node):
|
||||
token = self.stream.expect('lparen')
|
||||
args = []
|
||||
kwargs = []
|
||||
dyn_args = dyn_kwargs = None
|
||||
require_comma = False
|
||||
|
||||
def ensure(expr):
|
||||
if not expr:
|
||||
self.fail('invalid syntax for function call expression',
|
||||
token.lineno)
|
||||
|
||||
while self.stream.current.type != 'rparen':
|
||||
if require_comma:
|
||||
self.stream.expect('comma')
|
||||
# support for trailing comma
|
||||
if self.stream.current.type == 'rparen':
|
||||
break
|
||||
if self.stream.current.type == 'mul':
|
||||
ensure(dyn_args is None and dyn_kwargs is None)
|
||||
next(self.stream)
|
||||
dyn_args = self.parse_expression()
|
||||
elif self.stream.current.type == 'pow':
|
||||
ensure(dyn_kwargs is None)
|
||||
next(self.stream)
|
||||
dyn_kwargs = self.parse_expression()
|
||||
else:
|
||||
ensure(dyn_args is None and dyn_kwargs is None)
|
||||
if self.stream.current.type == 'name' and \
|
||||
self.stream.look().type == 'assign':
|
||||
key = self.stream.current.value
|
||||
self.stream.skip(2)
|
||||
value = self.parse_expression()
|
||||
kwargs.append(nodes.Keyword(key, value,
|
||||
lineno=value.lineno))
|
||||
else:
|
||||
ensure(not kwargs)
|
||||
args.append(self.parse_expression())
|
||||
|
||||
require_comma = True
|
||||
self.stream.expect('rparen')
|
||||
|
||||
if node is None:
|
||||
return args, kwargs, dyn_args, dyn_kwargs
|
||||
return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs,
|
||||
lineno=token.lineno)
|
||||
|
||||
def parse_filter(self, node, start_inline=False):
|
||||
while self.stream.current.type == 'pipe' or start_inline:
|
||||
if not start_inline:
|
||||
next(self.stream)
|
||||
token = self.stream.expect('name')
|
||||
name = token.value
|
||||
while self.stream.current.type == 'dot':
|
||||
next(self.stream)
|
||||
name += '.' + self.stream.expect('name').value
|
||||
if self.stream.current.type == 'lparen':
|
||||
args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
|
||||
else:
|
||||
args = []
|
||||
kwargs = []
|
||||
dyn_args = dyn_kwargs = None
|
||||
node = nodes.Filter(node, name, args, kwargs, dyn_args,
|
||||
dyn_kwargs, lineno=token.lineno)
|
||||
start_inline = False
|
||||
return node
|
||||
|
||||
def parse_test(self, node):
|
||||
token = next(self.stream)
|
||||
if self.stream.current.test('name:not'):
|
||||
next(self.stream)
|
||||
negated = True
|
||||
else:
|
||||
negated = False
|
||||
name = self.stream.expect('name').value
|
||||
while self.stream.current.type == 'dot':
|
||||
next(self.stream)
|
||||
name += '.' + self.stream.expect('name').value
|
||||
dyn_args = dyn_kwargs = None
|
||||
kwargs = []
|
||||
if self.stream.current.type == 'lparen':
|
||||
args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None)
|
||||
elif (self.stream.current.type in ('name', 'string', 'integer',
|
||||
'float', 'lparen', 'lbracket',
|
||||
'lbrace') and not
|
||||
self.stream.current.test_any('name:else', 'name:or',
|
||||
'name:and')):
|
||||
if self.stream.current.test('name:is'):
|
||||
self.fail('You cannot chain multiple tests with is')
|
||||
args = [self.parse_primary()]
|
||||
else:
|
||||
args = []
|
||||
node = nodes.Test(node, name, args, kwargs, dyn_args,
|
||||
dyn_kwargs, lineno=token.lineno)
|
||||
if negated:
|
||||
node = nodes.Not(node, lineno=token.lineno)
|
||||
return node
|
||||
|
||||
def subparse(self, end_tokens=None):
|
||||
body = []
|
||||
data_buffer = []
|
||||
add_data = data_buffer.append
|
||||
|
||||
if end_tokens is not None:
|
||||
self._end_token_stack.append(end_tokens)
|
||||
|
||||
def flush_data():
|
||||
if data_buffer:
|
||||
lineno = data_buffer[0].lineno
|
||||
body.append(nodes.Output(data_buffer[:], lineno=lineno))
|
||||
del data_buffer[:]
|
||||
|
||||
try:
|
||||
while self.stream:
|
||||
token = self.stream.current
|
||||
if token.type == 'data':
|
||||
if token.value:
|
||||
add_data(nodes.TemplateData(token.value,
|
||||
lineno=token.lineno))
|
||||
next(self.stream)
|
||||
elif token.type == 'variable_begin':
|
||||
next(self.stream)
|
||||
add_data(self.parse_tuple(with_condexpr=True))
|
||||
self.stream.expect('variable_end')
|
||||
elif token.type == 'block_begin':
|
||||
flush_data()
|
||||
next(self.stream)
|
||||
if end_tokens is not None and \
|
||||
self.stream.current.test_any(*end_tokens):
|
||||
return body
|
||||
rv = self.parse_statement()
|
||||
if isinstance(rv, list):
|
||||
body.extend(rv)
|
||||
else:
|
||||
body.append(rv)
|
||||
self.stream.expect('block_end')
|
||||
else:
|
||||
raise AssertionError('internal parsing error')
|
||||
|
||||
flush_data()
|
||||
finally:
|
||||
if end_tokens is not None:
|
||||
self._end_token_stack.pop()
|
||||
|
||||
return body
|
||||
|
||||
def parse(self):
|
||||
"""Parse the whole template into a `Template` node."""
|
||||
result = nodes.Template(self.subparse(), lineno=1)
|
||||
result.set_environment(self.environment)
|
||||
return result
|
||||
Vendored
-785
@@ -1,785 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.runtime
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Runtime helpers.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD.
|
||||
"""
|
||||
import sys
|
||||
|
||||
from itertools import chain
|
||||
from jinja2.nodes import EvalContext, _context_function_types
|
||||
from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \
|
||||
internalcode, object_type_repr, evalcontextfunction
|
||||
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
|
||||
TemplateNotFound
|
||||
from jinja2._compat import imap, text_type, iteritems, \
|
||||
implements_iterator, implements_to_string, string_types, PY2, \
|
||||
with_metaclass
|
||||
|
||||
|
||||
# these variables are exported to the template runtime
|
||||
__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
|
||||
'TemplateRuntimeError', 'missing', 'concat', 'escape',
|
||||
'markup_join', 'unicode_join', 'to_string', 'identity',
|
||||
'TemplateNotFound']
|
||||
|
||||
#: the name of the function that is used to convert something into
|
||||
#: a string. We can just use the text type here.
|
||||
to_string = text_type
|
||||
|
||||
#: the identity function. Useful for certain things in the environment
|
||||
identity = lambda x: x
|
||||
|
||||
_last_iteration = object()
|
||||
|
||||
|
||||
def markup_join(seq):
|
||||
"""Concatenation that escapes if necessary and converts to unicode."""
|
||||
buf = []
|
||||
iterator = imap(soft_unicode, seq)
|
||||
for arg in iterator:
|
||||
buf.append(arg)
|
||||
if hasattr(arg, '__html__'):
|
||||
return Markup(u'').join(chain(buf, iterator))
|
||||
return concat(buf)
|
||||
|
||||
|
||||
def unicode_join(seq):
|
||||
"""Simple args to unicode conversion and concatenation."""
|
||||
return concat(imap(text_type, seq))
|
||||
|
||||
|
||||
def new_context(environment, template_name, blocks, vars=None,
|
||||
shared=None, globals=None, locals=None):
|
||||
"""Internal helper to for context creation."""
|
||||
if vars is None:
|
||||
vars = {}
|
||||
if shared:
|
||||
parent = vars
|
||||
else:
|
||||
parent = dict(globals or (), **vars)
|
||||
if locals:
|
||||
# if the parent is shared a copy should be created because
|
||||
# we don't want to modify the dict passed
|
||||
if shared:
|
||||
parent = dict(parent)
|
||||
for key, value in iteritems(locals):
|
||||
if value is not missing:
|
||||
parent[key] = value
|
||||
return environment.context_class(environment, parent, template_name,
|
||||
blocks)
|
||||
|
||||
|
||||
class TemplateReference(object):
|
||||
"""The `self` in templates."""
|
||||
|
||||
def __init__(self, context):
|
||||
self.__context = context
|
||||
|
||||
def __getitem__(self, name):
|
||||
blocks = self.__context.blocks[name]
|
||||
return BlockReference(name, self.__context, blocks, 0)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r>' % (
|
||||
self.__class__.__name__,
|
||||
self.__context.name
|
||||
)
|
||||
|
||||
|
||||
def _get_func(x):
|
||||
return getattr(x, '__func__', x)
|
||||
|
||||
|
||||
class ContextMeta(type):
|
||||
|
||||
def __new__(cls, name, bases, d):
|
||||
rv = type.__new__(cls, name, bases, d)
|
||||
if bases == ():
|
||||
return rv
|
||||
|
||||
resolve = _get_func(rv.resolve)
|
||||
default_resolve = _get_func(Context.resolve)
|
||||
resolve_or_missing = _get_func(rv.resolve_or_missing)
|
||||
default_resolve_or_missing = _get_func(Context.resolve_or_missing)
|
||||
|
||||
# If we have a changed resolve but no changed default or missing
|
||||
# resolve we invert the call logic.
|
||||
if resolve is not default_resolve and \
|
||||
resolve_or_missing is default_resolve_or_missing:
|
||||
rv._legacy_resolve_mode = True
|
||||
elif resolve is default_resolve and \
|
||||
resolve_or_missing is default_resolve_or_missing:
|
||||
rv._fast_resolve_mode = True
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def resolve_or_missing(context, key, missing=missing):
|
||||
if key in context.vars:
|
||||
return context.vars[key]
|
||||
if key in context.parent:
|
||||
return context.parent[key]
|
||||
return missing
|
||||
|
||||
|
||||
class Context(with_metaclass(ContextMeta)):
|
||||
"""The template context holds the variables of a template. It stores the
|
||||
values passed to the template and also the names the template exports.
|
||||
Creating instances is neither supported nor useful as it's created
|
||||
automatically at various stages of the template evaluation and should not
|
||||
be created by hand.
|
||||
|
||||
The context is immutable. Modifications on :attr:`parent` **must not**
|
||||
happen and modifications on :attr:`vars` are allowed from generated
|
||||
template code only. Template filters and global functions marked as
|
||||
:func:`contextfunction`\\s get the active context passed as first argument
|
||||
and are allowed to access the context read-only.
|
||||
|
||||
The template context supports read only dict operations (`get`,
|
||||
`keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`,
|
||||
`__getitem__`, `__contains__`). Additionally there is a :meth:`resolve`
|
||||
method that doesn't fail with a `KeyError` but returns an
|
||||
:class:`Undefined` object for missing variables.
|
||||
"""
|
||||
# XXX: we want to eventually make this be a deprecation warning and
|
||||
# remove it.
|
||||
_legacy_resolve_mode = False
|
||||
_fast_resolve_mode = False
|
||||
|
||||
def __init__(self, environment, parent, name, blocks):
|
||||
self.parent = parent
|
||||
self.vars = {}
|
||||
self.environment = environment
|
||||
self.eval_ctx = EvalContext(self.environment, name)
|
||||
self.exported_vars = set()
|
||||
self.name = name
|
||||
|
||||
# create the initial mapping of blocks. Whenever template inheritance
|
||||
# takes place the runtime will update this mapping with the new blocks
|
||||
# from the template.
|
||||
self.blocks = dict((k, [v]) for k, v in iteritems(blocks))
|
||||
|
||||
# In case we detect the fast resolve mode we can set up an alias
|
||||
# here that bypasses the legacy code logic.
|
||||
if self._fast_resolve_mode:
|
||||
self.resolve_or_missing = resolve_or_missing
|
||||
|
||||
def super(self, name, current):
|
||||
"""Render a parent block."""
|
||||
try:
|
||||
blocks = self.blocks[name]
|
||||
index = blocks.index(current) + 1
|
||||
blocks[index]
|
||||
except LookupError:
|
||||
return self.environment.undefined('there is no parent block '
|
||||
'called %r.' % name,
|
||||
name='super')
|
||||
return BlockReference(name, self, blocks, index)
|
||||
|
||||
def get(self, key, default=None):
|
||||
"""Returns an item from the template context, if it doesn't exist
|
||||
`default` is returned.
|
||||
"""
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def resolve(self, key):
|
||||
"""Looks up a variable like `__getitem__` or `get` but returns an
|
||||
:class:`Undefined` object with the name of the name looked up.
|
||||
"""
|
||||
if self._legacy_resolve_mode:
|
||||
rv = resolve_or_missing(self, key)
|
||||
else:
|
||||
rv = self.resolve_or_missing(key)
|
||||
if rv is missing:
|
||||
return self.environment.undefined(name=key)
|
||||
return rv
|
||||
|
||||
def resolve_or_missing(self, key):
|
||||
"""Resolves a variable like :meth:`resolve` but returns the
|
||||
special `missing` value if it cannot be found.
|
||||
"""
|
||||
if self._legacy_resolve_mode:
|
||||
rv = self.resolve(key)
|
||||
if isinstance(rv, Undefined):
|
||||
rv = missing
|
||||
return rv
|
||||
return resolve_or_missing(self, key)
|
||||
|
||||
def get_exported(self):
|
||||
"""Get a new dict with the exported variables."""
|
||||
return dict((k, self.vars[k]) for k in self.exported_vars)
|
||||
|
||||
def get_all(self):
|
||||
"""Return the complete context as dict including the exported
|
||||
variables. For optimizations reasons this might not return an
|
||||
actual copy so be careful with using it.
|
||||
"""
|
||||
if not self.vars:
|
||||
return self.parent
|
||||
if not self.parent:
|
||||
return self.vars
|
||||
return dict(self.parent, **self.vars)
|
||||
|
||||
@internalcode
|
||||
def call(__self, __obj, *args, **kwargs):
|
||||
"""Call the callable with the arguments and keyword arguments
|
||||
provided but inject the active context or environment as first
|
||||
argument if the callable is a :func:`contextfunction` or
|
||||
:func:`environmentfunction`.
|
||||
"""
|
||||
if __debug__:
|
||||
__traceback_hide__ = True # noqa
|
||||
|
||||
# Allow callable classes to take a context
|
||||
fn = __obj.__call__
|
||||
for fn_type in ('contextfunction',
|
||||
'evalcontextfunction',
|
||||
'environmentfunction'):
|
||||
if hasattr(fn, fn_type):
|
||||
__obj = fn
|
||||
break
|
||||
|
||||
if isinstance(__obj, _context_function_types):
|
||||
if getattr(__obj, 'contextfunction', 0):
|
||||
args = (__self,) + args
|
||||
elif getattr(__obj, 'evalcontextfunction', 0):
|
||||
args = (__self.eval_ctx,) + args
|
||||
elif getattr(__obj, 'environmentfunction', 0):
|
||||
args = (__self.environment,) + args
|
||||
try:
|
||||
return __obj(*args, **kwargs)
|
||||
except StopIteration:
|
||||
return __self.environment.undefined('value was undefined because '
|
||||
'a callable raised a '
|
||||
'StopIteration exception')
|
||||
|
||||
def derived(self, locals=None):
|
||||
"""Internal helper function to create a derived context. This is
|
||||
used in situations where the system needs a new context in the same
|
||||
template that is independent.
|
||||
"""
|
||||
context = new_context(self.environment, self.name, {},
|
||||
self.get_all(), True, None, locals)
|
||||
context.eval_ctx = self.eval_ctx
|
||||
context.blocks.update((k, list(v)) for k, v in iteritems(self.blocks))
|
||||
return context
|
||||
|
||||
def _all(meth):
|
||||
proxy = lambda self: getattr(self.get_all(), meth)()
|
||||
proxy.__doc__ = getattr(dict, meth).__doc__
|
||||
proxy.__name__ = meth
|
||||
return proxy
|
||||
|
||||
keys = _all('keys')
|
||||
values = _all('values')
|
||||
items = _all('items')
|
||||
|
||||
# not available on python 3
|
||||
if PY2:
|
||||
iterkeys = _all('iterkeys')
|
||||
itervalues = _all('itervalues')
|
||||
iteritems = _all('iteritems')
|
||||
del _all
|
||||
|
||||
def __contains__(self, name):
|
||||
return name in self.vars or name in self.parent
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Lookup a variable or raise `KeyError` if the variable is
|
||||
undefined.
|
||||
"""
|
||||
item = self.resolve_or_missing(key)
|
||||
if item is missing:
|
||||
raise KeyError(key)
|
||||
return item
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s of %r>' % (
|
||||
self.__class__.__name__,
|
||||
repr(self.get_all()),
|
||||
self.name
|
||||
)
|
||||
|
||||
|
||||
# register the context as mapping if possible
|
||||
try:
|
||||
from collections import Mapping
|
||||
Mapping.register(Context)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class BlockReference(object):
|
||||
"""One block on a template reference."""
|
||||
|
||||
def __init__(self, name, context, stack, depth):
|
||||
self.name = name
|
||||
self._context = context
|
||||
self._stack = stack
|
||||
self._depth = depth
|
||||
|
||||
@property
|
||||
def super(self):
|
||||
"""Super the block."""
|
||||
if self._depth + 1 >= len(self._stack):
|
||||
return self._context.environment. \
|
||||
undefined('there is no parent block called %r.' %
|
||||
self.name, name='super')
|
||||
return BlockReference(self.name, self._context, self._stack,
|
||||
self._depth + 1)
|
||||
|
||||
@internalcode
|
||||
def __call__(self):
|
||||
rv = concat(self._stack[self._depth](self._context))
|
||||
if self._context.eval_ctx.autoescape:
|
||||
rv = Markup(rv)
|
||||
return rv
|
||||
|
||||
|
||||
class LoopContextBase(object):
|
||||
"""A loop context for dynamic iteration."""
|
||||
|
||||
_after = _last_iteration
|
||||
_length = None
|
||||
|
||||
def __init__(self, recurse=None, depth0=0):
|
||||
self._recurse = recurse
|
||||
self.index0 = -1
|
||||
self.depth0 = depth0
|
||||
|
||||
def cycle(self, *args):
|
||||
"""Cycles among the arguments with the current loop index."""
|
||||
if not args:
|
||||
raise TypeError('no items for cycling given')
|
||||
return args[self.index0 % len(args)]
|
||||
|
||||
first = property(lambda x: x.index0 == 0)
|
||||
last = property(lambda x: x._after is _last_iteration)
|
||||
index = property(lambda x: x.index0 + 1)
|
||||
revindex = property(lambda x: x.length - x.index0)
|
||||
revindex0 = property(lambda x: x.length - x.index)
|
||||
depth = property(lambda x: x.depth0 + 1)
|
||||
|
||||
def __len__(self):
|
||||
return self.length
|
||||
|
||||
@internalcode
|
||||
def loop(self, iterable):
|
||||
if self._recurse is None:
|
||||
raise TypeError('Tried to call non recursive loop. Maybe you '
|
||||
"forgot the 'recursive' modifier.")
|
||||
return self._recurse(iterable, self._recurse, self.depth0 + 1)
|
||||
|
||||
# a nifty trick to enhance the error message if someone tried to call
|
||||
# the the loop without or with too many arguments.
|
||||
__call__ = loop
|
||||
del loop
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r/%r>' % (
|
||||
self.__class__.__name__,
|
||||
self.index,
|
||||
self.length
|
||||
)
|
||||
|
||||
|
||||
class LoopContext(LoopContextBase):
|
||||
|
||||
def __init__(self, iterable, recurse=None, depth0=0):
|
||||
LoopContextBase.__init__(self, recurse, depth0)
|
||||
self._iterator = iter(iterable)
|
||||
|
||||
# try to get the length of the iterable early. This must be done
|
||||
# here because there are some broken iterators around where there
|
||||
# __len__ is the number of iterations left (i'm looking at your
|
||||
# listreverseiterator!).
|
||||
try:
|
||||
self._length = len(iterable)
|
||||
except (TypeError, AttributeError):
|
||||
self._length = None
|
||||
self._after = self._safe_next()
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
if self._length is None:
|
||||
# if was not possible to get the length of the iterator when
|
||||
# the loop context was created (ie: iterating over a generator)
|
||||
# we have to convert the iterable into a sequence and use the
|
||||
# length of that + the number of iterations so far.
|
||||
iterable = tuple(self._iterator)
|
||||
self._iterator = iter(iterable)
|
||||
iterations_done = self.index0 + 2
|
||||
self._length = len(iterable) + iterations_done
|
||||
return self._length
|
||||
|
||||
def __iter__(self):
|
||||
return LoopContextIterator(self)
|
||||
|
||||
def _safe_next(self):
|
||||
try:
|
||||
return next(self._iterator)
|
||||
except StopIteration:
|
||||
return _last_iteration
|
||||
|
||||
|
||||
@implements_iterator
|
||||
class LoopContextIterator(object):
|
||||
"""The iterator for a loop context."""
|
||||
__slots__ = ('context',)
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
ctx = self.context
|
||||
ctx.index0 += 1
|
||||
if ctx._after is _last_iteration:
|
||||
raise StopIteration()
|
||||
next_elem = ctx._after
|
||||
ctx._after = ctx._safe_next()
|
||||
return next_elem, ctx
|
||||
|
||||
|
||||
class Macro(object):
|
||||
"""Wraps a macro function."""
|
||||
|
||||
def __init__(self, environment, func, name, arguments,
|
||||
catch_kwargs, catch_varargs, caller,
|
||||
default_autoescape=None):
|
||||
self._environment = environment
|
||||
self._func = func
|
||||
self._argument_count = len(arguments)
|
||||
self.name = name
|
||||
self.arguments = arguments
|
||||
self.catch_kwargs = catch_kwargs
|
||||
self.catch_varargs = catch_varargs
|
||||
self.caller = caller
|
||||
self.explicit_caller = 'caller' in arguments
|
||||
if default_autoescape is None:
|
||||
default_autoescape = environment.autoescape
|
||||
self._default_autoescape = default_autoescape
|
||||
|
||||
@internalcode
|
||||
@evalcontextfunction
|
||||
def __call__(self, *args, **kwargs):
|
||||
# This requires a bit of explanation, In the past we used to
|
||||
# decide largely based on compile-time information if a macro is
|
||||
# safe or unsafe. While there was a volatile mode it was largely
|
||||
# unused for deciding on escaping. This turns out to be
|
||||
# problemtic for macros because if a macro is safe or not not so
|
||||
# much depends on the escape mode when it was defined but when it
|
||||
# was used.
|
||||
#
|
||||
# Because however we export macros from the module system and
|
||||
# there are historic callers that do not pass an eval context (and
|
||||
# will continue to not pass one), we need to perform an instance
|
||||
# check here.
|
||||
#
|
||||
# This is considered safe because an eval context is not a valid
|
||||
# argument to callables otherwise anwyays. Worst case here is
|
||||
# that if no eval context is passed we fall back to the compile
|
||||
# time autoescape flag.
|
||||
if args and isinstance(args[0], EvalContext):
|
||||
autoescape = args[0].autoescape
|
||||
args = args[1:]
|
||||
else:
|
||||
autoescape = self._default_autoescape
|
||||
|
||||
# try to consume the positional arguments
|
||||
arguments = list(args[:self._argument_count])
|
||||
off = len(arguments)
|
||||
|
||||
# For information why this is necessary refer to the handling
|
||||
# of caller in the `macro_body` handler in the compiler.
|
||||
found_caller = False
|
||||
|
||||
# if the number of arguments consumed is not the number of
|
||||
# arguments expected we start filling in keyword arguments
|
||||
# and defaults.
|
||||
if off != self._argument_count:
|
||||
for idx, name in enumerate(self.arguments[len(arguments):]):
|
||||
try:
|
||||
value = kwargs.pop(name)
|
||||
except KeyError:
|
||||
value = missing
|
||||
if name == 'caller':
|
||||
found_caller = True
|
||||
arguments.append(value)
|
||||
else:
|
||||
found_caller = self.explicit_caller
|
||||
|
||||
# it's important that the order of these arguments does not change
|
||||
# if not also changed in the compiler's `function_scoping` method.
|
||||
# the order is caller, keyword arguments, positional arguments!
|
||||
if self.caller and not found_caller:
|
||||
caller = kwargs.pop('caller', None)
|
||||
if caller is None:
|
||||
caller = self._environment.undefined('No caller defined',
|
||||
name='caller')
|
||||
arguments.append(caller)
|
||||
|
||||
if self.catch_kwargs:
|
||||
arguments.append(kwargs)
|
||||
elif kwargs:
|
||||
if 'caller' in kwargs:
|
||||
raise TypeError('macro %r was invoked with two values for '
|
||||
'the special caller argument. This is '
|
||||
'most likely a bug.' % self.name)
|
||||
raise TypeError('macro %r takes no keyword argument %r' %
|
||||
(self.name, next(iter(kwargs))))
|
||||
if self.catch_varargs:
|
||||
arguments.append(args[self._argument_count:])
|
||||
elif len(args) > self._argument_count:
|
||||
raise TypeError('macro %r takes not more than %d argument(s)' %
|
||||
(self.name, len(self.arguments)))
|
||||
|
||||
return self._invoke(arguments, autoescape)
|
||||
|
||||
def _invoke(self, arguments, autoescape):
|
||||
"""This method is being swapped out by the async implementation."""
|
||||
rv = self._func(*arguments)
|
||||
if autoescape:
|
||||
rv = Markup(rv)
|
||||
return rv
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s>' % (
|
||||
self.__class__.__name__,
|
||||
self.name is None and 'anonymous' or repr(self.name)
|
||||
)
|
||||
|
||||
|
||||
@implements_to_string
|
||||
class Undefined(object):
|
||||
"""The default undefined type. This undefined type can be printed and
|
||||
iterated over, but every other access will raise an :exc:`jinja2.exceptions.UndefinedError`:
|
||||
|
||||
>>> foo = Undefined(name='foo')
|
||||
>>> str(foo)
|
||||
''
|
||||
>>> not foo
|
||||
True
|
||||
>>> foo + 42
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
||||
"""
|
||||
__slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name',
|
||||
'_undefined_exception')
|
||||
|
||||
def __init__(self, hint=None, obj=missing, name=None, exc=UndefinedError):
|
||||
self._undefined_hint = hint
|
||||
self._undefined_obj = obj
|
||||
self._undefined_name = name
|
||||
self._undefined_exception = exc
|
||||
|
||||
@internalcode
|
||||
def _fail_with_undefined_error(self, *args, **kwargs):
|
||||
"""Regular callback function for undefined objects that raises an
|
||||
`jinja2.exceptions.UndefinedError` on call.
|
||||
"""
|
||||
if self._undefined_hint is None:
|
||||
if self._undefined_obj is missing:
|
||||
hint = '%r is undefined' % self._undefined_name
|
||||
elif not isinstance(self._undefined_name, string_types):
|
||||
hint = '%s has no element %r' % (
|
||||
object_type_repr(self._undefined_obj),
|
||||
self._undefined_name
|
||||
)
|
||||
else:
|
||||
hint = '%r has no attribute %r' % (
|
||||
object_type_repr(self._undefined_obj),
|
||||
self._undefined_name
|
||||
)
|
||||
else:
|
||||
hint = self._undefined_hint
|
||||
raise self._undefined_exception(hint)
|
||||
|
||||
@internalcode
|
||||
def __getattr__(self, name):
|
||||
if name[:2] == '__':
|
||||
raise AttributeError(name)
|
||||
return self._fail_with_undefined_error()
|
||||
|
||||
__add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
|
||||
__truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
|
||||
__mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
|
||||
__getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \
|
||||
__float__ = __complex__ = __pow__ = __rpow__ = __sub__ = \
|
||||
__rsub__ = _fail_with_undefined_error
|
||||
|
||||
def __eq__(self, other):
|
||||
return type(self) is type(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return id(type(self))
|
||||
|
||||
def __str__(self):
|
||||
return u''
|
||||
|
||||
def __len__(self):
|
||||
return 0
|
||||
|
||||
def __iter__(self):
|
||||
if 0:
|
||||
yield None
|
||||
|
||||
def __nonzero__(self):
|
||||
return False
|
||||
__bool__ = __nonzero__
|
||||
|
||||
def __repr__(self):
|
||||
return 'Undefined'
|
||||
|
||||
|
||||
def make_logging_undefined(logger=None, base=None):
|
||||
"""Given a logger object this returns a new undefined class that will
|
||||
log certain failures. It will log iterations and printing. If no
|
||||
logger is given a default logger is created.
|
||||
|
||||
Example::
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
LoggingUndefined = make_logging_undefined(
|
||||
logger=logger,
|
||||
base=Undefined
|
||||
)
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
:param logger: the logger to use. If not provided, a default logger
|
||||
is created.
|
||||
:param base: the base class to add logging functionality to. This
|
||||
defaults to :class:`Undefined`.
|
||||
"""
|
||||
if logger is None:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.addHandler(logging.StreamHandler(sys.stderr))
|
||||
if base is None:
|
||||
base = Undefined
|
||||
|
||||
def _log_message(undef):
|
||||
if undef._undefined_hint is None:
|
||||
if undef._undefined_obj is missing:
|
||||
hint = '%s is undefined' % undef._undefined_name
|
||||
elif not isinstance(undef._undefined_name, string_types):
|
||||
hint = '%s has no element %s' % (
|
||||
object_type_repr(undef._undefined_obj),
|
||||
undef._undefined_name)
|
||||
else:
|
||||
hint = '%s has no attribute %s' % (
|
||||
object_type_repr(undef._undefined_obj),
|
||||
undef._undefined_name)
|
||||
else:
|
||||
hint = undef._undefined_hint
|
||||
logger.warning('Template variable warning: %s', hint)
|
||||
|
||||
class LoggingUndefined(base):
|
||||
|
||||
def _fail_with_undefined_error(self, *args, **kwargs):
|
||||
try:
|
||||
return base._fail_with_undefined_error(self, *args, **kwargs)
|
||||
except self._undefined_exception as e:
|
||||
logger.error('Template variable error: %s', str(e))
|
||||
raise e
|
||||
|
||||
def __str__(self):
|
||||
rv = base.__str__(self)
|
||||
_log_message(self)
|
||||
return rv
|
||||
|
||||
def __iter__(self):
|
||||
rv = base.__iter__(self)
|
||||
_log_message(self)
|
||||
return rv
|
||||
|
||||
if PY2:
|
||||
def __nonzero__(self):
|
||||
rv = base.__nonzero__(self)
|
||||
_log_message(self)
|
||||
return rv
|
||||
|
||||
def __unicode__(self):
|
||||
rv = base.__unicode__(self)
|
||||
_log_message(self)
|
||||
return rv
|
||||
else:
|
||||
def __bool__(self):
|
||||
rv = base.__bool__(self)
|
||||
_log_message(self)
|
||||
return rv
|
||||
|
||||
return LoggingUndefined
|
||||
|
||||
|
||||
@implements_to_string
|
||||
class DebugUndefined(Undefined):
|
||||
"""An undefined that returns the debug info when printed.
|
||||
|
||||
>>> foo = DebugUndefined(name='foo')
|
||||
>>> str(foo)
|
||||
'{{ foo }}'
|
||||
>>> not foo
|
||||
True
|
||||
>>> foo + 42
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __str__(self):
|
||||
if self._undefined_hint is None:
|
||||
if self._undefined_obj is missing:
|
||||
return u'{{ %s }}' % self._undefined_name
|
||||
return '{{ no such element: %s[%r] }}' % (
|
||||
object_type_repr(self._undefined_obj),
|
||||
self._undefined_name
|
||||
)
|
||||
return u'{{ undefined value printed: %s }}' % self._undefined_hint
|
||||
|
||||
|
||||
@implements_to_string
|
||||
class StrictUndefined(Undefined):
|
||||
"""An undefined that barks on print and iteration as well as boolean
|
||||
tests and all kinds of comparisons. In other words: you can do nothing
|
||||
with it except checking if it's defined using the `defined` test.
|
||||
|
||||
>>> foo = StrictUndefined(name='foo')
|
||||
>>> str(foo)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
||||
>>> not foo
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
||||
>>> foo + 42
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
jinja2.exceptions.UndefinedError: 'foo' is undefined
|
||||
"""
|
||||
__slots__ = ()
|
||||
__iter__ = __str__ = __len__ = __nonzero__ = __eq__ = \
|
||||
__ne__ = __bool__ = __hash__ = \
|
||||
Undefined._fail_with_undefined_error
|
||||
|
||||
|
||||
# remove remaining slots attributes, after the metaclass did the magic they
|
||||
# are unneeded and irritating as they contain wrong data for the subclasses.
|
||||
del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__
|
||||
Vendored
-475
@@ -1,475 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.sandbox
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Adds a sandbox layer to Jinja as it was the default behavior in the old
|
||||
Jinja 1 releases. This sandbox is slightly different from Jinja 1 as the
|
||||
default behavior is easier to use.
|
||||
|
||||
The behavior can be changed by subclassing the environment.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD.
|
||||
"""
|
||||
import types
|
||||
import operator
|
||||
from collections import Mapping
|
||||
from jinja2.environment import Environment
|
||||
from jinja2.exceptions import SecurityError
|
||||
from jinja2._compat import string_types, PY2
|
||||
from jinja2.utils import Markup
|
||||
|
||||
from markupsafe import EscapeFormatter
|
||||
from string import Formatter
|
||||
|
||||
|
||||
#: maximum number of items a range may produce
|
||||
MAX_RANGE = 100000
|
||||
|
||||
#: attributes of function objects that are considered unsafe.
|
||||
if PY2:
|
||||
UNSAFE_FUNCTION_ATTRIBUTES = set(['func_closure', 'func_code', 'func_dict',
|
||||
'func_defaults', 'func_globals'])
|
||||
else:
|
||||
# On versions > python 2 the special attributes on functions are gone,
|
||||
# but they remain on methods and generators for whatever reason.
|
||||
UNSAFE_FUNCTION_ATTRIBUTES = set()
|
||||
|
||||
|
||||
#: unsafe method attributes. function attributes are unsafe for methods too
|
||||
UNSAFE_METHOD_ATTRIBUTES = set(['im_class', 'im_func', 'im_self'])
|
||||
|
||||
#: unsafe generator attirbutes.
|
||||
UNSAFE_GENERATOR_ATTRIBUTES = set(['gi_frame', 'gi_code'])
|
||||
|
||||
#: unsafe attributes on coroutines
|
||||
UNSAFE_COROUTINE_ATTRIBUTES = set(['cr_frame', 'cr_code'])
|
||||
|
||||
#: unsafe attributes on async generators
|
||||
UNSAFE_ASYNC_GENERATOR_ATTRIBUTES = set(['ag_code', 'ag_frame'])
|
||||
|
||||
import warnings
|
||||
|
||||
# make sure we don't warn in python 2.6 about stuff we don't care about
|
||||
warnings.filterwarnings('ignore', 'the sets module', DeprecationWarning,
|
||||
module='jinja2.sandbox')
|
||||
|
||||
from collections import deque
|
||||
|
||||
_mutable_set_types = (set,)
|
||||
_mutable_mapping_types = (dict,)
|
||||
_mutable_sequence_types = (list,)
|
||||
|
||||
|
||||
# on python 2.x we can register the user collection types
|
||||
try:
|
||||
from UserDict import UserDict, DictMixin
|
||||
from UserList import UserList
|
||||
_mutable_mapping_types += (UserDict, DictMixin)
|
||||
_mutable_set_types += (UserList,)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# if sets is still available, register the mutable set from there as well
|
||||
try:
|
||||
from sets import Set
|
||||
_mutable_set_types += (Set,)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
#: register Python 2.6 abstract base classes
|
||||
from collections import MutableSet, MutableMapping, MutableSequence
|
||||
_mutable_set_types += (MutableSet,)
|
||||
_mutable_mapping_types += (MutableMapping,)
|
||||
_mutable_sequence_types += (MutableSequence,)
|
||||
|
||||
|
||||
_mutable_spec = (
|
||||
(_mutable_set_types, frozenset([
|
||||
'add', 'clear', 'difference_update', 'discard', 'pop', 'remove',
|
||||
'symmetric_difference_update', 'update'
|
||||
])),
|
||||
(_mutable_mapping_types, frozenset([
|
||||
'clear', 'pop', 'popitem', 'setdefault', 'update'
|
||||
])),
|
||||
(_mutable_sequence_types, frozenset([
|
||||
'append', 'reverse', 'insert', 'sort', 'extend', 'remove'
|
||||
])),
|
||||
(deque, frozenset([
|
||||
'append', 'appendleft', 'clear', 'extend', 'extendleft', 'pop',
|
||||
'popleft', 'remove', 'rotate'
|
||||
]))
|
||||
)
|
||||
|
||||
|
||||
class _MagicFormatMapping(Mapping):
|
||||
"""This class implements a dummy wrapper to fix a bug in the Python
|
||||
standard library for string formatting.
|
||||
|
||||
See http://bugs.python.org/issue13598 for information about why
|
||||
this is necessary.
|
||||
"""
|
||||
|
||||
def __init__(self, args, kwargs):
|
||||
self._args = args
|
||||
self._kwargs = kwargs
|
||||
self._last_index = 0
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key == '':
|
||||
idx = self._last_index
|
||||
self._last_index += 1
|
||||
try:
|
||||
return self._args[idx]
|
||||
except LookupError:
|
||||
pass
|
||||
key = str(idx)
|
||||
return self._kwargs[key]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._kwargs)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._kwargs)
|
||||
|
||||
|
||||
def inspect_format_method(callable):
|
||||
if not isinstance(callable, (types.MethodType,
|
||||
types.BuiltinMethodType)) or \
|
||||
callable.__name__ != 'format':
|
||||
return None
|
||||
obj = callable.__self__
|
||||
if isinstance(obj, string_types):
|
||||
return obj
|
||||
|
||||
|
||||
def safe_range(*args):
|
||||
"""A range that can't generate ranges with a length of more than
|
||||
MAX_RANGE items.
|
||||
"""
|
||||
rng = range(*args)
|
||||
if len(rng) > MAX_RANGE:
|
||||
raise OverflowError('range too big, maximum size for range is %d' %
|
||||
MAX_RANGE)
|
||||
return rng
|
||||
|
||||
|
||||
def unsafe(f):
|
||||
"""Marks a function or method as unsafe.
|
||||
|
||||
::
|
||||
|
||||
@unsafe
|
||||
def delete(self):
|
||||
pass
|
||||
"""
|
||||
f.unsafe_callable = True
|
||||
return f
|
||||
|
||||
|
||||
def is_internal_attribute(obj, attr):
|
||||
"""Test if the attribute given is an internal python attribute. For
|
||||
example this function returns `True` for the `func_code` attribute of
|
||||
python objects. This is useful if the environment method
|
||||
:meth:`~SandboxedEnvironment.is_safe_attribute` is overridden.
|
||||
|
||||
>>> from jinja2.sandbox import is_internal_attribute
|
||||
>>> is_internal_attribute(str, "mro")
|
||||
True
|
||||
>>> is_internal_attribute(str, "upper")
|
||||
False
|
||||
"""
|
||||
if isinstance(obj, types.FunctionType):
|
||||
if attr in UNSAFE_FUNCTION_ATTRIBUTES:
|
||||
return True
|
||||
elif isinstance(obj, types.MethodType):
|
||||
if attr in UNSAFE_FUNCTION_ATTRIBUTES or \
|
||||
attr in UNSAFE_METHOD_ATTRIBUTES:
|
||||
return True
|
||||
elif isinstance(obj, type):
|
||||
if attr == 'mro':
|
||||
return True
|
||||
elif isinstance(obj, (types.CodeType, types.TracebackType, types.FrameType)):
|
||||
return True
|
||||
elif isinstance(obj, types.GeneratorType):
|
||||
if attr in UNSAFE_GENERATOR_ATTRIBUTES:
|
||||
return True
|
||||
elif hasattr(types, 'CoroutineType') and isinstance(obj, types.CoroutineType):
|
||||
if attr in UNSAFE_COROUTINE_ATTRIBUTES:
|
||||
return True
|
||||
elif hasattr(types, 'AsyncGeneratorType') and isinstance(obj, types.AsyncGeneratorType):
|
||||
if attr in UNSAFE_ASYNC_GENERATOR_ATTRIBUTES:
|
||||
return True
|
||||
return attr.startswith('__')
|
||||
|
||||
|
||||
def modifies_known_mutable(obj, attr):
|
||||
"""This function checks if an attribute on a builtin mutable object
|
||||
(list, dict, set or deque) would modify it if called. It also supports
|
||||
the "user"-versions of the objects (`sets.Set`, `UserDict.*` etc.) and
|
||||
with Python 2.6 onwards the abstract base classes `MutableSet`,
|
||||
`MutableMapping`, and `MutableSequence`.
|
||||
|
||||
>>> modifies_known_mutable({}, "clear")
|
||||
True
|
||||
>>> modifies_known_mutable({}, "keys")
|
||||
False
|
||||
>>> modifies_known_mutable([], "append")
|
||||
True
|
||||
>>> modifies_known_mutable([], "index")
|
||||
False
|
||||
|
||||
If called with an unsupported object (such as unicode) `False` is
|
||||
returned.
|
||||
|
||||
>>> modifies_known_mutable("foo", "upper")
|
||||
False
|
||||
"""
|
||||
for typespec, unsafe in _mutable_spec:
|
||||
if isinstance(obj, typespec):
|
||||
return attr in unsafe
|
||||
return False
|
||||
|
||||
|
||||
class SandboxedEnvironment(Environment):
|
||||
"""The sandboxed environment. It works like the regular environment but
|
||||
tells the compiler to generate sandboxed code. Additionally subclasses of
|
||||
this environment may override the methods that tell the runtime what
|
||||
attributes or functions are safe to access.
|
||||
|
||||
If the template tries to access insecure code a :exc:`SecurityError` is
|
||||
raised. However also other exceptions may occur during the rendering so
|
||||
the caller has to ensure that all exceptions are caught.
|
||||
"""
|
||||
sandboxed = True
|
||||
|
||||
#: default callback table for the binary operators. A copy of this is
|
||||
#: available on each instance of a sandboxed environment as
|
||||
#: :attr:`binop_table`
|
||||
default_binop_table = {
|
||||
'+': operator.add,
|
||||
'-': operator.sub,
|
||||
'*': operator.mul,
|
||||
'/': operator.truediv,
|
||||
'//': operator.floordiv,
|
||||
'**': operator.pow,
|
||||
'%': operator.mod
|
||||
}
|
||||
|
||||
#: default callback table for the unary operators. A copy of this is
|
||||
#: available on each instance of a sandboxed environment as
|
||||
#: :attr:`unop_table`
|
||||
default_unop_table = {
|
||||
'+': operator.pos,
|
||||
'-': operator.neg
|
||||
}
|
||||
|
||||
#: a set of binary operators that should be intercepted. Each operator
|
||||
#: that is added to this set (empty by default) is delegated to the
|
||||
#: :meth:`call_binop` method that will perform the operator. The default
|
||||
#: operator callback is specified by :attr:`binop_table`.
|
||||
#:
|
||||
#: The following binary operators are interceptable:
|
||||
#: ``//``, ``%``, ``+``, ``*``, ``-``, ``/``, and ``**``
|
||||
#:
|
||||
#: The default operation form the operator table corresponds to the
|
||||
#: builtin function. Intercepted calls are always slower than the native
|
||||
#: operator call, so make sure only to intercept the ones you are
|
||||
#: interested in.
|
||||
#:
|
||||
#: .. versionadded:: 2.6
|
||||
intercepted_binops = frozenset()
|
||||
|
||||
#: a set of unary operators that should be intercepted. Each operator
|
||||
#: that is added to this set (empty by default) is delegated to the
|
||||
#: :meth:`call_unop` method that will perform the operator. The default
|
||||
#: operator callback is specified by :attr:`unop_table`.
|
||||
#:
|
||||
#: The following unary operators are interceptable: ``+``, ``-``
|
||||
#:
|
||||
#: The default operation form the operator table corresponds to the
|
||||
#: builtin function. Intercepted calls are always slower than the native
|
||||
#: operator call, so make sure only to intercept the ones you are
|
||||
#: interested in.
|
||||
#:
|
||||
#: .. versionadded:: 2.6
|
||||
intercepted_unops = frozenset()
|
||||
|
||||
def intercept_unop(self, operator):
|
||||
"""Called during template compilation with the name of a unary
|
||||
operator to check if it should be intercepted at runtime. If this
|
||||
method returns `True`, :meth:`call_unop` is excuted for this unary
|
||||
operator. The default implementation of :meth:`call_unop` will use
|
||||
the :attr:`unop_table` dictionary to perform the operator with the
|
||||
same logic as the builtin one.
|
||||
|
||||
The following unary operators are interceptable: ``+`` and ``-``
|
||||
|
||||
Intercepted calls are always slower than the native operator call,
|
||||
so make sure only to intercept the ones you are interested in.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
Environment.__init__(self, *args, **kwargs)
|
||||
self.globals['range'] = safe_range
|
||||
self.binop_table = self.default_binop_table.copy()
|
||||
self.unop_table = self.default_unop_table.copy()
|
||||
|
||||
def is_safe_attribute(self, obj, attr, value):
|
||||
"""The sandboxed environment will call this method to check if the
|
||||
attribute of an object is safe to access. Per default all attributes
|
||||
starting with an underscore are considered private as well as the
|
||||
special attributes of internal python objects as returned by the
|
||||
:func:`is_internal_attribute` function.
|
||||
"""
|
||||
return not (attr.startswith('_') or is_internal_attribute(obj, attr))
|
||||
|
||||
def is_safe_callable(self, obj):
|
||||
"""Check if an object is safely callable. Per default a function is
|
||||
considered safe unless the `unsafe_callable` attribute exists and is
|
||||
True. Override this method to alter the behavior, but this won't
|
||||
affect the `unsafe` decorator from this module.
|
||||
"""
|
||||
return not (getattr(obj, 'unsafe_callable', False) or
|
||||
getattr(obj, 'alters_data', False))
|
||||
|
||||
def call_binop(self, context, operator, left, right):
|
||||
"""For intercepted binary operator calls (:meth:`intercepted_binops`)
|
||||
this function is executed instead of the builtin operator. This can
|
||||
be used to fine tune the behavior of certain operators.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
return self.binop_table[operator](left, right)
|
||||
|
||||
def call_unop(self, context, operator, arg):
|
||||
"""For intercepted unary operator calls (:meth:`intercepted_unops`)
|
||||
this function is executed instead of the builtin operator. This can
|
||||
be used to fine tune the behavior of certain operators.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
return self.unop_table[operator](arg)
|
||||
|
||||
def getitem(self, obj, argument):
|
||||
"""Subscribe an object from sandboxed code."""
|
||||
try:
|
||||
return obj[argument]
|
||||
except (TypeError, LookupError):
|
||||
if isinstance(argument, string_types):
|
||||
try:
|
||||
attr = str(argument)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
value = getattr(obj, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if self.is_safe_attribute(obj, argument, value):
|
||||
return value
|
||||
return self.unsafe_undefined(obj, argument)
|
||||
return self.undefined(obj=obj, name=argument)
|
||||
|
||||
def getattr(self, obj, attribute):
|
||||
"""Subscribe an object from sandboxed code and prefer the
|
||||
attribute. The attribute passed *must* be a bytestring.
|
||||
"""
|
||||
try:
|
||||
value = getattr(obj, attribute)
|
||||
except AttributeError:
|
||||
try:
|
||||
return obj[attribute]
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
else:
|
||||
if self.is_safe_attribute(obj, attribute, value):
|
||||
return value
|
||||
return self.unsafe_undefined(obj, attribute)
|
||||
return self.undefined(obj=obj, name=attribute)
|
||||
|
||||
def unsafe_undefined(self, obj, attribute):
|
||||
"""Return an undefined object for unsafe attributes."""
|
||||
return self.undefined('access to attribute %r of %r '
|
||||
'object is unsafe.' % (
|
||||
attribute,
|
||||
obj.__class__.__name__
|
||||
), name=attribute, obj=obj, exc=SecurityError)
|
||||
|
||||
def format_string(self, s, args, kwargs):
|
||||
"""If a format call is detected, then this is routed through this
|
||||
method so that our safety sandbox can be used for it.
|
||||
"""
|
||||
if isinstance(s, Markup):
|
||||
formatter = SandboxedEscapeFormatter(self, s.escape)
|
||||
else:
|
||||
formatter = SandboxedFormatter(self)
|
||||
kwargs = _MagicFormatMapping(args, kwargs)
|
||||
rv = formatter.vformat(s, args, kwargs)
|
||||
return type(s)(rv)
|
||||
|
||||
def call(__self, __context, __obj, *args, **kwargs):
|
||||
"""Call an object from sandboxed code."""
|
||||
fmt = inspect_format_method(__obj)
|
||||
if fmt is not None:
|
||||
return __self.format_string(fmt, args, kwargs)
|
||||
|
||||
# the double prefixes are to avoid double keyword argument
|
||||
# errors when proxying the call.
|
||||
if not __self.is_safe_callable(__obj):
|
||||
raise SecurityError('%r is not safely callable' % (__obj,))
|
||||
return __context.call(__obj, *args, **kwargs)
|
||||
|
||||
|
||||
class ImmutableSandboxedEnvironment(SandboxedEnvironment):
|
||||
"""Works exactly like the regular `SandboxedEnvironment` but does not
|
||||
permit modifications on the builtin mutable objects `list`, `set`, and
|
||||
`dict` by using the :func:`modifies_known_mutable` function.
|
||||
"""
|
||||
|
||||
def is_safe_attribute(self, obj, attr, value):
|
||||
if not SandboxedEnvironment.is_safe_attribute(self, obj, attr, value):
|
||||
return False
|
||||
return not modifies_known_mutable(obj, attr)
|
||||
|
||||
|
||||
# This really is not a public API apparenlty.
|
||||
try:
|
||||
from _string import formatter_field_name_split
|
||||
except ImportError:
|
||||
def formatter_field_name_split(field_name):
|
||||
return field_name._formatter_field_name_split()
|
||||
|
||||
|
||||
class SandboxedFormatterMixin(object):
|
||||
|
||||
def __init__(self, env):
|
||||
self._env = env
|
||||
|
||||
def get_field(self, field_name, args, kwargs):
|
||||
first, rest = formatter_field_name_split(field_name)
|
||||
obj = self.get_value(first, args, kwargs)
|
||||
for is_attr, i in rest:
|
||||
if is_attr:
|
||||
obj = self._env.getattr(obj, i)
|
||||
else:
|
||||
obj = self._env.getitem(obj, i)
|
||||
return obj, first
|
||||
|
||||
class SandboxedFormatter(SandboxedFormatterMixin, Formatter):
|
||||
|
||||
def __init__(self, env):
|
||||
SandboxedFormatterMixin.__init__(self, env)
|
||||
Formatter.__init__(self)
|
||||
|
||||
class SandboxedEscapeFormatter(SandboxedFormatterMixin, EscapeFormatter):
|
||||
|
||||
def __init__(self, env, escape):
|
||||
SandboxedFormatterMixin.__init__(self, env)
|
||||
EscapeFormatter.__init__(self, escape)
|
||||
Vendored
-185
@@ -1,185 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.tests
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Jinja test functions. Used with the "is" operator.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
from collections import Mapping
|
||||
from jinja2.runtime import Undefined
|
||||
from jinja2._compat import text_type, string_types, integer_types
|
||||
import decimal
|
||||
|
||||
number_re = re.compile(r'^-?\d+(\.\d+)?$')
|
||||
regex_type = type(number_re)
|
||||
|
||||
|
||||
test_callable = callable
|
||||
|
||||
|
||||
def test_odd(value):
|
||||
"""Return true if the variable is odd."""
|
||||
return value % 2 == 1
|
||||
|
||||
|
||||
def test_even(value):
|
||||
"""Return true if the variable is even."""
|
||||
return value % 2 == 0
|
||||
|
||||
|
||||
def test_divisibleby(value, num):
|
||||
"""Check if a variable is divisible by a number."""
|
||||
return value % num == 0
|
||||
|
||||
|
||||
def test_defined(value):
|
||||
"""Return true if the variable is defined:
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{% if variable is defined %}
|
||||
value of variable: {{ variable }}
|
||||
{% else %}
|
||||
variable is not defined
|
||||
{% endif %}
|
||||
|
||||
See the :func:`default` filter for a simple way to set undefined
|
||||
variables.
|
||||
"""
|
||||
return not isinstance(value, Undefined)
|
||||
|
||||
|
||||
def test_undefined(value):
|
||||
"""Like :func:`defined` but the other way round."""
|
||||
return isinstance(value, Undefined)
|
||||
|
||||
|
||||
def test_none(value):
|
||||
"""Return true if the variable is none."""
|
||||
return value is None
|
||||
|
||||
|
||||
def test_lower(value):
|
||||
"""Return true if the variable is lowercased."""
|
||||
return text_type(value).islower()
|
||||
|
||||
|
||||
def test_upper(value):
|
||||
"""Return true if the variable is uppercased."""
|
||||
return text_type(value).isupper()
|
||||
|
||||
|
||||
def test_string(value):
|
||||
"""Return true if the object is a string."""
|
||||
return isinstance(value, string_types)
|
||||
|
||||
|
||||
def test_mapping(value):
|
||||
"""Return true if the object is a mapping (dict etc.).
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
return isinstance(value, Mapping)
|
||||
|
||||
|
||||
def test_number(value):
|
||||
"""Return true if the variable is a number."""
|
||||
return isinstance(value, integer_types + (float, complex, decimal.Decimal))
|
||||
|
||||
|
||||
def test_sequence(value):
|
||||
"""Return true if the variable is a sequence. Sequences are variables
|
||||
that are iterable.
|
||||
"""
|
||||
try:
|
||||
len(value)
|
||||
value.__getitem__
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def test_equalto(value, other):
|
||||
"""Check if an object has the same value as another object:
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{% if foo.expression is equalto 42 %}
|
||||
the foo attribute evaluates to the constant 42
|
||||
{% endif %}
|
||||
|
||||
This appears to be a useless test as it does exactly the same as the
|
||||
``==`` operator, but it can be useful when used together with the
|
||||
`selectattr` function:
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{{ users|selectattr("email", "equalto", "foo@bar.invalid") }}
|
||||
|
||||
.. versionadded:: 2.8
|
||||
"""
|
||||
return value == other
|
||||
|
||||
|
||||
def test_sameas(value, other):
|
||||
"""Check if an object points to the same memory address than another
|
||||
object:
|
||||
|
||||
.. sourcecode:: jinja
|
||||
|
||||
{% if foo.attribute is sameas false %}
|
||||
the foo attribute really is the `False` singleton
|
||||
{% endif %}
|
||||
"""
|
||||
return value is other
|
||||
|
||||
|
||||
def test_iterable(value):
|
||||
"""Check if it's possible to iterate over an object."""
|
||||
try:
|
||||
iter(value)
|
||||
except TypeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def test_escaped(value):
|
||||
"""Check if the value is escaped."""
|
||||
return hasattr(value, '__html__')
|
||||
|
||||
|
||||
def test_greaterthan(value, other):
|
||||
"""Check if value is greater than other."""
|
||||
return value > other
|
||||
|
||||
|
||||
def test_lessthan(value, other):
|
||||
"""Check if value is less than other."""
|
||||
return value < other
|
||||
|
||||
|
||||
TESTS = {
|
||||
'odd': test_odd,
|
||||
'even': test_even,
|
||||
'divisibleby': test_divisibleby,
|
||||
'defined': test_defined,
|
||||
'undefined': test_undefined,
|
||||
'none': test_none,
|
||||
'lower': test_lower,
|
||||
'upper': test_upper,
|
||||
'string': test_string,
|
||||
'mapping': test_mapping,
|
||||
'number': test_number,
|
||||
'sequence': test_sequence,
|
||||
'iterable': test_iterable,
|
||||
'callable': test_callable,
|
||||
'sameas': test_sameas,
|
||||
'equalto': test_equalto,
|
||||
'escaped': test_escaped,
|
||||
'greaterthan': test_greaterthan,
|
||||
'lessthan': test_lessthan
|
||||
}
|
||||
Vendored
-624
@@ -1,624 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.utils
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Utility functions.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
import json
|
||||
import errno
|
||||
from collections import deque
|
||||
from threading import Lock
|
||||
from jinja2._compat import text_type, string_types, implements_iterator, \
|
||||
url_quote
|
||||
|
||||
|
||||
_word_split_re = re.compile(r'(\s+)')
|
||||
_punctuation_re = re.compile(
|
||||
'^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
|
||||
'|'.join(map(re.escape, ('(', '<', '<'))),
|
||||
'|'.join(map(re.escape, ('.', ',', ')', '>', '\n', '>')))
|
||||
)
|
||||
)
|
||||
_simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
|
||||
_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
|
||||
_entity_re = re.compile(r'&([^;]+);')
|
||||
_letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
_digits = '0123456789'
|
||||
|
||||
# special singleton representing missing values for the runtime
|
||||
missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
|
||||
|
||||
# internal code
|
||||
internal_code = set()
|
||||
|
||||
concat = u''.join
|
||||
|
||||
_slash_escape = '\\/' not in json.dumps('/')
|
||||
|
||||
|
||||
def contextfunction(f):
|
||||
"""This decorator can be used to mark a function or method context callable.
|
||||
A context callable is passed the active :class:`Context` as first argument when
|
||||
called from the template. This is useful if a function wants to get access
|
||||
to the context or functions provided on the context object. For example
|
||||
a function that returns a sorted list of template variables the current
|
||||
template exports could look like this::
|
||||
|
||||
@contextfunction
|
||||
def get_exported_names(context):
|
||||
return sorted(context.exported_vars)
|
||||
"""
|
||||
f.contextfunction = True
|
||||
return f
|
||||
|
||||
|
||||
def evalcontextfunction(f):
|
||||
"""This decorator can be used to mark a function or method as an eval
|
||||
context callable. This is similar to the :func:`contextfunction`
|
||||
but instead of passing the context, an evaluation context object is
|
||||
passed. For more information about the eval context, see
|
||||
:ref:`eval-context`.
|
||||
|
||||
.. versionadded:: 2.4
|
||||
"""
|
||||
f.evalcontextfunction = True
|
||||
return f
|
||||
|
||||
|
||||
def environmentfunction(f):
|
||||
"""This decorator can be used to mark a function or method as environment
|
||||
callable. This decorator works exactly like the :func:`contextfunction`
|
||||
decorator just that the first argument is the active :class:`Environment`
|
||||
and not context.
|
||||
"""
|
||||
f.environmentfunction = True
|
||||
return f
|
||||
|
||||
|
||||
def internalcode(f):
|
||||
"""Marks the function as internally used"""
|
||||
internal_code.add(f.__code__)
|
||||
return f
|
||||
|
||||
|
||||
def is_undefined(obj):
|
||||
"""Check if the object passed is undefined. This does nothing more than
|
||||
performing an instance check against :class:`Undefined` but looks nicer.
|
||||
This can be used for custom filters or tests that want to react to
|
||||
undefined variables. For example a custom default filter can look like
|
||||
this::
|
||||
|
||||
def default(var, default=''):
|
||||
if is_undefined(var):
|
||||
return default
|
||||
return var
|
||||
"""
|
||||
from jinja2.runtime import Undefined
|
||||
return isinstance(obj, Undefined)
|
||||
|
||||
|
||||
def consume(iterable):
|
||||
"""Consumes an iterable without doing anything with it."""
|
||||
for event in iterable:
|
||||
pass
|
||||
|
||||
|
||||
def clear_caches():
|
||||
"""Jinja2 keeps internal caches for environments and lexers. These are
|
||||
used so that Jinja2 doesn't have to recreate environments and lexers all
|
||||
the time. Normally you don't have to care about that but if you are
|
||||
measuring memory consumption you may want to clean the caches.
|
||||
"""
|
||||
from jinja2.environment import _spontaneous_environments
|
||||
from jinja2.lexer import _lexer_cache
|
||||
_spontaneous_environments.clear()
|
||||
_lexer_cache.clear()
|
||||
|
||||
|
||||
def import_string(import_name, silent=False):
|
||||
"""Imports an object based on a string. This is useful if you want to
|
||||
use import paths as endpoints or something similar. An import path can
|
||||
be specified either in dotted notation (``xml.sax.saxutils.escape``)
|
||||
or with a colon as object delimiter (``xml.sax.saxutils:escape``).
|
||||
|
||||
If the `silent` is True the return value will be `None` if the import
|
||||
fails.
|
||||
|
||||
:return: imported object
|
||||
"""
|
||||
try:
|
||||
if ':' in import_name:
|
||||
module, obj = import_name.split(':', 1)
|
||||
elif '.' in import_name:
|
||||
items = import_name.split('.')
|
||||
module = '.'.join(items[:-1])
|
||||
obj = items[-1]
|
||||
else:
|
||||
return __import__(import_name)
|
||||
return getattr(__import__(module, None, None, [obj]), obj)
|
||||
except (ImportError, AttributeError):
|
||||
if not silent:
|
||||
raise
|
||||
|
||||
|
||||
def open_if_exists(filename, mode='rb'):
|
||||
"""Returns a file descriptor for the filename if that file exists,
|
||||
otherwise `None`.
|
||||
"""
|
||||
try:
|
||||
return open(filename, mode)
|
||||
except IOError as e:
|
||||
if e.errno not in (errno.ENOENT, errno.EISDIR, errno.EINVAL):
|
||||
raise
|
||||
|
||||
|
||||
def object_type_repr(obj):
|
||||
"""Returns the name of the object's type. For some recognized
|
||||
singletons the name of the object is returned instead. (For
|
||||
example for `None` and `Ellipsis`).
|
||||
"""
|
||||
if obj is None:
|
||||
return 'None'
|
||||
elif obj is Ellipsis:
|
||||
return 'Ellipsis'
|
||||
# __builtin__ in 2.x, builtins in 3.x
|
||||
if obj.__class__.__module__ in ('__builtin__', 'builtins'):
|
||||
name = obj.__class__.__name__
|
||||
else:
|
||||
name = obj.__class__.__module__ + '.' + obj.__class__.__name__
|
||||
return '%s object' % name
|
||||
|
||||
|
||||
def pformat(obj, verbose=False):
|
||||
"""Prettyprint an object. Either use the `pretty` library or the
|
||||
builtin `pprint`.
|
||||
"""
|
||||
try:
|
||||
from pretty import pretty
|
||||
return pretty(obj, verbose=verbose)
|
||||
except ImportError:
|
||||
from pprint import pformat
|
||||
return pformat(obj)
|
||||
|
||||
|
||||
def urlize(text, trim_url_limit=None, rel=None, target=None):
|
||||
"""Converts any URLs in text into clickable links. Works on http://,
|
||||
https:// and www. links. Links can have trailing punctuation (periods,
|
||||
commas, close-parens) and leading punctuation (opening parens) and
|
||||
it'll still do the right thing.
|
||||
|
||||
If trim_url_limit is not None, the URLs in link text will be limited
|
||||
to trim_url_limit characters.
|
||||
|
||||
If nofollow is True, the URLs in link text will get a rel="nofollow"
|
||||
attribute.
|
||||
|
||||
If target is not None, a target attribute will be added to the link.
|
||||
"""
|
||||
trim_url = lambda x, limit=trim_url_limit: limit is not None \
|
||||
and (x[:limit] + (len(x) >=limit and '...'
|
||||
or '')) or x
|
||||
words = _word_split_re.split(text_type(escape(text)))
|
||||
rel_attr = rel and ' rel="%s"' % text_type(escape(rel)) or ''
|
||||
target_attr = target and ' target="%s"' % escape(target) or ''
|
||||
|
||||
for i, word in enumerate(words):
|
||||
match = _punctuation_re.match(word)
|
||||
if match:
|
||||
lead, middle, trail = match.groups()
|
||||
if middle.startswith('www.') or (
|
||||
'@' not in middle and
|
||||
not middle.startswith('http://') and
|
||||
not middle.startswith('https://') and
|
||||
len(middle) > 0 and
|
||||
middle[0] in _letters + _digits and (
|
||||
middle.endswith('.org') or
|
||||
middle.endswith('.net') or
|
||||
middle.endswith('.com')
|
||||
)):
|
||||
middle = '<a href="http://%s"%s%s>%s</a>' % (middle,
|
||||
rel_attr, target_attr, trim_url(middle))
|
||||
if middle.startswith('http://') or \
|
||||
middle.startswith('https://'):
|
||||
middle = '<a href="%s"%s%s>%s</a>' % (middle,
|
||||
rel_attr, target_attr, trim_url(middle))
|
||||
if '@' in middle and not middle.startswith('www.') and \
|
||||
not ':' in middle and _simple_email_re.match(middle):
|
||||
middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
|
||||
if lead + middle + trail != word:
|
||||
words[i] = lead + middle + trail
|
||||
return u''.join(words)
|
||||
|
||||
|
||||
def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
|
||||
"""Generate some lorem ipsum for the template."""
|
||||
from jinja2.constants import LOREM_IPSUM_WORDS
|
||||
from random import choice, randrange
|
||||
words = LOREM_IPSUM_WORDS.split()
|
||||
result = []
|
||||
|
||||
for _ in range(n):
|
||||
next_capitalized = True
|
||||
last_comma = last_fullstop = 0
|
||||
word = None
|
||||
last = None
|
||||
p = []
|
||||
|
||||
# each paragraph contains out of 20 to 100 words.
|
||||
for idx, _ in enumerate(range(randrange(min, max))):
|
||||
while True:
|
||||
word = choice(words)
|
||||
if word != last:
|
||||
last = word
|
||||
break
|
||||
if next_capitalized:
|
||||
word = word.capitalize()
|
||||
next_capitalized = False
|
||||
# add commas
|
||||
if idx - randrange(3, 8) > last_comma:
|
||||
last_comma = idx
|
||||
last_fullstop += 2
|
||||
word += ','
|
||||
# add end of sentences
|
||||
if idx - randrange(10, 20) > last_fullstop:
|
||||
last_comma = last_fullstop = idx
|
||||
word += '.'
|
||||
next_capitalized = True
|
||||
p.append(word)
|
||||
|
||||
# ensure that the paragraph ends with a dot.
|
||||
p = u' '.join(p)
|
||||
if p.endswith(','):
|
||||
p = p[:-1] + '.'
|
||||
elif not p.endswith('.'):
|
||||
p += '.'
|
||||
result.append(p)
|
||||
|
||||
if not html:
|
||||
return u'\n\n'.join(result)
|
||||
return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
|
||||
|
||||
|
||||
def unicode_urlencode(obj, charset='utf-8', for_qs=False):
|
||||
"""URL escapes a single bytestring or unicode string with the
|
||||
given charset if applicable to URL safe quoting under all rules
|
||||
that need to be considered under all supported Python versions.
|
||||
|
||||
If non strings are provided they are converted to their unicode
|
||||
representation first.
|
||||
"""
|
||||
if not isinstance(obj, string_types):
|
||||
obj = text_type(obj)
|
||||
if isinstance(obj, text_type):
|
||||
obj = obj.encode(charset)
|
||||
safe = not for_qs and b'/' or b''
|
||||
rv = text_type(url_quote(obj, safe))
|
||||
if for_qs:
|
||||
rv = rv.replace('%20', '+')
|
||||
return rv
|
||||
|
||||
|
||||
class LRUCache(object):
|
||||
"""A simple LRU Cache implementation."""
|
||||
|
||||
# this is fast for small capacities (something below 1000) but doesn't
|
||||
# scale. But as long as it's only used as storage for templates this
|
||||
# won't do any harm.
|
||||
|
||||
def __init__(self, capacity):
|
||||
self.capacity = capacity
|
||||
self._mapping = {}
|
||||
self._queue = deque()
|
||||
self._postinit()
|
||||
|
||||
def _postinit(self):
|
||||
# alias all queue methods for faster lookup
|
||||
self._popleft = self._queue.popleft
|
||||
self._pop = self._queue.pop
|
||||
self._remove = self._queue.remove
|
||||
self._wlock = Lock()
|
||||
self._append = self._queue.append
|
||||
|
||||
def __getstate__(self):
|
||||
return {
|
||||
'capacity': self.capacity,
|
||||
'_mapping': self._mapping,
|
||||
'_queue': self._queue
|
||||
}
|
||||
|
||||
def __setstate__(self, d):
|
||||
self.__dict__.update(d)
|
||||
self._postinit()
|
||||
|
||||
def __getnewargs__(self):
|
||||
return (self.capacity,)
|
||||
|
||||
def copy(self):
|
||||
"""Return a shallow copy of the instance."""
|
||||
rv = self.__class__(self.capacity)
|
||||
rv._mapping.update(self._mapping)
|
||||
rv._queue = deque(self._queue)
|
||||
return rv
|
||||
|
||||
def get(self, key, default=None):
|
||||
"""Return an item from the cache dict or `default`"""
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
"""Set `default` if the key is not in the cache otherwise
|
||||
leave unchanged. Return the value of this key.
|
||||
"""
|
||||
self._wlock.acquire()
|
||||
try:
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
self[key] = default
|
||||
return default
|
||||
finally:
|
||||
self._wlock.release()
|
||||
|
||||
def clear(self):
|
||||
"""Clear the cache."""
|
||||
self._wlock.acquire()
|
||||
try:
|
||||
self._mapping.clear()
|
||||
self._queue.clear()
|
||||
finally:
|
||||
self._wlock.release()
|
||||
|
||||
def __contains__(self, key):
|
||||
"""Check if a key exists in this cache."""
|
||||
return key in self._mapping
|
||||
|
||||
def __len__(self):
|
||||
"""Return the current size of the cache."""
|
||||
return len(self._mapping)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r>' % (
|
||||
self.__class__.__name__,
|
||||
self._mapping
|
||||
)
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Get an item from the cache. Moves the item up so that it has the
|
||||
highest priority then.
|
||||
|
||||
Raise a `KeyError` if it does not exist.
|
||||
"""
|
||||
self._wlock.acquire()
|
||||
try:
|
||||
rv = self._mapping[key]
|
||||
if self._queue[-1] != key:
|
||||
try:
|
||||
self._remove(key)
|
||||
except ValueError:
|
||||
# if something removed the key from the container
|
||||
# when we read, ignore the ValueError that we would
|
||||
# get otherwise.
|
||||
pass
|
||||
self._append(key)
|
||||
return rv
|
||||
finally:
|
||||
self._wlock.release()
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Sets the value for an item. Moves the item up so that it
|
||||
has the highest priority then.
|
||||
"""
|
||||
self._wlock.acquire()
|
||||
try:
|
||||
if key in self._mapping:
|
||||
self._remove(key)
|
||||
elif len(self._mapping) == self.capacity:
|
||||
del self._mapping[self._popleft()]
|
||||
self._append(key)
|
||||
self._mapping[key] = value
|
||||
finally:
|
||||
self._wlock.release()
|
||||
|
||||
def __delitem__(self, key):
|
||||
"""Remove an item from the cache dict.
|
||||
Raise a `KeyError` if it does not exist.
|
||||
"""
|
||||
self._wlock.acquire()
|
||||
try:
|
||||
del self._mapping[key]
|
||||
try:
|
||||
self._remove(key)
|
||||
except ValueError:
|
||||
# __getitem__ is not locked, it might happen
|
||||
pass
|
||||
finally:
|
||||
self._wlock.release()
|
||||
|
||||
def items(self):
|
||||
"""Return a list of items."""
|
||||
result = [(key, self._mapping[key]) for key in list(self._queue)]
|
||||
result.reverse()
|
||||
return result
|
||||
|
||||
def iteritems(self):
|
||||
"""Iterate over all items."""
|
||||
return iter(self.items())
|
||||
|
||||
def values(self):
|
||||
"""Return a list of all values."""
|
||||
return [x[1] for x in self.items()]
|
||||
|
||||
def itervalue(self):
|
||||
"""Iterate over all values."""
|
||||
return iter(self.values())
|
||||
|
||||
def keys(self):
|
||||
"""Return a list of all keys ordered by most recent usage."""
|
||||
return list(self)
|
||||
|
||||
def iterkeys(self):
|
||||
"""Iterate over all keys in the cache dict, ordered by
|
||||
the most recent usage.
|
||||
"""
|
||||
return reversed(tuple(self._queue))
|
||||
|
||||
__iter__ = iterkeys
|
||||
|
||||
def __reversed__(self):
|
||||
"""Iterate over the values in the cache dict, oldest items
|
||||
coming first.
|
||||
"""
|
||||
return iter(tuple(self._queue))
|
||||
|
||||
__copy__ = copy
|
||||
|
||||
|
||||
# register the LRU cache as mutable mapping if possible
|
||||
try:
|
||||
from collections import MutableMapping
|
||||
MutableMapping.register(LRUCache)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def select_autoescape(enabled_extensions=('html', 'htm', 'xml'),
|
||||
disabled_extensions=(),
|
||||
default_for_string=True,
|
||||
default=False):
|
||||
"""Intelligently sets the initial value of autoescaping based on the
|
||||
filename of the template. This is the recommended way to configure
|
||||
autoescaping if you do not want to write a custom function yourself.
|
||||
|
||||
If you want to enable it for all templates created from strings or
|
||||
for all templates with `.html` and `.xml` extensions::
|
||||
|
||||
from jinja2 import Environment, select_autoescape
|
||||
env = Environment(autoescape=select_autoescape(
|
||||
enabled_extensions=('html', 'xml'),
|
||||
default_for_string=True,
|
||||
))
|
||||
|
||||
Example configuration to turn it on at all times except if the template
|
||||
ends with `.txt`::
|
||||
|
||||
from jinja2 import Environment, select_autoescape
|
||||
env = Environment(autoescape=select_autoescape(
|
||||
disabled_extensions=('txt',),
|
||||
default_for_string=True,
|
||||
default=True,
|
||||
))
|
||||
|
||||
The `enabled_extensions` is an iterable of all the extensions that
|
||||
autoescaping should be enabled for. Likewise `disabled_extensions` is
|
||||
a list of all templates it should be disabled for. If a template is
|
||||
loaded from a string then the default from `default_for_string` is used.
|
||||
If nothing matches then the initial value of autoescaping is set to the
|
||||
value of `default`.
|
||||
|
||||
For security reasons this function operates case insensitive.
|
||||
|
||||
.. versionadded:: 2.9
|
||||
"""
|
||||
enabled_patterns = tuple('.' + x.lstrip('.').lower()
|
||||
for x in enabled_extensions)
|
||||
disabled_patterns = tuple('.' + x.lstrip('.').lower()
|
||||
for x in disabled_extensions)
|
||||
def autoescape(template_name):
|
||||
if template_name is None:
|
||||
return default_for_string
|
||||
template_name = template_name.lower()
|
||||
if template_name.endswith(enabled_patterns):
|
||||
return True
|
||||
if template_name.endswith(disabled_patterns):
|
||||
return False
|
||||
return default
|
||||
return autoescape
|
||||
|
||||
|
||||
def htmlsafe_json_dumps(obj, dumper=None, **kwargs):
|
||||
"""Works exactly like :func:`dumps` but is safe for use in ``<script>``
|
||||
tags. It accepts the same arguments and returns a JSON string. Note that
|
||||
this is available in templates through the ``|tojson`` filter which will
|
||||
also mark the result as safe. Due to how this function escapes certain
|
||||
characters this is safe even if used outside of ``<script>`` tags.
|
||||
|
||||
The following characters are escaped in strings:
|
||||
|
||||
- ``<``
|
||||
- ``>``
|
||||
- ``&``
|
||||
- ``'``
|
||||
|
||||
This makes it safe to embed such strings in any place in HTML with the
|
||||
notable exception of double quoted attributes. In that case single
|
||||
quote your attributes or HTML escape it in addition.
|
||||
"""
|
||||
if dumper is None:
|
||||
dumper = json.dumps
|
||||
rv = dumper(obj, **kwargs) \
|
||||
.replace(u'<', u'\\u003c') \
|
||||
.replace(u'>', u'\\u003e') \
|
||||
.replace(u'&', u'\\u0026') \
|
||||
.replace(u"'", u'\\u0027')
|
||||
return rv
|
||||
|
||||
|
||||
@implements_iterator
|
||||
class Cycler(object):
|
||||
"""A cycle helper for templates."""
|
||||
|
||||
def __init__(self, *items):
|
||||
if not items:
|
||||
raise RuntimeError('at least one item has to be provided')
|
||||
self.items = items
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
"""Resets the cycle."""
|
||||
self.pos = 0
|
||||
|
||||
@property
|
||||
def current(self):
|
||||
"""Returns the current item."""
|
||||
return self.items[self.pos]
|
||||
|
||||
def next(self):
|
||||
"""Goes one item ahead and returns it."""
|
||||
rv = self.current
|
||||
self.pos = (self.pos + 1) % len(self.items)
|
||||
return rv
|
||||
|
||||
__next__ = next
|
||||
|
||||
|
||||
class Joiner(object):
|
||||
"""A joining helper for templates."""
|
||||
|
||||
def __init__(self, sep=u', '):
|
||||
self.sep = sep
|
||||
self.used = False
|
||||
|
||||
def __call__(self):
|
||||
if not self.used:
|
||||
self.used = True
|
||||
return u''
|
||||
return self.sep
|
||||
|
||||
|
||||
# does this python version support async for in and async generators?
|
||||
try:
|
||||
exec('async def _():\n async for _ in ():\n yield _')
|
||||
have_async_gen = True
|
||||
except SyntaxError:
|
||||
have_async_gen = False
|
||||
|
||||
|
||||
# Imported here because that's where it was in the past
|
||||
from markupsafe import Markup, escape, soft_unicode
|
||||
Vendored
-87
@@ -1,87 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
jinja2.visitor
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module implements a visitor for the nodes.
|
||||
|
||||
:copyright: (c) 2017 by the Jinja Team.
|
||||
:license: BSD.
|
||||
"""
|
||||
from jinja2.nodes import Node
|
||||
|
||||
|
||||
class NodeVisitor(object):
|
||||
"""Walks the abstract syntax tree and call visitor functions for every
|
||||
node found. The visitor functions may return values which will be
|
||||
forwarded by the `visit` method.
|
||||
|
||||
Per default the visitor functions for the nodes are ``'visit_'`` +
|
||||
class name of the node. So a `TryFinally` node visit function would
|
||||
be `visit_TryFinally`. This behavior can be changed by overriding
|
||||
the `get_visitor` function. If no visitor function exists for a node
|
||||
(return value `None`) the `generic_visit` visitor is used instead.
|
||||
"""
|
||||
|
||||
def get_visitor(self, node):
|
||||
"""Return the visitor function for this node or `None` if no visitor
|
||||
exists for this node. In that case the generic visit function is
|
||||
used instead.
|
||||
"""
|
||||
method = 'visit_' + node.__class__.__name__
|
||||
return getattr(self, method, None)
|
||||
|
||||
def visit(self, node, *args, **kwargs):
|
||||
"""Visit a node."""
|
||||
f = self.get_visitor(node)
|
||||
if f is not None:
|
||||
return f(node, *args, **kwargs)
|
||||
return self.generic_visit(node, *args, **kwargs)
|
||||
|
||||
def generic_visit(self, node, *args, **kwargs):
|
||||
"""Called if no explicit visitor function exists for a node."""
|
||||
for node in node.iter_child_nodes():
|
||||
self.visit(node, *args, **kwargs)
|
||||
|
||||
|
||||
class NodeTransformer(NodeVisitor):
|
||||
"""Walks the abstract syntax tree and allows modifications of nodes.
|
||||
|
||||
The `NodeTransformer` will walk the AST and use the return value of the
|
||||
visitor functions to replace or remove the old node. If the return
|
||||
value of the visitor function is `None` the node will be removed
|
||||
from the previous location otherwise it's replaced with the return
|
||||
value. The return value may be the original node in which case no
|
||||
replacement takes place.
|
||||
"""
|
||||
|
||||
def generic_visit(self, node, *args, **kwargs):
|
||||
for field, old_value in node.iter_fields():
|
||||
if isinstance(old_value, list):
|
||||
new_values = []
|
||||
for value in old_value:
|
||||
if isinstance(value, Node):
|
||||
value = self.visit(value, *args, **kwargs)
|
||||
if value is None:
|
||||
continue
|
||||
elif not isinstance(value, Node):
|
||||
new_values.extend(value)
|
||||
continue
|
||||
new_values.append(value)
|
||||
old_value[:] = new_values
|
||||
elif isinstance(old_value, Node):
|
||||
new_node = self.visit(old_value, *args, **kwargs)
|
||||
if new_node is None:
|
||||
delattr(node, field)
|
||||
else:
|
||||
setattr(node, field, new_node)
|
||||
return node
|
||||
|
||||
def visit_list(self, node, *args, **kwargs):
|
||||
"""As transformers may return lists in some places this method
|
||||
can be used to enforce a list as return value.
|
||||
"""
|
||||
rv = self.visit(node, *args, **kwargs)
|
||||
if not isinstance(rv, list):
|
||||
rv = [rv]
|
||||
return rv
|
||||
Vendored
-305
@@ -1,305 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
markupsafe
|
||||
~~~~~~~~~~
|
||||
|
||||
Implements a Markup string.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
import string
|
||||
from collections import Mapping
|
||||
from markupsafe._compat import text_type, string_types, int_types, \
|
||||
unichr, iteritems, PY2
|
||||
|
||||
__version__ = "1.0"
|
||||
|
||||
__all__ = ['Markup', 'soft_unicode', 'escape', 'escape_silent']
|
||||
|
||||
|
||||
_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
|
||||
_entity_re = re.compile(r'&([^& ;]+);')
|
||||
|
||||
|
||||
class Markup(text_type):
|
||||
r"""Marks a string as being safe for inclusion in HTML/XML output without
|
||||
needing to be escaped. This implements the `__html__` interface a couple
|
||||
of frameworks and web applications use. :class:`Markup` is a direct
|
||||
subclass of `unicode` and provides all the methods of `unicode` just that
|
||||
it escapes arguments passed and always returns `Markup`.
|
||||
|
||||
The `escape` function returns markup objects so that double escaping can't
|
||||
happen.
|
||||
|
||||
The constructor of the :class:`Markup` class can be used for three
|
||||
different things: When passed an unicode object it's assumed to be safe,
|
||||
when passed an object with an HTML representation (has an `__html__`
|
||||
method) that representation is used, otherwise the object passed is
|
||||
converted into a unicode string and then assumed to be safe:
|
||||
|
||||
>>> Markup("Hello <em>World</em>!")
|
||||
Markup(u'Hello <em>World</em>!')
|
||||
>>> class Foo(object):
|
||||
... def __html__(self):
|
||||
... return '<a href="#">foo</a>'
|
||||
...
|
||||
>>> Markup(Foo())
|
||||
Markup(u'<a href="#">foo</a>')
|
||||
|
||||
If you want object passed being always treated as unsafe you can use the
|
||||
:meth:`escape` classmethod to create a :class:`Markup` object:
|
||||
|
||||
>>> Markup.escape("Hello <em>World</em>!")
|
||||
Markup(u'Hello <em>World</em>!')
|
||||
|
||||
Operations on a markup string are markup aware which means that all
|
||||
arguments are passed through the :func:`escape` function:
|
||||
|
||||
>>> em = Markup("<em>%s</em>")
|
||||
>>> em % "foo & bar"
|
||||
Markup(u'<em>foo & bar</em>')
|
||||
>>> strong = Markup("<strong>%(text)s</strong>")
|
||||
>>> strong % {'text': '<blink>hacker here</blink>'}
|
||||
Markup(u'<strong><blink>hacker here</blink></strong>')
|
||||
>>> Markup("<em>Hello</em> ") + "<foo>"
|
||||
Markup(u'<em>Hello</em> <foo>')
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def __new__(cls, base=u'', encoding=None, errors='strict'):
|
||||
if hasattr(base, '__html__'):
|
||||
base = base.__html__()
|
||||
if encoding is None:
|
||||
return text_type.__new__(cls, base)
|
||||
return text_type.__new__(cls, base, encoding, errors)
|
||||
|
||||
def __html__(self):
|
||||
return self
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, string_types) or hasattr(other, '__html__'):
|
||||
return self.__class__(super(Markup, self).__add__(self.escape(other)))
|
||||
return NotImplemented
|
||||
|
||||
def __radd__(self, other):
|
||||
if hasattr(other, '__html__') or isinstance(other, string_types):
|
||||
return self.escape(other).__add__(self)
|
||||
return NotImplemented
|
||||
|
||||
def __mul__(self, num):
|
||||
if isinstance(num, int_types):
|
||||
return self.__class__(text_type.__mul__(self, num))
|
||||
return NotImplemented
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __mod__(self, arg):
|
||||
if isinstance(arg, tuple):
|
||||
arg = tuple(_MarkupEscapeHelper(x, self.escape) for x in arg)
|
||||
else:
|
||||
arg = _MarkupEscapeHelper(arg, self.escape)
|
||||
return self.__class__(text_type.__mod__(self, arg))
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (
|
||||
self.__class__.__name__,
|
||||
text_type.__repr__(self)
|
||||
)
|
||||
|
||||
def join(self, seq):
|
||||
return self.__class__(text_type.join(self, map(self.escape, seq)))
|
||||
join.__doc__ = text_type.join.__doc__
|
||||
|
||||
def split(self, *args, **kwargs):
|
||||
return list(map(self.__class__, text_type.split(self, *args, **kwargs)))
|
||||
split.__doc__ = text_type.split.__doc__
|
||||
|
||||
def rsplit(self, *args, **kwargs):
|
||||
return list(map(self.__class__, text_type.rsplit(self, *args, **kwargs)))
|
||||
rsplit.__doc__ = text_type.rsplit.__doc__
|
||||
|
||||
def splitlines(self, *args, **kwargs):
|
||||
return list(map(self.__class__, text_type.splitlines(
|
||||
self, *args, **kwargs)))
|
||||
splitlines.__doc__ = text_type.splitlines.__doc__
|
||||
|
||||
def unescape(self):
|
||||
r"""Unescape markup again into an text_type string. This also resolves
|
||||
known HTML4 and XHTML entities:
|
||||
|
||||
>>> Markup("Main » <em>About</em>").unescape()
|
||||
u'Main \xbb <em>About</em>'
|
||||
"""
|
||||
from markupsafe._constants import HTML_ENTITIES
|
||||
def handle_match(m):
|
||||
name = m.group(1)
|
||||
if name in HTML_ENTITIES:
|
||||
return unichr(HTML_ENTITIES[name])
|
||||
try:
|
||||
if name[:2] in ('#x', '#X'):
|
||||
return unichr(int(name[2:], 16))
|
||||
elif name.startswith('#'):
|
||||
return unichr(int(name[1:]))
|
||||
except ValueError:
|
||||
pass
|
||||
# Don't modify unexpected input.
|
||||
return m.group()
|
||||
return _entity_re.sub(handle_match, text_type(self))
|
||||
|
||||
def striptags(self):
|
||||
r"""Unescape markup into an text_type string and strip all tags. This
|
||||
also resolves known HTML4 and XHTML entities. Whitespace is
|
||||
normalized to one:
|
||||
|
||||
>>> Markup("Main » <em>About</em>").striptags()
|
||||
u'Main \xbb About'
|
||||
"""
|
||||
stripped = u' '.join(_striptags_re.sub('', self).split())
|
||||
return Markup(stripped).unescape()
|
||||
|
||||
@classmethod
|
||||
def escape(cls, s):
|
||||
"""Escape the string. Works like :func:`escape` with the difference
|
||||
that for subclasses of :class:`Markup` this function would return the
|
||||
correct subclass.
|
||||
"""
|
||||
rv = escape(s)
|
||||
if rv.__class__ is not cls:
|
||||
return cls(rv)
|
||||
return rv
|
||||
|
||||
def make_simple_escaping_wrapper(name):
|
||||
orig = getattr(text_type, name)
|
||||
def func(self, *args, **kwargs):
|
||||
args = _escape_argspec(list(args), enumerate(args), self.escape)
|
||||
_escape_argspec(kwargs, iteritems(kwargs), self.escape)
|
||||
return self.__class__(orig(self, *args, **kwargs))
|
||||
func.__name__ = orig.__name__
|
||||
func.__doc__ = orig.__doc__
|
||||
return func
|
||||
|
||||
for method in '__getitem__', 'capitalize', \
|
||||
'title', 'lower', 'upper', 'replace', 'ljust', \
|
||||
'rjust', 'lstrip', 'rstrip', 'center', 'strip', \
|
||||
'translate', 'expandtabs', 'swapcase', 'zfill':
|
||||
locals()[method] = make_simple_escaping_wrapper(method)
|
||||
|
||||
# new in python 2.5
|
||||
if hasattr(text_type, 'partition'):
|
||||
def partition(self, sep):
|
||||
return tuple(map(self.__class__,
|
||||
text_type.partition(self, self.escape(sep))))
|
||||
def rpartition(self, sep):
|
||||
return tuple(map(self.__class__,
|
||||
text_type.rpartition(self, self.escape(sep))))
|
||||
|
||||
# new in python 2.6
|
||||
if hasattr(text_type, 'format'):
|
||||
def format(*args, **kwargs):
|
||||
self, args = args[0], args[1:]
|
||||
formatter = EscapeFormatter(self.escape)
|
||||
kwargs = _MagicFormatMapping(args, kwargs)
|
||||
return self.__class__(formatter.vformat(self, args, kwargs))
|
||||
|
||||
def __html_format__(self, format_spec):
|
||||
if format_spec:
|
||||
raise ValueError('Unsupported format specification '
|
||||
'for Markup.')
|
||||
return self
|
||||
|
||||
# not in python 3
|
||||
if hasattr(text_type, '__getslice__'):
|
||||
__getslice__ = make_simple_escaping_wrapper('__getslice__')
|
||||
|
||||
del method, make_simple_escaping_wrapper
|
||||
|
||||
|
||||
class _MagicFormatMapping(Mapping):
|
||||
"""This class implements a dummy wrapper to fix a bug in the Python
|
||||
standard library for string formatting.
|
||||
|
||||
See http://bugs.python.org/issue13598 for information about why
|
||||
this is necessary.
|
||||
"""
|
||||
|
||||
def __init__(self, args, kwargs):
|
||||
self._args = args
|
||||
self._kwargs = kwargs
|
||||
self._last_index = 0
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key == '':
|
||||
idx = self._last_index
|
||||
self._last_index += 1
|
||||
try:
|
||||
return self._args[idx]
|
||||
except LookupError:
|
||||
pass
|
||||
key = str(idx)
|
||||
return self._kwargs[key]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._kwargs)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._kwargs)
|
||||
|
||||
|
||||
if hasattr(text_type, 'format'):
|
||||
class EscapeFormatter(string.Formatter):
|
||||
|
||||
def __init__(self, escape):
|
||||
self.escape = escape
|
||||
|
||||
def format_field(self, value, format_spec):
|
||||
if hasattr(value, '__html_format__'):
|
||||
rv = value.__html_format__(format_spec)
|
||||
elif hasattr(value, '__html__'):
|
||||
if format_spec:
|
||||
raise ValueError('No format specification allowed '
|
||||
'when formatting an object with '
|
||||
'its __html__ method.')
|
||||
rv = value.__html__()
|
||||
else:
|
||||
# We need to make sure the format spec is unicode here as
|
||||
# otherwise the wrong callback methods are invoked. For
|
||||
# instance a byte string there would invoke __str__ and
|
||||
# not __unicode__.
|
||||
rv = string.Formatter.format_field(
|
||||
self, value, text_type(format_spec))
|
||||
return text_type(self.escape(rv))
|
||||
|
||||
|
||||
def _escape_argspec(obj, iterable, escape):
|
||||
"""Helper for various string-wrapped functions."""
|
||||
for key, value in iterable:
|
||||
if hasattr(value, '__html__') or isinstance(value, string_types):
|
||||
obj[key] = escape(value)
|
||||
return obj
|
||||
|
||||
|
||||
class _MarkupEscapeHelper(object):
|
||||
"""Helper for Markup.__mod__"""
|
||||
|
||||
def __init__(self, obj, escape):
|
||||
self.obj = obj
|
||||
self.escape = escape
|
||||
|
||||
__getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x], s.escape)
|
||||
__unicode__ = __str__ = lambda s: text_type(s.escape(s.obj))
|
||||
__repr__ = lambda s: str(s.escape(repr(s.obj)))
|
||||
__int__ = lambda s: int(s.obj)
|
||||
__float__ = lambda s: float(s.obj)
|
||||
|
||||
|
||||
# we have to import it down here as the speedups and native
|
||||
# modules imports the markup type which is define above.
|
||||
try:
|
||||
from markupsafe._speedups import escape, escape_silent, soft_unicode
|
||||
except ImportError:
|
||||
from markupsafe._native import escape, escape_silent, soft_unicode
|
||||
|
||||
if not PY2:
|
||||
soft_str = soft_unicode
|
||||
__all__.append('soft_str')
|
||||
Vendored
-26
@@ -1,26 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
markupsafe._compat
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Compatibility module for different Python versions.
|
||||
|
||||
:copyright: (c) 2013 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
|
||||
if not PY2:
|
||||
text_type = str
|
||||
string_types = (str,)
|
||||
unichr = chr
|
||||
int_types = (int,)
|
||||
iteritems = lambda x: iter(x.items())
|
||||
else:
|
||||
text_type = unicode
|
||||
string_types = (str, unicode)
|
||||
unichr = unichr
|
||||
int_types = (int, long)
|
||||
iteritems = lambda x: x.iteritems()
|
||||
-267
@@ -1,267 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
markupsafe._constants
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Highlevel implementation of the Markup string.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
|
||||
HTML_ENTITIES = {
|
||||
'AElig': 198,
|
||||
'Aacute': 193,
|
||||
'Acirc': 194,
|
||||
'Agrave': 192,
|
||||
'Alpha': 913,
|
||||
'Aring': 197,
|
||||
'Atilde': 195,
|
||||
'Auml': 196,
|
||||
'Beta': 914,
|
||||
'Ccedil': 199,
|
||||
'Chi': 935,
|
||||
'Dagger': 8225,
|
||||
'Delta': 916,
|
||||
'ETH': 208,
|
||||
'Eacute': 201,
|
||||
'Ecirc': 202,
|
||||
'Egrave': 200,
|
||||
'Epsilon': 917,
|
||||
'Eta': 919,
|
||||
'Euml': 203,
|
||||
'Gamma': 915,
|
||||
'Iacute': 205,
|
||||
'Icirc': 206,
|
||||
'Igrave': 204,
|
||||
'Iota': 921,
|
||||
'Iuml': 207,
|
||||
'Kappa': 922,
|
||||
'Lambda': 923,
|
||||
'Mu': 924,
|
||||
'Ntilde': 209,
|
||||
'Nu': 925,
|
||||
'OElig': 338,
|
||||
'Oacute': 211,
|
||||
'Ocirc': 212,
|
||||
'Ograve': 210,
|
||||
'Omega': 937,
|
||||
'Omicron': 927,
|
||||
'Oslash': 216,
|
||||
'Otilde': 213,
|
||||
'Ouml': 214,
|
||||
'Phi': 934,
|
||||
'Pi': 928,
|
||||
'Prime': 8243,
|
||||
'Psi': 936,
|
||||
'Rho': 929,
|
||||
'Scaron': 352,
|
||||
'Sigma': 931,
|
||||
'THORN': 222,
|
||||
'Tau': 932,
|
||||
'Theta': 920,
|
||||
'Uacute': 218,
|
||||
'Ucirc': 219,
|
||||
'Ugrave': 217,
|
||||
'Upsilon': 933,
|
||||
'Uuml': 220,
|
||||
'Xi': 926,
|
||||
'Yacute': 221,
|
||||
'Yuml': 376,
|
||||
'Zeta': 918,
|
||||
'aacute': 225,
|
||||
'acirc': 226,
|
||||
'acute': 180,
|
||||
'aelig': 230,
|
||||
'agrave': 224,
|
||||
'alefsym': 8501,
|
||||
'alpha': 945,
|
||||
'amp': 38,
|
||||
'and': 8743,
|
||||
'ang': 8736,
|
||||
'apos': 39,
|
||||
'aring': 229,
|
||||
'asymp': 8776,
|
||||
'atilde': 227,
|
||||
'auml': 228,
|
||||
'bdquo': 8222,
|
||||
'beta': 946,
|
||||
'brvbar': 166,
|
||||
'bull': 8226,
|
||||
'cap': 8745,
|
||||
'ccedil': 231,
|
||||
'cedil': 184,
|
||||
'cent': 162,
|
||||
'chi': 967,
|
||||
'circ': 710,
|
||||
'clubs': 9827,
|
||||
'cong': 8773,
|
||||
'copy': 169,
|
||||
'crarr': 8629,
|
||||
'cup': 8746,
|
||||
'curren': 164,
|
||||
'dArr': 8659,
|
||||
'dagger': 8224,
|
||||
'darr': 8595,
|
||||
'deg': 176,
|
||||
'delta': 948,
|
||||
'diams': 9830,
|
||||
'divide': 247,
|
||||
'eacute': 233,
|
||||
'ecirc': 234,
|
||||
'egrave': 232,
|
||||
'empty': 8709,
|
||||
'emsp': 8195,
|
||||
'ensp': 8194,
|
||||
'epsilon': 949,
|
||||
'equiv': 8801,
|
||||
'eta': 951,
|
||||
'eth': 240,
|
||||
'euml': 235,
|
||||
'euro': 8364,
|
||||
'exist': 8707,
|
||||
'fnof': 402,
|
||||
'forall': 8704,
|
||||
'frac12': 189,
|
||||
'frac14': 188,
|
||||
'frac34': 190,
|
||||
'frasl': 8260,
|
||||
'gamma': 947,
|
||||
'ge': 8805,
|
||||
'gt': 62,
|
||||
'hArr': 8660,
|
||||
'harr': 8596,
|
||||
'hearts': 9829,
|
||||
'hellip': 8230,
|
||||
'iacute': 237,
|
||||
'icirc': 238,
|
||||
'iexcl': 161,
|
||||
'igrave': 236,
|
||||
'image': 8465,
|
||||
'infin': 8734,
|
||||
'int': 8747,
|
||||
'iota': 953,
|
||||
'iquest': 191,
|
||||
'isin': 8712,
|
||||
'iuml': 239,
|
||||
'kappa': 954,
|
||||
'lArr': 8656,
|
||||
'lambda': 955,
|
||||
'lang': 9001,
|
||||
'laquo': 171,
|
||||
'larr': 8592,
|
||||
'lceil': 8968,
|
||||
'ldquo': 8220,
|
||||
'le': 8804,
|
||||
'lfloor': 8970,
|
||||
'lowast': 8727,
|
||||
'loz': 9674,
|
||||
'lrm': 8206,
|
||||
'lsaquo': 8249,
|
||||
'lsquo': 8216,
|
||||
'lt': 60,
|
||||
'macr': 175,
|
||||
'mdash': 8212,
|
||||
'micro': 181,
|
||||
'middot': 183,
|
||||
'minus': 8722,
|
||||
'mu': 956,
|
||||
'nabla': 8711,
|
||||
'nbsp': 160,
|
||||
'ndash': 8211,
|
||||
'ne': 8800,
|
||||
'ni': 8715,
|
||||
'not': 172,
|
||||
'notin': 8713,
|
||||
'nsub': 8836,
|
||||
'ntilde': 241,
|
||||
'nu': 957,
|
||||
'oacute': 243,
|
||||
'ocirc': 244,
|
||||
'oelig': 339,
|
||||
'ograve': 242,
|
||||
'oline': 8254,
|
||||
'omega': 969,
|
||||
'omicron': 959,
|
||||
'oplus': 8853,
|
||||
'or': 8744,
|
||||
'ordf': 170,
|
||||
'ordm': 186,
|
||||
'oslash': 248,
|
||||
'otilde': 245,
|
||||
'otimes': 8855,
|
||||
'ouml': 246,
|
||||
'para': 182,
|
||||
'part': 8706,
|
||||
'permil': 8240,
|
||||
'perp': 8869,
|
||||
'phi': 966,
|
||||
'pi': 960,
|
||||
'piv': 982,
|
||||
'plusmn': 177,
|
||||
'pound': 163,
|
||||
'prime': 8242,
|
||||
'prod': 8719,
|
||||
'prop': 8733,
|
||||
'psi': 968,
|
||||
'quot': 34,
|
||||
'rArr': 8658,
|
||||
'radic': 8730,
|
||||
'rang': 9002,
|
||||
'raquo': 187,
|
||||
'rarr': 8594,
|
||||
'rceil': 8969,
|
||||
'rdquo': 8221,
|
||||
'real': 8476,
|
||||
'reg': 174,
|
||||
'rfloor': 8971,
|
||||
'rho': 961,
|
||||
'rlm': 8207,
|
||||
'rsaquo': 8250,
|
||||
'rsquo': 8217,
|
||||
'sbquo': 8218,
|
||||
'scaron': 353,
|
||||
'sdot': 8901,
|
||||
'sect': 167,
|
||||
'shy': 173,
|
||||
'sigma': 963,
|
||||
'sigmaf': 962,
|
||||
'sim': 8764,
|
||||
'spades': 9824,
|
||||
'sub': 8834,
|
||||
'sube': 8838,
|
||||
'sum': 8721,
|
||||
'sup': 8835,
|
||||
'sup1': 185,
|
||||
'sup2': 178,
|
||||
'sup3': 179,
|
||||
'supe': 8839,
|
||||
'szlig': 223,
|
||||
'tau': 964,
|
||||
'there4': 8756,
|
||||
'theta': 952,
|
||||
'thetasym': 977,
|
||||
'thinsp': 8201,
|
||||
'thorn': 254,
|
||||
'tilde': 732,
|
||||
'times': 215,
|
||||
'trade': 8482,
|
||||
'uArr': 8657,
|
||||
'uacute': 250,
|
||||
'uarr': 8593,
|
||||
'ucirc': 251,
|
||||
'ugrave': 249,
|
||||
'uml': 168,
|
||||
'upsih': 978,
|
||||
'upsilon': 965,
|
||||
'uuml': 252,
|
||||
'weierp': 8472,
|
||||
'xi': 958,
|
||||
'yacute': 253,
|
||||
'yen': 165,
|
||||
'yuml': 255,
|
||||
'zeta': 950,
|
||||
'zwj': 8205,
|
||||
'zwnj': 8204
|
||||
}
|
||||
Vendored
-46
@@ -1,46 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
markupsafe._native
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Native Python implementation the C module is not compiled.
|
||||
|
||||
:copyright: (c) 2010 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from markupsafe import Markup
|
||||
from markupsafe._compat import text_type
|
||||
|
||||
|
||||
def escape(s):
|
||||
"""Convert the characters &, <, >, ' and " in string s to HTML-safe
|
||||
sequences. Use this if you need to display text that might contain
|
||||
such characters in HTML. Marks return value as markup string.
|
||||
"""
|
||||
if hasattr(s, '__html__'):
|
||||
return s.__html__()
|
||||
return Markup(text_type(s)
|
||||
.replace('&', '&')
|
||||
.replace('>', '>')
|
||||
.replace('<', '<')
|
||||
.replace("'", ''')
|
||||
.replace('"', '"')
|
||||
)
|
||||
|
||||
|
||||
def escape_silent(s):
|
||||
"""Like :func:`escape` but converts `None` into an empty
|
||||
markup string.
|
||||
"""
|
||||
if s is None:
|
||||
return Markup()
|
||||
return escape(s)
|
||||
|
||||
|
||||
def soft_unicode(s):
|
||||
"""Make a string unicode if it isn't already. That way a markup
|
||||
string is not converted back to unicode.
|
||||
"""
|
||||
if not isinstance(s, text_type):
|
||||
s = text_type(s)
|
||||
return s
|
||||
Vendored
-239
@@ -1,239 +0,0 @@
|
||||
/**
|
||||
* markupsafe._speedups
|
||||
* ~~~~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* This module implements functions for automatic escaping in C for better
|
||||
* performance.
|
||||
*
|
||||
* :copyright: (c) 2010 by Armin Ronacher.
|
||||
* :license: BSD.
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#define ESCAPED_CHARS_TABLE_SIZE 63
|
||||
#define UNICHR(x) (PyUnicode_AS_UNICODE((PyUnicodeObject*)PyUnicode_DecodeASCII(x, strlen(x), NULL)));
|
||||
|
||||
#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
|
||||
typedef int Py_ssize_t;
|
||||
#define PY_SSIZE_T_MAX INT_MAX
|
||||
#define PY_SSIZE_T_MIN INT_MIN
|
||||
#endif
|
||||
|
||||
|
||||
static PyObject* markup;
|
||||
static Py_ssize_t escaped_chars_delta_len[ESCAPED_CHARS_TABLE_SIZE];
|
||||
static Py_UNICODE *escaped_chars_repl[ESCAPED_CHARS_TABLE_SIZE];
|
||||
|
||||
static int
|
||||
init_constants(void)
|
||||
{
|
||||
PyObject *module;
|
||||
/* mapping of characters to replace */
|
||||
escaped_chars_repl['"'] = UNICHR(""");
|
||||
escaped_chars_repl['\''] = UNICHR("'");
|
||||
escaped_chars_repl['&'] = UNICHR("&");
|
||||
escaped_chars_repl['<'] = UNICHR("<");
|
||||
escaped_chars_repl['>'] = UNICHR(">");
|
||||
|
||||
/* lengths of those characters when replaced - 1 */
|
||||
memset(escaped_chars_delta_len, 0, sizeof (escaped_chars_delta_len));
|
||||
escaped_chars_delta_len['"'] = escaped_chars_delta_len['\''] = \
|
||||
escaped_chars_delta_len['&'] = 4;
|
||||
escaped_chars_delta_len['<'] = escaped_chars_delta_len['>'] = 3;
|
||||
|
||||
/* import markup type so that we can mark the return value */
|
||||
module = PyImport_ImportModule("markupsafe");
|
||||
if (!module)
|
||||
return 0;
|
||||
markup = PyObject_GetAttrString(module, "Markup");
|
||||
Py_DECREF(module);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
escape_unicode(PyUnicodeObject *in)
|
||||
{
|
||||
PyUnicodeObject *out;
|
||||
Py_UNICODE *inp = PyUnicode_AS_UNICODE(in);
|
||||
const Py_UNICODE *inp_end = PyUnicode_AS_UNICODE(in) + PyUnicode_GET_SIZE(in);
|
||||
Py_UNICODE *next_escp;
|
||||
Py_UNICODE *outp;
|
||||
Py_ssize_t delta=0, erepl=0, delta_len=0;
|
||||
|
||||
/* First we need to figure out how long the escaped string will be */
|
||||
while (*(inp) || inp < inp_end) {
|
||||
if (*inp < ESCAPED_CHARS_TABLE_SIZE) {
|
||||
delta += escaped_chars_delta_len[*inp];
|
||||
erepl += !!escaped_chars_delta_len[*inp];
|
||||
}
|
||||
++inp;
|
||||
}
|
||||
|
||||
/* Do we need to escape anything at all? */
|
||||
if (!erepl) {
|
||||
Py_INCREF(in);
|
||||
return (PyObject*)in;
|
||||
}
|
||||
|
||||
out = (PyUnicodeObject*)PyUnicode_FromUnicode(NULL, PyUnicode_GET_SIZE(in) + delta);
|
||||
if (!out)
|
||||
return NULL;
|
||||
|
||||
outp = PyUnicode_AS_UNICODE(out);
|
||||
inp = PyUnicode_AS_UNICODE(in);
|
||||
while (erepl-- > 0) {
|
||||
/* look for the next substitution */
|
||||
next_escp = inp;
|
||||
while (next_escp < inp_end) {
|
||||
if (*next_escp < ESCAPED_CHARS_TABLE_SIZE &&
|
||||
(delta_len = escaped_chars_delta_len[*next_escp])) {
|
||||
++delta_len;
|
||||
break;
|
||||
}
|
||||
++next_escp;
|
||||
}
|
||||
|
||||
if (next_escp > inp) {
|
||||
/* copy unescaped chars between inp and next_escp */
|
||||
Py_UNICODE_COPY(outp, inp, next_escp-inp);
|
||||
outp += next_escp - inp;
|
||||
}
|
||||
|
||||
/* escape 'next_escp' */
|
||||
Py_UNICODE_COPY(outp, escaped_chars_repl[*next_escp], delta_len);
|
||||
outp += delta_len;
|
||||
|
||||
inp = next_escp + 1;
|
||||
}
|
||||
if (inp < inp_end)
|
||||
Py_UNICODE_COPY(outp, inp, PyUnicode_GET_SIZE(in) - (inp - PyUnicode_AS_UNICODE(in)));
|
||||
|
||||
return (PyObject*)out;
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
escape(PyObject *self, PyObject *text)
|
||||
{
|
||||
PyObject *s = NULL, *rv = NULL, *html;
|
||||
|
||||
/* we don't have to escape integers, bools or floats */
|
||||
if (PyLong_CheckExact(text) ||
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
PyInt_CheckExact(text) ||
|
||||
#endif
|
||||
PyFloat_CheckExact(text) || PyBool_Check(text) ||
|
||||
text == Py_None)
|
||||
return PyObject_CallFunctionObjArgs(markup, text, NULL);
|
||||
|
||||
/* if the object has an __html__ method that performs the escaping */
|
||||
html = PyObject_GetAttrString(text, "__html__");
|
||||
if (html) {
|
||||
rv = PyObject_CallObject(html, NULL);
|
||||
Py_DECREF(html);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* otherwise make the object unicode if it isn't, then escape */
|
||||
PyErr_Clear();
|
||||
if (!PyUnicode_Check(text)) {
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
PyObject *unicode = PyObject_Unicode(text);
|
||||
#else
|
||||
PyObject *unicode = PyObject_Str(text);
|
||||
#endif
|
||||
if (!unicode)
|
||||
return NULL;
|
||||
s = escape_unicode((PyUnicodeObject*)unicode);
|
||||
Py_DECREF(unicode);
|
||||
}
|
||||
else
|
||||
s = escape_unicode((PyUnicodeObject*)text);
|
||||
|
||||
/* convert the unicode string into a markup object. */
|
||||
rv = PyObject_CallFunctionObjArgs(markup, (PyObject*)s, NULL);
|
||||
Py_DECREF(s);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
escape_silent(PyObject *self, PyObject *text)
|
||||
{
|
||||
if (text != Py_None)
|
||||
return escape(self, text);
|
||||
return PyObject_CallFunctionObjArgs(markup, NULL);
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
soft_unicode(PyObject *self, PyObject *s)
|
||||
{
|
||||
if (!PyUnicode_Check(s))
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
return PyObject_Unicode(s);
|
||||
#else
|
||||
return PyObject_Str(s);
|
||||
#endif
|
||||
Py_INCREF(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"escape", (PyCFunction)escape, METH_O,
|
||||
"escape(s) -> markup\n\n"
|
||||
"Convert the characters &, <, >, ', and \" in string s to HTML-safe\n"
|
||||
"sequences. Use this if you need to display text that might contain\n"
|
||||
"such characters in HTML. Marks return value as markup string."},
|
||||
{"escape_silent", (PyCFunction)escape_silent, METH_O,
|
||||
"escape_silent(s) -> markup\n\n"
|
||||
"Like escape but converts None to an empty string."},
|
||||
{"soft_unicode", (PyCFunction)soft_unicode, METH_O,
|
||||
"soft_unicode(object) -> string\n\n"
|
||||
"Make a string unicode if it isn't already. That way a markup\n"
|
||||
"string is not converted back to unicode."},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
#if PY_MAJOR_VERSION < 3
|
||||
|
||||
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
|
||||
#define PyMODINIT_FUNC void
|
||||
#endif
|
||||
PyMODINIT_FUNC
|
||||
init_speedups(void)
|
||||
{
|
||||
if (!init_constants())
|
||||
return;
|
||||
|
||||
Py_InitModule3("markupsafe._speedups", module_methods, "");
|
||||
}
|
||||
|
||||
#else /* Python 3.x module initialization */
|
||||
|
||||
static struct PyModuleDef module_definition = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"markupsafe._speedups",
|
||||
NULL,
|
||||
-1,
|
||||
module_methods,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit__speedups(void)
|
||||
{
|
||||
if (!init_constants())
|
||||
return NULL;
|
||||
|
||||
return PyModule_Create(&module_definition);
|
||||
}
|
||||
|
||||
#endif
|
||||
Vendored
-1209
File diff suppressed because it is too large
Load Diff
Vendored
-1280
File diff suppressed because it is too large
Load Diff
Vendored
-351
@@ -1,351 +0,0 @@
|
||||
'''This implements an ANSI (VT100) terminal emulator as a subclass of screen.
|
||||
|
||||
PEXPECT LICENSE
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
'''
|
||||
|
||||
# references:
|
||||
# http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
# http://www.retards.org/terminals/vt102.html
|
||||
# http://vt100.net/docs/vt102-ug/contents.html
|
||||
# http://vt100.net/docs/vt220-rm/
|
||||
# http://www.termsys.demon.co.uk/vtansi.htm
|
||||
|
||||
from . import screen
|
||||
from . import FSM
|
||||
import string
|
||||
|
||||
#
|
||||
# The 'Do.*' functions are helper functions for the ANSI class.
|
||||
#
|
||||
def DoEmit (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.write_ch(fsm.input_symbol)
|
||||
|
||||
def DoStartNumber (fsm):
|
||||
|
||||
fsm.memory.append (fsm.input_symbol)
|
||||
|
||||
def DoBuildNumber (fsm):
|
||||
|
||||
ns = fsm.memory.pop()
|
||||
ns = ns + fsm.input_symbol
|
||||
fsm.memory.append (ns)
|
||||
|
||||
def DoBackOne (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_back ()
|
||||
|
||||
def DoBack (fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_back (count)
|
||||
|
||||
def DoDownOne (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_down ()
|
||||
|
||||
def DoDown (fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_down (count)
|
||||
|
||||
def DoForwardOne (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_forward ()
|
||||
|
||||
def DoForward (fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_forward (count)
|
||||
|
||||
def DoUpReverse (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_up_reverse()
|
||||
|
||||
def DoUpOne (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_up ()
|
||||
|
||||
def DoUp (fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_up (count)
|
||||
|
||||
def DoHome (fsm):
|
||||
|
||||
c = int(fsm.memory.pop())
|
||||
r = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_home (r,c)
|
||||
|
||||
def DoHomeOrigin (fsm):
|
||||
|
||||
c = 1
|
||||
r = 1
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_home (r,c)
|
||||
|
||||
def DoEraseDown (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.erase_down()
|
||||
|
||||
def DoErase (fsm):
|
||||
|
||||
arg = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
if arg == 0:
|
||||
screen.erase_down()
|
||||
elif arg == 1:
|
||||
screen.erase_up()
|
||||
elif arg == 2:
|
||||
screen.erase_screen()
|
||||
|
||||
def DoEraseEndOfLine (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.erase_end_of_line()
|
||||
|
||||
def DoEraseLine (fsm):
|
||||
|
||||
arg = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
if arg == 0:
|
||||
screen.erase_end_of_line()
|
||||
elif arg == 1:
|
||||
screen.erase_start_of_line()
|
||||
elif arg == 2:
|
||||
screen.erase_line()
|
||||
|
||||
def DoEnableScroll (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.scroll_screen()
|
||||
|
||||
def DoCursorSave (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_save_attrs()
|
||||
|
||||
def DoCursorRestore (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_restore_attrs()
|
||||
|
||||
def DoScrollRegion (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
r2 = int(fsm.memory.pop())
|
||||
r1 = int(fsm.memory.pop())
|
||||
screen.scroll_screen_rows (r1,r2)
|
||||
|
||||
def DoMode (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
mode = fsm.memory.pop() # Should be 4
|
||||
# screen.setReplaceMode ()
|
||||
|
||||
def DoLog (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
fsm.memory = [screen]
|
||||
fout = open ('log', 'a')
|
||||
fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n')
|
||||
fout.close()
|
||||
|
||||
class term (screen.screen):
|
||||
|
||||
'''This class is an abstract, generic terminal.
|
||||
This does nothing. This is a placeholder that
|
||||
provides a common base class for other terminals
|
||||
such as an ANSI terminal. '''
|
||||
|
||||
def __init__ (self, r=24, c=80, *args, **kwargs):
|
||||
|
||||
screen.screen.__init__(self, r,c,*args,**kwargs)
|
||||
|
||||
class ANSI (term):
|
||||
'''This class implements an ANSI (VT100) terminal.
|
||||
It is a stream filter that recognizes ANSI terminal
|
||||
escape sequences and maintains the state of a screen object. '''
|
||||
|
||||
def __init__ (self, r=24,c=80,*args,**kwargs):
|
||||
|
||||
term.__init__(self,r,c,*args,**kwargs)
|
||||
|
||||
#self.screen = screen (24,80)
|
||||
self.state = FSM.FSM ('INIT',[self])
|
||||
self.state.set_default_transition (DoLog, 'INIT')
|
||||
self.state.add_transition_any ('INIT', DoEmit, 'INIT')
|
||||
self.state.add_transition ('\x1b', 'INIT', None, 'ESC')
|
||||
self.state.add_transition_any ('ESC', DoLog, 'INIT')
|
||||
self.state.add_transition ('(', 'ESC', None, 'G0SCS')
|
||||
self.state.add_transition (')', 'ESC', None, 'G1SCS')
|
||||
self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT')
|
||||
self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT')
|
||||
self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT')
|
||||
self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT')
|
||||
self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT')
|
||||
self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT')
|
||||
self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT')
|
||||
self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad.
|
||||
self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND')
|
||||
self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT')
|
||||
self.state.add_transition ('[', 'ESC', None, 'ELB')
|
||||
# ELB means Escape Left Bracket. That is ^[[
|
||||
self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT')
|
||||
self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT')
|
||||
self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT')
|
||||
self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT')
|
||||
self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT')
|
||||
self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT')
|
||||
self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT')
|
||||
self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT')
|
||||
self.state.add_transition ('m', 'ELB', self.do_sgr, 'INIT')
|
||||
self.state.add_transition ('?', 'ELB', None, 'MODECRAP')
|
||||
self.state.add_transition_list (string.digits, 'ELB', DoStartNumber, 'NUMBER_1')
|
||||
self.state.add_transition_list (string.digits, 'NUMBER_1', DoBuildNumber, 'NUMBER_1')
|
||||
self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT')
|
||||
self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT')
|
||||
self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT')
|
||||
self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT')
|
||||
self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT')
|
||||
self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT')
|
||||
self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT')
|
||||
### It gets worse... the 'm' code can have infinite number of
|
||||
### number;number;number before it. I've never seen more than two,
|
||||
### but the specs say it's allowed. crap!
|
||||
self.state.add_transition ('m', 'NUMBER_1', self.do_sgr, 'INIT')
|
||||
### LED control. Same implementation problem as 'm' code.
|
||||
self.state.add_transition ('q', 'NUMBER_1', self.do_decsca, 'INIT')
|
||||
|
||||
# \E[?47h switch to alternate screen
|
||||
# \E[?47l restores to normal screen from alternate screen.
|
||||
self.state.add_transition_list (string.digits, 'MODECRAP', DoStartNumber, 'MODECRAP_NUM')
|
||||
self.state.add_transition_list (string.digits, 'MODECRAP_NUM', DoBuildNumber, 'MODECRAP_NUM')
|
||||
self.state.add_transition ('l', 'MODECRAP_NUM', self.do_modecrap, 'INIT')
|
||||
self.state.add_transition ('h', 'MODECRAP_NUM', self.do_modecrap, 'INIT')
|
||||
|
||||
#RM Reset Mode Esc [ Ps l none
|
||||
self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON')
|
||||
self.state.add_transition_any ('SEMICOLON', DoLog, 'INIT')
|
||||
self.state.add_transition_list (string.digits, 'SEMICOLON', DoStartNumber, 'NUMBER_2')
|
||||
self.state.add_transition_list (string.digits, 'NUMBER_2', DoBuildNumber, 'NUMBER_2')
|
||||
self.state.add_transition_any ('NUMBER_2', DoLog, 'INIT')
|
||||
self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT')
|
||||
self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT')
|
||||
self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT')
|
||||
### It gets worse... the 'm' code can have infinite number of
|
||||
### number;number;number before it. I've never seen more than two,
|
||||
### but the specs say it's allowed. crap!
|
||||
self.state.add_transition ('m', 'NUMBER_2', self.do_sgr, 'INIT')
|
||||
### LED control. Same problem as 'm' code.
|
||||
self.state.add_transition ('q', 'NUMBER_2', self.do_decsca, 'INIT')
|
||||
self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X')
|
||||
|
||||
# Create a state for 'q' and 'm' which allows an infinite number of ignored numbers
|
||||
self.state.add_transition_any ('SEMICOLON_X', DoLog, 'INIT')
|
||||
self.state.add_transition_list (string.digits, 'SEMICOLON_X', DoStartNumber, 'NUMBER_X')
|
||||
self.state.add_transition_list (string.digits, 'NUMBER_X', DoBuildNumber, 'NUMBER_X')
|
||||
self.state.add_transition_any ('NUMBER_X', DoLog, 'INIT')
|
||||
self.state.add_transition ('m', 'NUMBER_X', self.do_sgr, 'INIT')
|
||||
self.state.add_transition ('q', 'NUMBER_X', self.do_decsca, 'INIT')
|
||||
self.state.add_transition (';', 'NUMBER_X', None, 'SEMICOLON_X')
|
||||
|
||||
def process (self, c):
|
||||
"""Process a single character. Called by :meth:`write`."""
|
||||
if isinstance(c, bytes):
|
||||
c = self._decode(c)
|
||||
self.state.process(c)
|
||||
|
||||
def process_list (self, l):
|
||||
|
||||
self.write(l)
|
||||
|
||||
def write (self, s):
|
||||
"""Process text, writing it to the virtual screen while handling
|
||||
ANSI escape codes.
|
||||
"""
|
||||
if isinstance(s, bytes):
|
||||
s = self._decode(s)
|
||||
for c in s:
|
||||
self.process(c)
|
||||
|
||||
def flush (self):
|
||||
pass
|
||||
|
||||
def write_ch (self, ch):
|
||||
'''This puts a character at the current cursor position. The cursor
|
||||
position is moved forward with wrap-around, but no scrolling is done if
|
||||
the cursor hits the lower-right corner of the screen. '''
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
#\r and \n both produce a call to cr() and lf(), respectively.
|
||||
ch = ch[0]
|
||||
|
||||
if ch == u'\r':
|
||||
self.cr()
|
||||
return
|
||||
if ch == u'\n':
|
||||
self.crlf()
|
||||
return
|
||||
if ch == chr(screen.BS):
|
||||
self.cursor_back()
|
||||
return
|
||||
self.put_abs(self.cur_r, self.cur_c, ch)
|
||||
old_r = self.cur_r
|
||||
old_c = self.cur_c
|
||||
self.cursor_forward()
|
||||
if old_c == self.cur_c:
|
||||
self.cursor_down()
|
||||
if old_r != self.cur_r:
|
||||
self.cursor_home (self.cur_r, 1)
|
||||
else:
|
||||
self.scroll_up ()
|
||||
self.cursor_home (self.cur_r, 1)
|
||||
self.erase_line()
|
||||
|
||||
def do_sgr (self, fsm):
|
||||
'''Select Graphic Rendition, e.g. color. '''
|
||||
screen = fsm.memory[0]
|
||||
fsm.memory = [screen]
|
||||
|
||||
def do_decsca (self, fsm):
|
||||
'''Select character protection attribute. '''
|
||||
screen = fsm.memory[0]
|
||||
fsm.memory = [screen]
|
||||
|
||||
def do_modecrap (self, fsm):
|
||||
'''Handler for \x1b[?<number>h and \x1b[?<number>l. If anyone
|
||||
wanted to actually use these, they'd need to add more states to the
|
||||
FSM rather than just improve or override this method. '''
|
||||
screen = fsm.memory[0]
|
||||
fsm.memory = [screen]
|
||||
Vendored
-334
@@ -1,334 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''This module implements a Finite State Machine (FSM). In addition to state
|
||||
this FSM also maintains a user defined "memory". So this FSM can be used as a
|
||||
Push-down Automata (PDA) since a PDA is a FSM + memory.
|
||||
|
||||
The following describes how the FSM works, but you will probably also need to
|
||||
see the example function to understand how the FSM is used in practice.
|
||||
|
||||
You define an FSM by building tables of transitions. For a given input symbol
|
||||
the process() method uses these tables to decide what action to call and what
|
||||
the next state will be. The FSM has a table of transitions that associate:
|
||||
|
||||
(input_symbol, current_state) --> (action, next_state)
|
||||
|
||||
Where "action" is a function you define. The symbols and states can be any
|
||||
objects. You use the add_transition() and add_transition_list() methods to add
|
||||
to the transition table. The FSM also has a table of transitions that
|
||||
associate:
|
||||
|
||||
(current_state) --> (action, next_state)
|
||||
|
||||
You use the add_transition_any() method to add to this transition table. The
|
||||
FSM also has one default transition that is not associated with any specific
|
||||
input_symbol or state. You use the set_default_transition() method to set the
|
||||
default transition.
|
||||
|
||||
When an action function is called it is passed a reference to the FSM. The
|
||||
action function may then access attributes of the FSM such as input_symbol,
|
||||
current_state, or "memory". The "memory" attribute can be any object that you
|
||||
want to pass along to the action functions. It is not used by the FSM itself.
|
||||
For parsing you would typically pass a list to be used as a stack.
|
||||
|
||||
The processing sequence is as follows. The process() method is given an
|
||||
input_symbol to process. The FSM will search the table of transitions that
|
||||
associate:
|
||||
|
||||
(input_symbol, current_state) --> (action, next_state)
|
||||
|
||||
If the pair (input_symbol, current_state) is found then process() will call the
|
||||
associated action function and then set the current state to the next_state.
|
||||
|
||||
If the FSM cannot find a match for (input_symbol, current_state) it will then
|
||||
search the table of transitions that associate:
|
||||
|
||||
(current_state) --> (action, next_state)
|
||||
|
||||
If the current_state is found then the process() method will call the
|
||||
associated action function and then set the current state to the next_state.
|
||||
Notice that this table lacks an input_symbol. It lets you define transitions
|
||||
for a current_state and ANY input_symbol. Hence, it is called the "any" table.
|
||||
Remember, it is always checked after first searching the table for a specific
|
||||
(input_symbol, current_state).
|
||||
|
||||
For the case where the FSM did not match either of the previous two cases the
|
||||
FSM will try to use the default transition. If the default transition is
|
||||
defined then the process() method will call the associated action function and
|
||||
then set the current state to the next_state. This lets you define a default
|
||||
transition as a catch-all case. You can think of it as an exception handler.
|
||||
There can be only one default transition.
|
||||
|
||||
Finally, if none of the previous cases are defined for an input_symbol and
|
||||
current_state then the FSM will raise an exception. This may be desirable, but
|
||||
you can always prevent this just by defining a default transition.
|
||||
|
||||
Noah Spurrier 20020822
|
||||
|
||||
PEXPECT LICENSE
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
'''
|
||||
|
||||
class ExceptionFSM(Exception):
|
||||
|
||||
'''This is the FSM Exception class.'''
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return 'ExceptionFSM: ' + str(self.value)
|
||||
|
||||
class FSM:
|
||||
|
||||
'''This is a Finite State Machine (FSM).
|
||||
'''
|
||||
|
||||
def __init__(self, initial_state, memory=None):
|
||||
|
||||
'''This creates the FSM. You set the initial state here. The "memory"
|
||||
attribute is any object that you want to pass along to the action
|
||||
functions. It is not used by the FSM. For parsing you would typically
|
||||
pass a list to be used as a stack. '''
|
||||
|
||||
# Map (input_symbol, current_state) --> (action, next_state).
|
||||
self.state_transitions = {}
|
||||
# Map (current_state) --> (action, next_state).
|
||||
self.state_transitions_any = {}
|
||||
self.default_transition = None
|
||||
|
||||
self.input_symbol = None
|
||||
self.initial_state = initial_state
|
||||
self.current_state = self.initial_state
|
||||
self.next_state = None
|
||||
self.action = None
|
||||
self.memory = memory
|
||||
|
||||
def reset (self):
|
||||
|
||||
'''This sets the current_state to the initial_state and sets
|
||||
input_symbol to None. The initial state was set by the constructor
|
||||
__init__(). '''
|
||||
|
||||
self.current_state = self.initial_state
|
||||
self.input_symbol = None
|
||||
|
||||
def add_transition (self, input_symbol, state, action=None, next_state=None):
|
||||
|
||||
'''This adds a transition that associates:
|
||||
|
||||
(input_symbol, current_state) --> (action, next_state)
|
||||
|
||||
The action may be set to None in which case the process() method will
|
||||
ignore the action and only set the next_state. The next_state may be
|
||||
set to None in which case the current state will be unchanged.
|
||||
|
||||
You can also set transitions for a list of symbols by using
|
||||
add_transition_list(). '''
|
||||
|
||||
if next_state is None:
|
||||
next_state = state
|
||||
self.state_transitions[(input_symbol, state)] = (action, next_state)
|
||||
|
||||
def add_transition_list (self, list_input_symbols, state, action=None, next_state=None):
|
||||
|
||||
'''This adds the same transition for a list of input symbols.
|
||||
You can pass a list or a string. Note that it is handy to use
|
||||
string.digits, string.whitespace, string.letters, etc. to add
|
||||
transitions that match character classes.
|
||||
|
||||
The action may be set to None in which case the process() method will
|
||||
ignore the action and only set the next_state. The next_state may be
|
||||
set to None in which case the current state will be unchanged. '''
|
||||
|
||||
if next_state is None:
|
||||
next_state = state
|
||||
for input_symbol in list_input_symbols:
|
||||
self.add_transition (input_symbol, state, action, next_state)
|
||||
|
||||
def add_transition_any (self, state, action=None, next_state=None):
|
||||
|
||||
'''This adds a transition that associates:
|
||||
|
||||
(current_state) --> (action, next_state)
|
||||
|
||||
That is, any input symbol will match the current state.
|
||||
The process() method checks the "any" state associations after it first
|
||||
checks for an exact match of (input_symbol, current_state).
|
||||
|
||||
The action may be set to None in which case the process() method will
|
||||
ignore the action and only set the next_state. The next_state may be
|
||||
set to None in which case the current state will be unchanged. '''
|
||||
|
||||
if next_state is None:
|
||||
next_state = state
|
||||
self.state_transitions_any [state] = (action, next_state)
|
||||
|
||||
def set_default_transition (self, action, next_state):
|
||||
|
||||
'''This sets the default transition. This defines an action and
|
||||
next_state if the FSM cannot find the input symbol and the current
|
||||
state in the transition list and if the FSM cannot find the
|
||||
current_state in the transition_any list. This is useful as a final
|
||||
fall-through state for catching errors and undefined states.
|
||||
|
||||
The default transition can be removed by setting the attribute
|
||||
default_transition to None. '''
|
||||
|
||||
self.default_transition = (action, next_state)
|
||||
|
||||
def get_transition (self, input_symbol, state):
|
||||
|
||||
'''This returns (action, next state) given an input_symbol and state.
|
||||
This does not modify the FSM state, so calling this method has no side
|
||||
effects. Normally you do not call this method directly. It is called by
|
||||
process().
|
||||
|
||||
The sequence of steps to check for a defined transition goes from the
|
||||
most specific to the least specific.
|
||||
|
||||
1. Check state_transitions[] that match exactly the tuple,
|
||||
(input_symbol, state)
|
||||
|
||||
2. Check state_transitions_any[] that match (state)
|
||||
In other words, match a specific state and ANY input_symbol.
|
||||
|
||||
3. Check if the default_transition is defined.
|
||||
This catches any input_symbol and any state.
|
||||
This is a handler for errors, undefined states, or defaults.
|
||||
|
||||
4. No transition was defined. If we get here then raise an exception.
|
||||
'''
|
||||
|
||||
if (input_symbol, state) in self.state_transitions:
|
||||
return self.state_transitions[(input_symbol, state)]
|
||||
elif state in self.state_transitions_any:
|
||||
return self.state_transitions_any[state]
|
||||
elif self.default_transition is not None:
|
||||
return self.default_transition
|
||||
else:
|
||||
raise ExceptionFSM ('Transition is undefined: (%s, %s).' %
|
||||
(str(input_symbol), str(state)) )
|
||||
|
||||
def process (self, input_symbol):
|
||||
|
||||
'''This is the main method that you call to process input. This may
|
||||
cause the FSM to change state and call an action. This method calls
|
||||
get_transition() to find the action and next_state associated with the
|
||||
input_symbol and current_state. If the action is None then the action
|
||||
is not called and only the current state is changed. This method
|
||||
processes one complete input symbol. You can process a list of symbols
|
||||
(or a string) by calling process_list(). '''
|
||||
|
||||
self.input_symbol = input_symbol
|
||||
(self.action, self.next_state) = self.get_transition (self.input_symbol, self.current_state)
|
||||
if self.action is not None:
|
||||
self.action (self)
|
||||
self.current_state = self.next_state
|
||||
self.next_state = None
|
||||
|
||||
def process_list (self, input_symbols):
|
||||
|
||||
'''This takes a list and sends each element to process(). The list may
|
||||
be a string or any iterable object. '''
|
||||
|
||||
for s in input_symbols:
|
||||
self.process (s)
|
||||
|
||||
##############################################################################
|
||||
# The following is an example that demonstrates the use of the FSM class to
|
||||
# process an RPN expression. Run this module from the command line. You will
|
||||
# get a prompt > for input. Enter an RPN Expression. Numbers may be integers.
|
||||
# Operators are * / + - Use the = sign to evaluate and print the expression.
|
||||
# For example:
|
||||
#
|
||||
# 167 3 2 2 * * * 1 - =
|
||||
#
|
||||
# will print:
|
||||
#
|
||||
# 2003
|
||||
##############################################################################
|
||||
|
||||
import sys
|
||||
import string
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
|
||||
#
|
||||
# These define the actions.
|
||||
# Note that "memory" is a list being used as a stack.
|
||||
#
|
||||
|
||||
def BeginBuildNumber (fsm):
|
||||
fsm.memory.append (fsm.input_symbol)
|
||||
|
||||
def BuildNumber (fsm):
|
||||
s = fsm.memory.pop ()
|
||||
s = s + fsm.input_symbol
|
||||
fsm.memory.append (s)
|
||||
|
||||
def EndBuildNumber (fsm):
|
||||
s = fsm.memory.pop ()
|
||||
fsm.memory.append (int(s))
|
||||
|
||||
def DoOperator (fsm):
|
||||
ar = fsm.memory.pop()
|
||||
al = fsm.memory.pop()
|
||||
if fsm.input_symbol == '+':
|
||||
fsm.memory.append (al + ar)
|
||||
elif fsm.input_symbol == '-':
|
||||
fsm.memory.append (al - ar)
|
||||
elif fsm.input_symbol == '*':
|
||||
fsm.memory.append (al * ar)
|
||||
elif fsm.input_symbol == '/':
|
||||
fsm.memory.append (al / ar)
|
||||
|
||||
def DoEqual (fsm):
|
||||
print(str(fsm.memory.pop()))
|
||||
|
||||
def Error (fsm):
|
||||
print('That does not compute.')
|
||||
print(str(fsm.input_symbol))
|
||||
|
||||
def main():
|
||||
|
||||
'''This is where the example starts and the FSM state transitions are
|
||||
defined. Note that states are strings (such as 'INIT'). This is not
|
||||
necessary, but it makes the example easier to read. '''
|
||||
|
||||
f = FSM ('INIT', [])
|
||||
f.set_default_transition (Error, 'INIT')
|
||||
f.add_transition_any ('INIT', None, 'INIT')
|
||||
f.add_transition ('=', 'INIT', DoEqual, 'INIT')
|
||||
f.add_transition_list (string.digits, 'INIT', BeginBuildNumber, 'BUILDING_NUMBER')
|
||||
f.add_transition_list (string.digits, 'BUILDING_NUMBER', BuildNumber, 'BUILDING_NUMBER')
|
||||
f.add_transition_list (string.whitespace, 'BUILDING_NUMBER', EndBuildNumber, 'INIT')
|
||||
f.add_transition_list ('+-*/', 'INIT', DoOperator, 'INIT')
|
||||
|
||||
print()
|
||||
print('Enter an RPN Expression.')
|
||||
print('Numbers may be integers. Operators are * / + -')
|
||||
print('Use the = sign to evaluate and print the expression.')
|
||||
print('For example: ')
|
||||
print(' 167 3 2 2 * * * 1 - =')
|
||||
inputstr = (input if PY3 else raw_input)('> ') # analysis:ignore
|
||||
f.process_list(inputstr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Vendored
-85
@@ -1,85 +0,0 @@
|
||||
'''Pexpect is a Python module for spawning child applications and controlling
|
||||
them automatically. Pexpect can be used for automating interactive applications
|
||||
such as ssh, ftp, passwd, telnet, etc. It can be used to a automate setup
|
||||
scripts for duplicating software package installations on different servers. It
|
||||
can be used for automated software testing. Pexpect is in the spirit of Don
|
||||
Libes' Expect, but Pexpect is pure Python. Other Expect-like modules for Python
|
||||
require TCL and Expect or require C extensions to be compiled. Pexpect does not
|
||||
use C, Expect, or TCL extensions. It should work on any platform that supports
|
||||
the standard Python pty module. The Pexpect interface focuses on ease of use so
|
||||
that simple tasks are easy.
|
||||
|
||||
There are two main interfaces to the Pexpect system; these are the function,
|
||||
run() and the class, spawn. The spawn class is more powerful. The run()
|
||||
function is simpler than spawn, and is good for quickly calling program. When
|
||||
you call the run() function it executes a given program and then returns the
|
||||
output. This is a handy replacement for os.system().
|
||||
|
||||
For example::
|
||||
|
||||
pexpect.run('ls -la')
|
||||
|
||||
The spawn class is the more powerful interface to the Pexpect system. You can
|
||||
use this to spawn a child program then interact with it by sending input and
|
||||
expecting responses (waiting for patterns in the child's output).
|
||||
|
||||
For example::
|
||||
|
||||
child = pexpect.spawn('scp foo user@example.com:.')
|
||||
child.expect('Password:')
|
||||
child.sendline(mypassword)
|
||||
|
||||
This works even for commands that ask for passwords or other input outside of
|
||||
the normal stdio streams. For example, ssh reads input directly from the TTY
|
||||
device which bypasses stdin.
|
||||
|
||||
Credits: Noah Spurrier, Richard Holden, Marco Molteni, Kimberley Burchett,
|
||||
Robert Stone, Hartmut Goebel, Chad Schroeder, Erick Tryzelaar, Dave Kirby, Ids
|
||||
vander Molen, George Todd, Noel Taylor, Nicolas D. Cesar, Alexander Gattin,
|
||||
Jacques-Etienne Baudoux, Geoffrey Marshall, Francisco Lourenco, Glen Mabey,
|
||||
Karthik Gurusamy, Fernando Perez, Corey Minyard, Jon Cohen, Guillaume
|
||||
Chazarain, Andrew Ryan, Nick Craig-Wood, Andrew Stone, Jorgen Grahn, John
|
||||
Spiegel, Jan Grant, and Shane Kerr. Let me know if I forgot anyone.
|
||||
|
||||
Pexpect is free, open source, and all that good stuff.
|
||||
http://pexpect.sourceforge.net/
|
||||
|
||||
PEXPECT LICENSE
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
'''
|
||||
|
||||
import sys
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
|
||||
from .exceptions import ExceptionPexpect, EOF, TIMEOUT
|
||||
from .utils import split_command_line, which, is_executable_file
|
||||
from .expect import Expecter, searcher_re, searcher_string
|
||||
|
||||
if sys.platform != 'win32':
|
||||
# On Unix, these are available at the top level for backwards compatibility
|
||||
from .pty_spawn import spawn, spawnu
|
||||
from .run import run, runu
|
||||
|
||||
__version__ = '4.2.1'
|
||||
__revision__ = ''
|
||||
__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
|
||||
'which', 'split_command_line', '__version__', '__revision__']
|
||||
|
||||
|
||||
|
||||
# vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent :
|
||||
Vendored
-78
@@ -1,78 +0,0 @@
|
||||
import asyncio
|
||||
import errno
|
||||
|
||||
from pexpect import EOF
|
||||
|
||||
@asyncio.coroutine
|
||||
def expect_async(expecter, timeout=None):
|
||||
# First process data that was previously read - if it maches, we don't need
|
||||
# async stuff.
|
||||
previously_read = expecter.spawn.buffer
|
||||
expecter.spawn.buffer = expecter.spawn.string_type()
|
||||
idx = expecter.new_data(previously_read)
|
||||
if idx is not None:
|
||||
return idx
|
||||
|
||||
transport, pw = yield from asyncio.get_event_loop()\
|
||||
.connect_read_pipe(lambda: PatternWaiter(expecter), expecter.spawn)
|
||||
|
||||
try:
|
||||
return (yield from asyncio.wait_for(pw.fut, timeout))
|
||||
except asyncio.TimeoutError as e:
|
||||
transport.pause_reading()
|
||||
return expecter.timeout(e)
|
||||
|
||||
class PatternWaiter(asyncio.Protocol):
|
||||
transport = None
|
||||
def __init__(self, expecter):
|
||||
self.expecter = expecter
|
||||
self.fut = asyncio.Future()
|
||||
|
||||
def found(self, result):
|
||||
if not self.fut.done():
|
||||
self.fut.set_result(result)
|
||||
self.transport.pause_reading()
|
||||
|
||||
def error(self, exc):
|
||||
if not self.fut.done():
|
||||
self.fut.set_exception(exc)
|
||||
self.transport.pause_reading()
|
||||
|
||||
def connection_made(self, transport):
|
||||
self.transport = transport
|
||||
|
||||
def data_received(self, data):
|
||||
spawn = self.expecter.spawn
|
||||
s = spawn._decoder.decode(data)
|
||||
spawn._log(s, 'read')
|
||||
|
||||
if self.fut.done():
|
||||
spawn.buffer += s
|
||||
return
|
||||
|
||||
try:
|
||||
index = self.expecter.new_data(s)
|
||||
if index is not None:
|
||||
# Found a match
|
||||
self.found(index)
|
||||
except Exception as e:
|
||||
self.expecter.errored()
|
||||
self.error(e)
|
||||
|
||||
def eof_received(self):
|
||||
# N.B. If this gets called, async will close the pipe (the spawn object)
|
||||
# for us
|
||||
try:
|
||||
self.expecter.spawn.flag_eof = True
|
||||
index = self.expecter.eof()
|
||||
except EOF as e:
|
||||
self.error(e)
|
||||
else:
|
||||
self.found(index)
|
||||
|
||||
def connection_lost(self, exc):
|
||||
if isinstance(exc, OSError) and exc.errno == errno.EIO:
|
||||
# We may get here without eof_received being called, e.g on Linux
|
||||
self.eof_received()
|
||||
elif exc is not None:
|
||||
self.error(exc)
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
source /etc/bash.bashrc
|
||||
source ~/.bashrc
|
||||
|
||||
# Reset PS1 so pexpect can find it
|
||||
PS1="$"
|
||||
Vendored
-35
@@ -1,35 +0,0 @@
|
||||
"""Exception classes used by Pexpect"""
|
||||
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
class ExceptionPexpect(Exception):
|
||||
'''Base class for all exceptions raised by this module.
|
||||
'''
|
||||
|
||||
def __init__(self, value):
|
||||
super(ExceptionPexpect, self).__init__(value)
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
def get_trace(self):
|
||||
'''This returns an abbreviated stack trace with lines that only concern
|
||||
the caller. In other words, the stack trace inside the Pexpect module
|
||||
is not included. '''
|
||||
|
||||
tblist = traceback.extract_tb(sys.exc_info()[2])
|
||||
tblist = [item for item in tblist if ('pexpect/__init__' not in item[0])
|
||||
and ('pexpect/expect' not in item[0])]
|
||||
tblist = traceback.format_list(tblist)
|
||||
return ''.join(tblist)
|
||||
|
||||
|
||||
class EOF(ExceptionPexpect):
|
||||
'''Raised when EOF is read from a child.
|
||||
This usually means the child has exited.'''
|
||||
|
||||
|
||||
class TIMEOUT(ExceptionPexpect):
|
||||
'''Raised when a read time exceeds the timeout. '''
|
||||
Vendored
-300
@@ -1,300 +0,0 @@
|
||||
import time
|
||||
|
||||
from .exceptions import EOF, TIMEOUT
|
||||
|
||||
class Expecter(object):
|
||||
def __init__(self, spawn, searcher, searchwindowsize=-1):
|
||||
self.spawn = spawn
|
||||
self.searcher = searcher
|
||||
if searchwindowsize == -1:
|
||||
searchwindowsize = spawn.searchwindowsize
|
||||
self.searchwindowsize = searchwindowsize
|
||||
|
||||
def new_data(self, data):
|
||||
spawn = self.spawn
|
||||
searcher = self.searcher
|
||||
|
||||
incoming = spawn.buffer + data
|
||||
freshlen = len(data)
|
||||
index = searcher.search(incoming, freshlen, self.searchwindowsize)
|
||||
if index >= 0:
|
||||
spawn.buffer = incoming[searcher.end:]
|
||||
spawn.before = incoming[: searcher.start]
|
||||
spawn.after = incoming[searcher.start: searcher.end]
|
||||
spawn.match = searcher.match
|
||||
spawn.match_index = index
|
||||
# Found a match
|
||||
return index
|
||||
|
||||
spawn.buffer = incoming
|
||||
|
||||
def eof(self, err=None):
|
||||
spawn = self.spawn
|
||||
from . import EOF
|
||||
|
||||
spawn.before = spawn.buffer
|
||||
spawn.buffer = spawn.string_type()
|
||||
spawn.after = EOF
|
||||
index = self.searcher.eof_index
|
||||
if index >= 0:
|
||||
spawn.match = EOF
|
||||
spawn.match_index = index
|
||||
return index
|
||||
else:
|
||||
spawn.match = None
|
||||
spawn.match_index = None
|
||||
msg = str(spawn)
|
||||
msg += '\nsearcher: %s' % self.searcher
|
||||
if err is not None:
|
||||
msg = str(err) + '\n' + msg
|
||||
raise EOF(msg)
|
||||
|
||||
def timeout(self, err=None):
|
||||
spawn = self.spawn
|
||||
from . import TIMEOUT
|
||||
|
||||
spawn.before = spawn.buffer
|
||||
spawn.after = TIMEOUT
|
||||
index = self.searcher.timeout_index
|
||||
if index >= 0:
|
||||
spawn.match = TIMEOUT
|
||||
spawn.match_index = index
|
||||
return index
|
||||
else:
|
||||
spawn.match = None
|
||||
spawn.match_index = None
|
||||
msg = str(spawn)
|
||||
msg += '\nsearcher: %s' % self.searcher
|
||||
if err is not None:
|
||||
msg = str(err) + '\n' + msg
|
||||
raise TIMEOUT(msg)
|
||||
|
||||
def errored(self):
|
||||
spawn = self.spawn
|
||||
spawn.before = spawn.buffer
|
||||
spawn.after = None
|
||||
spawn.match = None
|
||||
spawn.match_index = None
|
||||
|
||||
def expect_loop(self, timeout=-1):
|
||||
"""Blocking expect"""
|
||||
spawn = self.spawn
|
||||
from . import EOF, TIMEOUT
|
||||
|
||||
if timeout is not None:
|
||||
end_time = time.time() + timeout
|
||||
|
||||
try:
|
||||
incoming = spawn.buffer
|
||||
spawn.buffer = spawn.string_type() # Treat buffer as new data
|
||||
while True:
|
||||
idx = self.new_data(incoming)
|
||||
# Keep reading until exception or return.
|
||||
if idx is not None:
|
||||
return idx
|
||||
# No match at this point
|
||||
if (timeout is not None) and (timeout < 0):
|
||||
return self.timeout()
|
||||
# Still have time left, so read more data
|
||||
incoming = spawn.read_nonblocking(spawn.maxread, timeout)
|
||||
if self.spawn.delayafterread is not None:
|
||||
time.sleep(self.spawn.delayafterread)
|
||||
if timeout is not None:
|
||||
timeout = end_time - time.time()
|
||||
except EOF as e:
|
||||
return self.eof(e)
|
||||
except TIMEOUT as e:
|
||||
return self.timeout(e)
|
||||
except:
|
||||
self.errored()
|
||||
raise
|
||||
|
||||
|
||||
class searcher_string(object):
|
||||
'''This is a plain string search helper for the spawn.expect_any() method.
|
||||
This helper class is for speed. For more powerful regex patterns
|
||||
see the helper class, searcher_re.
|
||||
|
||||
Attributes:
|
||||
|
||||
eof_index - index of EOF, or -1
|
||||
timeout_index - index of TIMEOUT, or -1
|
||||
|
||||
After a successful match by the search() method the following attributes
|
||||
are available:
|
||||
|
||||
start - index into the buffer, first byte of match
|
||||
end - index into the buffer, first byte after match
|
||||
match - the matching string itself
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, strings):
|
||||
'''This creates an instance of searcher_string. This argument 'strings'
|
||||
may be a list; a sequence of strings; or the EOF or TIMEOUT types. '''
|
||||
|
||||
self.eof_index = -1
|
||||
self.timeout_index = -1
|
||||
self._strings = []
|
||||
for n, s in enumerate(strings):
|
||||
if s is EOF:
|
||||
self.eof_index = n
|
||||
continue
|
||||
if s is TIMEOUT:
|
||||
self.timeout_index = n
|
||||
continue
|
||||
self._strings.append((n, s))
|
||||
|
||||
def __str__(self):
|
||||
'''This returns a human-readable string that represents the state of
|
||||
the object.'''
|
||||
|
||||
ss = [(ns[0], ' %d: "%s"' % ns) for ns in self._strings]
|
||||
ss.append((-1, 'searcher_string:'))
|
||||
if self.eof_index >= 0:
|
||||
ss.append((self.eof_index, ' %d: EOF' % self.eof_index))
|
||||
if self.timeout_index >= 0:
|
||||
ss.append((self.timeout_index,
|
||||
' %d: TIMEOUT' % self.timeout_index))
|
||||
ss.sort()
|
||||
ss = list(zip(*ss))[1]
|
||||
return '\n'.join(ss)
|
||||
|
||||
def search(self, buffer, freshlen, searchwindowsize=None):
|
||||
'''This searches 'buffer' for the first occurence of one of the search
|
||||
strings. 'freshlen' must indicate the number of bytes at the end of
|
||||
'buffer' which have not been searched before. It helps to avoid
|
||||
searching the same, possibly big, buffer over and over again.
|
||||
|
||||
See class spawn for the 'searchwindowsize' argument.
|
||||
|
||||
If there is a match this returns the index of that string, and sets
|
||||
'start', 'end' and 'match'. Otherwise, this returns -1. '''
|
||||
|
||||
first_match = None
|
||||
|
||||
# 'freshlen' helps a lot here. Further optimizations could
|
||||
# possibly include:
|
||||
#
|
||||
# using something like the Boyer-Moore Fast String Searching
|
||||
# Algorithm; pre-compiling the search through a list of
|
||||
# strings into something that can scan the input once to
|
||||
# search for all N strings; realize that if we search for
|
||||
# ['bar', 'baz'] and the input is '...foo' we need not bother
|
||||
# rescanning until we've read three more bytes.
|
||||
#
|
||||
# Sadly, I don't know enough about this interesting topic. /grahn
|
||||
|
||||
for index, s in self._strings:
|
||||
if searchwindowsize is None:
|
||||
# the match, if any, can only be in the fresh data,
|
||||
# or at the very end of the old data
|
||||
offset = -(freshlen + len(s))
|
||||
else:
|
||||
# better obey searchwindowsize
|
||||
offset = -searchwindowsize
|
||||
n = buffer.find(s, offset)
|
||||
if n >= 0 and (first_match is None or n < first_match):
|
||||
first_match = n
|
||||
best_index, best_match = index, s
|
||||
if first_match is None:
|
||||
return -1
|
||||
self.match = best_match
|
||||
self.start = first_match
|
||||
self.end = self.start + len(self.match)
|
||||
return best_index
|
||||
|
||||
|
||||
class searcher_re(object):
|
||||
'''This is regular expression string search helper for the
|
||||
spawn.expect_any() method. This helper class is for powerful
|
||||
pattern matching. For speed, see the helper class, searcher_string.
|
||||
|
||||
Attributes:
|
||||
|
||||
eof_index - index of EOF, or -1
|
||||
timeout_index - index of TIMEOUT, or -1
|
||||
|
||||
After a successful match by the search() method the following attributes
|
||||
are available:
|
||||
|
||||
start - index into the buffer, first byte of match
|
||||
end - index into the buffer, first byte after match
|
||||
match - the re.match object returned by a succesful re.search
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, patterns):
|
||||
'''This creates an instance that searches for 'patterns' Where
|
||||
'patterns' may be a list or other sequence of compiled regular
|
||||
expressions, or the EOF or TIMEOUT types.'''
|
||||
|
||||
self.eof_index = -1
|
||||
self.timeout_index = -1
|
||||
self._searches = []
|
||||
for n, s in zip(list(range(len(patterns))), patterns):
|
||||
if s is EOF:
|
||||
self.eof_index = n
|
||||
continue
|
||||
if s is TIMEOUT:
|
||||
self.timeout_index = n
|
||||
continue
|
||||
self._searches.append((n, s))
|
||||
|
||||
def __str__(self):
|
||||
'''This returns a human-readable string that represents the state of
|
||||
the object.'''
|
||||
|
||||
#ss = [(n, ' %d: re.compile("%s")' %
|
||||
# (n, repr(s.pattern))) for n, s in self._searches]
|
||||
ss = list()
|
||||
for n, s in self._searches:
|
||||
try:
|
||||
ss.append((n, ' %d: re.compile("%s")' % (n, s.pattern)))
|
||||
except UnicodeEncodeError:
|
||||
# for test cases that display __str__ of searches, dont throw
|
||||
# another exception just because stdout is ascii-only, using
|
||||
# repr()
|
||||
ss.append((n, ' %d: re.compile(%r)' % (n, s.pattern)))
|
||||
ss.append((-1, 'searcher_re:'))
|
||||
if self.eof_index >= 0:
|
||||
ss.append((self.eof_index, ' %d: EOF' % self.eof_index))
|
||||
if self.timeout_index >= 0:
|
||||
ss.append((self.timeout_index, ' %d: TIMEOUT' %
|
||||
self.timeout_index))
|
||||
ss.sort()
|
||||
ss = list(zip(*ss))[1]
|
||||
return '\n'.join(ss)
|
||||
|
||||
def search(self, buffer, freshlen, searchwindowsize=None):
|
||||
'''This searches 'buffer' for the first occurence of one of the regular
|
||||
expressions. 'freshlen' must indicate the number of bytes at the end of
|
||||
'buffer' which have not been searched before.
|
||||
|
||||
See class spawn for the 'searchwindowsize' argument.
|
||||
|
||||
If there is a match this returns the index of that string, and sets
|
||||
'start', 'end' and 'match'. Otherwise, returns -1.'''
|
||||
|
||||
first_match = None
|
||||
# 'freshlen' doesn't help here -- we cannot predict the
|
||||
# length of a match, and the re module provides no help.
|
||||
if searchwindowsize is None:
|
||||
searchstart = 0
|
||||
else:
|
||||
searchstart = max(0, len(buffer) - searchwindowsize)
|
||||
for index, s in self._searches:
|
||||
match = s.search(buffer, searchstart)
|
||||
if match is None:
|
||||
continue
|
||||
n = match.start()
|
||||
if first_match is None or n < first_match:
|
||||
first_match = n
|
||||
the_match = match
|
||||
best_index = index
|
||||
if first_match is None:
|
||||
return -1
|
||||
self.start = first_match
|
||||
self.match = the_match
|
||||
self.end = self.match.end()
|
||||
return best_index
|
||||
Vendored
-142
@@ -1,142 +0,0 @@
|
||||
'''This is like pexpect, but it will work with any file descriptor that you
|
||||
pass it. You are reponsible for opening and close the file descriptor.
|
||||
This allows you to use Pexpect with sockets and named pipes (FIFOs).
|
||||
|
||||
PEXPECT LICENSE
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
'''
|
||||
|
||||
from .spawnbase import SpawnBase
|
||||
from .exceptions import ExceptionPexpect, TIMEOUT
|
||||
from .utils import select_ignore_interrupts
|
||||
import os
|
||||
|
||||
__all__ = ['fdspawn']
|
||||
|
||||
class fdspawn(SpawnBase):
|
||||
'''This is like pexpect.spawn but allows you to supply your own open file
|
||||
descriptor. For example, you could use it to read through a file looking
|
||||
for patterns, or to control a modem or serial device. '''
|
||||
|
||||
def __init__ (self, fd, args=None, timeout=30, maxread=2000, searchwindowsize=None,
|
||||
logfile=None, encoding=None, codec_errors='strict'):
|
||||
'''This takes a file descriptor (an int) or an object that support the
|
||||
fileno() method (returning an int). All Python file-like objects
|
||||
support fileno(). '''
|
||||
|
||||
if type(fd) != type(0) and hasattr(fd, 'fileno'):
|
||||
fd = fd.fileno()
|
||||
|
||||
if type(fd) != type(0):
|
||||
raise ExceptionPexpect('The fd argument is not an int. If this is a command string then maybe you want to use pexpect.spawn.')
|
||||
|
||||
try: # make sure fd is a valid file descriptor
|
||||
os.fstat(fd)
|
||||
except OSError:
|
||||
raise ExceptionPexpect('The fd argument is not a valid file descriptor.')
|
||||
|
||||
self.args = None
|
||||
self.command = None
|
||||
SpawnBase.__init__(self, timeout, maxread, searchwindowsize, logfile,
|
||||
encoding=encoding, codec_errors=codec_errors)
|
||||
self.child_fd = fd
|
||||
self.own_fd = False
|
||||
self.closed = False
|
||||
self.name = '<file descriptor %d>' % fd
|
||||
|
||||
def close (self):
|
||||
"""Close the file descriptor.
|
||||
|
||||
Calling this method a second time does nothing, but if the file
|
||||
descriptor was closed elsewhere, :class:`OSError` will be raised.
|
||||
"""
|
||||
if self.child_fd == -1:
|
||||
return
|
||||
|
||||
self.flush()
|
||||
os.close(self.child_fd)
|
||||
self.child_fd = -1
|
||||
self.closed = True
|
||||
|
||||
def isalive (self):
|
||||
'''This checks if the file descriptor is still valid. If :func:`os.fstat`
|
||||
does not raise an exception then we assume it is alive. '''
|
||||
|
||||
if self.child_fd == -1:
|
||||
return False
|
||||
try:
|
||||
os.fstat(self.child_fd)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def terminate (self, force=False): # pragma: no cover
|
||||
'''Deprecated and invalid. Just raises an exception.'''
|
||||
raise ExceptionPexpect('This method is not valid for file descriptors.')
|
||||
|
||||
# These four methods are left around for backwards compatibility, but not
|
||||
# documented as part of fdpexpect. You're encouraged to use os.write
|
||||
# directly.
|
||||
def send(self, s):
|
||||
"Write to fd, return number of bytes written"
|
||||
s = self._coerce_send_string(s)
|
||||
self._log(s, 'send')
|
||||
|
||||
b = self._encoder.encode(s, final=False)
|
||||
return os.write(self.child_fd, b)
|
||||
|
||||
def sendline(self, s):
|
||||
"Write to fd with trailing newline, return number of bytes written"
|
||||
s = self._coerce_send_string(s)
|
||||
return self.send(s + self.linesep)
|
||||
|
||||
def write(self, s):
|
||||
"Write to fd, return None"
|
||||
self.send(s)
|
||||
|
||||
def writelines(self, sequence):
|
||||
"Call self.write() for each item in sequence"
|
||||
for s in sequence:
|
||||
self.write(s)
|
||||
|
||||
def read_nonblocking(self, size=1, timeout=-1):
|
||||
"""
|
||||
Read from the file descriptor and return the result as a string.
|
||||
|
||||
The read_nonblocking method of :class:`SpawnBase` assumes that a call
|
||||
to os.read will not block (timeout parameter is ignored). This is not
|
||||
the case for POSIX file-like objects such as sockets and serial ports.
|
||||
|
||||
Use :func:`select.select`, timeout is implemented conditionally for
|
||||
POSIX systems.
|
||||
|
||||
:param int size: Read at most *size* bytes.
|
||||
:param int timeout: Wait timeout seconds for file descriptor to be
|
||||
ready to read. When -1 (default), use self.timeout. When 0, poll.
|
||||
:return: String containing the bytes read
|
||||
"""
|
||||
if os.name == 'posix':
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
rlist = [self.child_fd]
|
||||
wlist = []
|
||||
xlist = []
|
||||
rlist, wlist, xlist = select_ignore_interrupts(rlist, wlist, xlist, timeout)
|
||||
if self.child_fd not in rlist:
|
||||
raise TIMEOUT('Timeout exceeded.')
|
||||
return super(fdspawn, self).read_nonblocking(size)
|
||||
Vendored
-179
@@ -1,179 +0,0 @@
|
||||
"""Provides an interface like pexpect.spawn interface using subprocess.Popen
|
||||
"""
|
||||
import os
|
||||
import threading
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import signal
|
||||
import shlex
|
||||
|
||||
try:
|
||||
from queue import Queue, Empty # Python 3
|
||||
except ImportError:
|
||||
from Queue import Queue, Empty # Python 2
|
||||
|
||||
from .spawnbase import SpawnBase, PY3
|
||||
from .exceptions import EOF
|
||||
|
||||
class PopenSpawn(SpawnBase):
|
||||
if PY3:
|
||||
crlf = '\n'.encode('ascii')
|
||||
else:
|
||||
crlf = '\n'
|
||||
|
||||
def __init__(self, cmd, timeout=30, maxread=2000, searchwindowsize=None,
|
||||
logfile=None, cwd=None, env=None, encoding=None,
|
||||
codec_errors='strict'):
|
||||
super(PopenSpawn, self).__init__(timeout=timeout, maxread=maxread,
|
||||
searchwindowsize=searchwindowsize, logfile=logfile,
|
||||
encoding=encoding, codec_errors=codec_errors)
|
||||
|
||||
kwargs = dict(bufsize=0, stdin=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, stdout=subprocess.PIPE,
|
||||
cwd=cwd, env=env)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
kwargs['startupinfo'] = startupinfo
|
||||
kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
|
||||
if not isinstance(cmd, (list, tuple)):
|
||||
cmd = shlex.split(cmd)
|
||||
|
||||
self.proc = subprocess.Popen(cmd, **kwargs)
|
||||
self.closed = False
|
||||
self._buf = self.string_type()
|
||||
|
||||
self._read_queue = Queue()
|
||||
self._read_thread = threading.Thread(target=self._read_incoming)
|
||||
self._read_thread.setDaemon(True)
|
||||
self._read_thread.start()
|
||||
|
||||
_read_reached_eof = False
|
||||
|
||||
def read_nonblocking(self, size, timeout):
|
||||
buf = self._buf
|
||||
if self._read_reached_eof:
|
||||
# We have already finished reading. Use up any buffered data,
|
||||
# then raise EOF
|
||||
if buf:
|
||||
self._buf = buf[size:]
|
||||
return buf[:size]
|
||||
else:
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF).')
|
||||
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
elif timeout is None:
|
||||
timeout = 1e6
|
||||
|
||||
t0 = time.time()
|
||||
while (time.time() - t0) < timeout and size and len(buf) < size:
|
||||
try:
|
||||
incoming = self._read_queue.get_nowait()
|
||||
except Empty:
|
||||
break
|
||||
else:
|
||||
if incoming is None:
|
||||
self._read_reached_eof = True
|
||||
break
|
||||
|
||||
buf += self._decoder.decode(incoming, final=False)
|
||||
|
||||
r, self._buf = buf[:size], buf[size:]
|
||||
|
||||
self._log(r, 'read')
|
||||
return r
|
||||
|
||||
def _read_incoming(self):
|
||||
"""Run in a thread to move output from a pipe to a queue."""
|
||||
fileno = self.proc.stdout.fileno()
|
||||
while 1:
|
||||
buf = b''
|
||||
try:
|
||||
buf = os.read(fileno, 1024)
|
||||
except OSError as e:
|
||||
self._log(e, 'read')
|
||||
|
||||
if not buf:
|
||||
# This indicates we have reached EOF
|
||||
self._read_queue.put(None)
|
||||
return
|
||||
|
||||
self._read_queue.put(buf)
|
||||
|
||||
def write(self, s):
|
||||
'''This is similar to send() except that there is no return value.
|
||||
'''
|
||||
self.send(s)
|
||||
|
||||
def writelines(self, sequence):
|
||||
'''This calls write() for each element in the sequence.
|
||||
|
||||
The sequence can be any iterable object producing strings, typically a
|
||||
list of strings. This does not add line separators. There is no return
|
||||
value.
|
||||
'''
|
||||
for s in sequence:
|
||||
self.send(s)
|
||||
|
||||
def send(self, s):
|
||||
'''Send data to the subprocess' stdin.
|
||||
|
||||
Returns the number of bytes written.
|
||||
'''
|
||||
s = self._coerce_send_string(s)
|
||||
self._log(s, 'send')
|
||||
|
||||
b = self._encoder.encode(s, final=False)
|
||||
if PY3:
|
||||
return self.proc.stdin.write(b)
|
||||
else:
|
||||
# On Python 2, .write() returns None, so we return the length of
|
||||
# bytes written ourselves. This assumes they all got written.
|
||||
self.proc.stdin.write(b)
|
||||
return len(b)
|
||||
|
||||
def sendline(self, s=''):
|
||||
'''Wraps send(), sending string ``s`` to child process, with os.linesep
|
||||
automatically appended. Returns number of bytes written. '''
|
||||
|
||||
n = self.send(s)
|
||||
return n + self.send(self.linesep)
|
||||
|
||||
def wait(self):
|
||||
'''Wait for the subprocess to finish.
|
||||
|
||||
Returns the exit code.
|
||||
'''
|
||||
status = self.proc.wait()
|
||||
if status >= 0:
|
||||
self.exitstatus = status
|
||||
self.signalstatus = None
|
||||
else:
|
||||
self.exitstatus = None
|
||||
self.signalstatus = -status
|
||||
self.terminated = True
|
||||
return status
|
||||
|
||||
def kill(self, sig):
|
||||
'''Sends a Unix signal to the subprocess.
|
||||
|
||||
Use constants from the :mod:`signal` module to specify which signal.
|
||||
'''
|
||||
if sys.platform == 'win32':
|
||||
if sig in [signal.SIGINT, signal.CTRL_C_EVENT]:
|
||||
sig = signal.CTRL_C_EVENT
|
||||
elif sig in [signal.SIGBREAK, signal.CTRL_BREAK_EVENT]:
|
||||
sig = signal.CTRL_BREAK_EVENT
|
||||
else:
|
||||
sig = signal.SIGTERM
|
||||
|
||||
os.kill(self.proc.pid, sig)
|
||||
|
||||
def sendeof(self):
|
||||
'''Closes the stdin pipe from the writing end.'''
|
||||
self.proc.stdin.close()
|
||||
Vendored
-806
@@ -1,806 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import pty
|
||||
import tty
|
||||
import errno
|
||||
import signal
|
||||
from contextlib import contextmanager
|
||||
|
||||
import ptyprocess
|
||||
from ptyprocess.ptyprocess import use_native_pty_fork
|
||||
|
||||
from .exceptions import ExceptionPexpect, EOF, TIMEOUT
|
||||
from .spawnbase import SpawnBase
|
||||
from .utils import which, split_command_line, select_ignore_interrupts
|
||||
|
||||
@contextmanager
|
||||
def _wrap_ptyprocess_err():
|
||||
"""Turn ptyprocess errors into our own ExceptionPexpect errors"""
|
||||
try:
|
||||
yield
|
||||
except ptyprocess.PtyProcessError as e:
|
||||
raise ExceptionPexpect(*e.args)
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
|
||||
class spawn(SpawnBase):
|
||||
'''This is the main class interface for Pexpect. Use this class to start
|
||||
and control child applications. '''
|
||||
|
||||
# This is purely informational now - changing it has no effect
|
||||
use_native_pty_fork = use_native_pty_fork
|
||||
|
||||
def __init__(self, command, args=[], timeout=30, maxread=2000,
|
||||
searchwindowsize=None, logfile=None, cwd=None, env=None,
|
||||
ignore_sighup=False, echo=True, preexec_fn=None,
|
||||
encoding=None, codec_errors='strict', dimensions=None):
|
||||
'''This is the constructor. The command parameter may be a string that
|
||||
includes a command and any arguments to the command. For example::
|
||||
|
||||
child = pexpect.spawn('/usr/bin/ftp')
|
||||
child = pexpect.spawn('/usr/bin/ssh user@example.com')
|
||||
child = pexpect.spawn('ls -latr /tmp')
|
||||
|
||||
You may also construct it with a list of arguments like so::
|
||||
|
||||
child = pexpect.spawn('/usr/bin/ftp', [])
|
||||
child = pexpect.spawn('/usr/bin/ssh', ['user@example.com'])
|
||||
child = pexpect.spawn('ls', ['-latr', '/tmp'])
|
||||
|
||||
After this the child application will be created and will be ready to
|
||||
talk to. For normal use, see expect() and send() and sendline().
|
||||
|
||||
Remember that Pexpect does NOT interpret shell meta characters such as
|
||||
redirect, pipe, or wild cards (``>``, ``|``, or ``*``). This is a
|
||||
common mistake. If you want to run a command and pipe it through
|
||||
another command then you must also start a shell. For example::
|
||||
|
||||
child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > logs.txt"')
|
||||
child.expect(pexpect.EOF)
|
||||
|
||||
The second form of spawn (where you pass a list of arguments) is useful
|
||||
in situations where you wish to spawn a command and pass it its own
|
||||
argument list. This can make syntax more clear. For example, the
|
||||
following is equivalent to the previous example::
|
||||
|
||||
shell_cmd = 'ls -l | grep LOG > logs.txt'
|
||||
child = pexpect.spawn('/bin/bash', ['-c', shell_cmd])
|
||||
child.expect(pexpect.EOF)
|
||||
|
||||
The maxread attribute sets the read buffer size. This is maximum number
|
||||
of bytes that Pexpect will try to read from a TTY at one time. Setting
|
||||
the maxread size to 1 will turn off buffering. Setting the maxread
|
||||
value higher may help performance in cases where large amounts of
|
||||
output are read back from the child. This feature is useful in
|
||||
conjunction with searchwindowsize.
|
||||
|
||||
When the keyword argument *searchwindowsize* is None (default), the
|
||||
full buffer is searched at each iteration of receiving incoming data.
|
||||
The default number of bytes scanned at each iteration is very large
|
||||
and may be reduced to collaterally reduce search cost. After
|
||||
:meth:`~.expect` returns, the full buffer attribute remains up to
|
||||
size *maxread* irrespective of *searchwindowsize* value.
|
||||
|
||||
When the keyword argument ``timeout`` is specified as a number,
|
||||
(default: *30*), then :class:`TIMEOUT` will be raised after the value
|
||||
specified has elapsed, in seconds, for any of the :meth:`~.expect`
|
||||
family of method calls. When None, TIMEOUT will not be raised, and
|
||||
:meth:`~.expect` may block indefinitely until match.
|
||||
|
||||
|
||||
The logfile member turns on or off logging. All input and output will
|
||||
be copied to the given file object. Set logfile to None to stop
|
||||
logging. This is the default. Set logfile to sys.stdout to echo
|
||||
everything to standard output. The logfile is flushed after each write.
|
||||
|
||||
Example log input and output to a file::
|
||||
|
||||
child = pexpect.spawn('some_command')
|
||||
fout = open('mylog.txt','wb')
|
||||
child.logfile = fout
|
||||
|
||||
Example log to stdout::
|
||||
|
||||
# In Python 2:
|
||||
child = pexpect.spawn('some_command')
|
||||
child.logfile = sys.stdout
|
||||
|
||||
# In Python 3, spawnu should be used to give str to stdout:
|
||||
child = pexpect.spawnu('some_command')
|
||||
child.logfile = sys.stdout
|
||||
|
||||
The logfile_read and logfile_send members can be used to separately log
|
||||
the input from the child and output sent to the child. Sometimes you
|
||||
don't want to see everything you write to the child. You only want to
|
||||
log what the child sends back. For example::
|
||||
|
||||
child = pexpect.spawn('some_command')
|
||||
child.logfile_read = sys.stdout
|
||||
|
||||
You will need to pass an encoding to spawn in the above code if you are
|
||||
using Python 3.
|
||||
|
||||
To separately log output sent to the child use logfile_send::
|
||||
|
||||
child.logfile_send = fout
|
||||
|
||||
If ``ignore_sighup`` is True, the child process will ignore SIGHUP
|
||||
signals. The default is False from Pexpect 4.0, meaning that SIGHUP
|
||||
will be handled normally by the child.
|
||||
|
||||
The delaybeforesend helps overcome a weird behavior that many users
|
||||
were experiencing. The typical problem was that a user would expect() a
|
||||
"Password:" prompt and then immediately call sendline() to send the
|
||||
password. The user would then see that their password was echoed back
|
||||
to them. Passwords don't normally echo. The problem is caused by the
|
||||
fact that most applications print out the "Password" prompt and then
|
||||
turn off stdin echo, but if you send your password before the
|
||||
application turned off echo, then you get your password echoed.
|
||||
Normally this wouldn't be a problem when interacting with a human at a
|
||||
real keyboard. If you introduce a slight delay just before writing then
|
||||
this seems to clear up the problem. This was such a common problem for
|
||||
many users that I decided that the default pexpect behavior should be
|
||||
to sleep just before writing to the child application. 1/20th of a
|
||||
second (50 ms) seems to be enough to clear up the problem. You can set
|
||||
delaybeforesend to None to return to the old behavior.
|
||||
|
||||
Note that spawn is clever about finding commands on your path.
|
||||
It uses the same logic that "which" uses to find executables.
|
||||
|
||||
If you wish to get the exit status of the child you must call the
|
||||
close() method. The exit or signal status of the child will be stored
|
||||
in self.exitstatus or self.signalstatus. If the child exited normally
|
||||
then exitstatus will store the exit return code and signalstatus will
|
||||
be None. If the child was terminated abnormally with a signal then
|
||||
signalstatus will store the signal value and exitstatus will be None::
|
||||
|
||||
child = pexpect.spawn('some_command')
|
||||
child.close()
|
||||
print(child.exitstatus, child.signalstatus)
|
||||
|
||||
If you need more detail you can also read the self.status member which
|
||||
stores the status returned by os.waitpid. You can interpret this using
|
||||
os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG.
|
||||
|
||||
The echo attribute may be set to False to disable echoing of input.
|
||||
As a pseudo-terminal, all input echoed by the "keyboard" (send()
|
||||
or sendline()) will be repeated to output. For many cases, it is
|
||||
not desirable to have echo enabled, and it may be later disabled
|
||||
using setecho(False) followed by waitnoecho(). However, for some
|
||||
platforms such as Solaris, this is not possible, and should be
|
||||
disabled immediately on spawn.
|
||||
|
||||
If preexec_fn is given, it will be called in the child process before
|
||||
launching the given command. This is useful to e.g. reset inherited
|
||||
signal handlers.
|
||||
|
||||
The dimensions attribute specifies the size of the pseudo-terminal as
|
||||
seen by the subprocess, and is specified as a two-entry tuple (rows,
|
||||
columns). If this is unspecified, the defaults in ptyprocess will apply.
|
||||
'''
|
||||
super(spawn, self).__init__(timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize,
|
||||
logfile=logfile, encoding=encoding, codec_errors=codec_errors)
|
||||
self.STDIN_FILENO = pty.STDIN_FILENO
|
||||
self.STDOUT_FILENO = pty.STDOUT_FILENO
|
||||
self.STDERR_FILENO = pty.STDERR_FILENO
|
||||
self.cwd = cwd
|
||||
self.env = env
|
||||
self.echo = echo
|
||||
self.ignore_sighup = ignore_sighup
|
||||
self.__irix_hack = sys.platform.lower().startswith('irix')
|
||||
if command is None:
|
||||
self.command = None
|
||||
self.args = None
|
||||
self.name = '<pexpect factory incomplete>'
|
||||
else:
|
||||
self._spawn(command, args, preexec_fn, dimensions)
|
||||
|
||||
def __str__(self):
|
||||
'''This returns a human-readable string that represents the state of
|
||||
the object. '''
|
||||
|
||||
s = []
|
||||
s.append(repr(self))
|
||||
s.append('command: ' + str(self.command))
|
||||
s.append('args: %r' % (self.args,))
|
||||
s.append('buffer (last 100 chars): %r' % (
|
||||
self.buffer[-100:] if self.buffer else self.buffer,))
|
||||
s.append('before (last 100 chars): %r' % (
|
||||
self.before[-100:] if self.before else self.before,))
|
||||
s.append('after: %r' % (self.after,))
|
||||
s.append('match: %r' % (self.match,))
|
||||
s.append('match_index: ' + str(self.match_index))
|
||||
s.append('exitstatus: ' + str(self.exitstatus))
|
||||
if hasattr(self, 'ptyproc'):
|
||||
s.append('flag_eof: ' + str(self.flag_eof))
|
||||
s.append('pid: ' + str(self.pid))
|
||||
s.append('child_fd: ' + str(self.child_fd))
|
||||
s.append('closed: ' + str(self.closed))
|
||||
s.append('timeout: ' + str(self.timeout))
|
||||
s.append('delimiter: ' + str(self.delimiter))
|
||||
s.append('logfile: ' + str(self.logfile))
|
||||
s.append('logfile_read: ' + str(self.logfile_read))
|
||||
s.append('logfile_send: ' + str(self.logfile_send))
|
||||
s.append('maxread: ' + str(self.maxread))
|
||||
s.append('ignorecase: ' + str(self.ignorecase))
|
||||
s.append('searchwindowsize: ' + str(self.searchwindowsize))
|
||||
s.append('delaybeforesend: ' + str(self.delaybeforesend))
|
||||
s.append('delayafterclose: ' + str(self.delayafterclose))
|
||||
s.append('delayafterterminate: ' + str(self.delayafterterminate))
|
||||
return '\n'.join(s)
|
||||
|
||||
def _spawn(self, command, args=[], preexec_fn=None, dimensions=None):
|
||||
'''This starts the given command in a child process. This does all the
|
||||
fork/exec type of stuff for a pty. This is called by __init__. If args
|
||||
is empty then command will be parsed (split on spaces) and args will be
|
||||
set to parsed arguments. '''
|
||||
|
||||
# The pid and child_fd of this object get set by this method.
|
||||
# Note that it is difficult for this method to fail.
|
||||
# You cannot detect if the child process cannot start.
|
||||
# So the only way you can tell if the child process started
|
||||
# or not is to try to read from the file descriptor. If you get
|
||||
# EOF immediately then it means that the child is already dead.
|
||||
# That may not necessarily be bad because you may have spawned a child
|
||||
# that performs some task; creates no stdout output; and then dies.
|
||||
|
||||
# If command is an int type then it may represent a file descriptor.
|
||||
if isinstance(command, type(0)):
|
||||
raise ExceptionPexpect('Command is an int type. ' +
|
||||
'If this is a file descriptor then maybe you want to ' +
|
||||
'use fdpexpect.fdspawn which takes an existing ' +
|
||||
'file descriptor instead of a command string.')
|
||||
|
||||
if not isinstance(args, type([])):
|
||||
raise TypeError('The argument, args, must be a list.')
|
||||
|
||||
if args == []:
|
||||
self.args = split_command_line(command)
|
||||
self.command = self.args[0]
|
||||
else:
|
||||
# Make a shallow copy of the args list.
|
||||
self.args = args[:]
|
||||
self.args.insert(0, command)
|
||||
self.command = command
|
||||
|
||||
command_with_path = which(self.command, env=self.env)
|
||||
if command_with_path is None:
|
||||
raise ExceptionPexpect('The command was not found or was not ' +
|
||||
'executable: %s.' % self.command)
|
||||
self.command = command_with_path
|
||||
self.args[0] = self.command
|
||||
|
||||
self.name = '<' + ' '.join(self.args) + '>'
|
||||
|
||||
assert self.pid is None, 'The pid member must be None.'
|
||||
assert self.command is not None, 'The command member must not be None.'
|
||||
|
||||
kwargs = {'echo': self.echo, 'preexec_fn': preexec_fn}
|
||||
if self.ignore_sighup:
|
||||
def preexec_wrapper():
|
||||
"Set SIGHUP to be ignored, then call the real preexec_fn"
|
||||
signal.signal(signal.SIGHUP, signal.SIG_IGN)
|
||||
if preexec_fn is not None:
|
||||
preexec_fn()
|
||||
kwargs['preexec_fn'] = preexec_wrapper
|
||||
|
||||
if dimensions is not None:
|
||||
kwargs['dimensions'] = dimensions
|
||||
|
||||
if self.encoding is not None:
|
||||
# Encode command line using the specified encoding
|
||||
self.args = [a if isinstance(a, bytes) else a.encode(self.encoding)
|
||||
for a in self.args]
|
||||
|
||||
self.ptyproc = self._spawnpty(self.args, env=self.env,
|
||||
cwd=self.cwd, **kwargs)
|
||||
|
||||
self.pid = self.ptyproc.pid
|
||||
self.child_fd = self.ptyproc.fd
|
||||
|
||||
|
||||
self.terminated = False
|
||||
self.closed = False
|
||||
|
||||
def _spawnpty(self, args, **kwargs):
|
||||
'''Spawn a pty and return an instance of PtyProcess.'''
|
||||
return ptyprocess.PtyProcess.spawn(args, **kwargs)
|
||||
|
||||
def close(self, force=True):
|
||||
'''This closes the connection with the child application. Note that
|
||||
calling close() more than once is valid. This emulates standard Python
|
||||
behavior with files. Set force to True if you want to make sure that
|
||||
the child is terminated (SIGKILL is sent if the child ignores SIGHUP
|
||||
and SIGINT). '''
|
||||
|
||||
self.flush()
|
||||
self.ptyproc.close(force=force)
|
||||
self.isalive() # Update exit status from ptyproc
|
||||
self.child_fd = -1
|
||||
|
||||
def isatty(self):
|
||||
'''This returns True if the file descriptor is open and connected to a
|
||||
tty(-like) device, else False.
|
||||
|
||||
On SVR4-style platforms implementing streams, such as SunOS and HP-UX,
|
||||
the child pty may not appear as a terminal device. This means
|
||||
methods such as setecho(), setwinsize(), getwinsize() may raise an
|
||||
IOError. '''
|
||||
|
||||
return os.isatty(self.child_fd)
|
||||
|
||||
def waitnoecho(self, timeout=-1):
|
||||
'''This waits until the terminal ECHO flag is set False. This returns
|
||||
True if the echo mode is off. This returns False if the ECHO flag was
|
||||
not set False before the timeout. This can be used to detect when the
|
||||
child is waiting for a password. Usually a child application will turn
|
||||
off echo mode when it is waiting for the user to enter a password. For
|
||||
example, instead of expecting the "password:" prompt you can wait for
|
||||
the child to set ECHO off::
|
||||
|
||||
p = pexpect.spawn('ssh user@example.com')
|
||||
p.waitnoecho()
|
||||
p.sendline(mypassword)
|
||||
|
||||
If timeout==-1 then this method will use the value in self.timeout.
|
||||
If timeout==None then this method to block until ECHO flag is False.
|
||||
'''
|
||||
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
if timeout is not None:
|
||||
end_time = time.time() + timeout
|
||||
while True:
|
||||
if not self.getecho():
|
||||
return True
|
||||
if timeout < 0 and timeout is not None:
|
||||
return False
|
||||
if timeout is not None:
|
||||
timeout = end_time - time.time()
|
||||
time.sleep(0.1)
|
||||
|
||||
def getecho(self):
|
||||
'''This returns the terminal echo mode. This returns True if echo is
|
||||
on or False if echo is off. Child applications that are expecting you
|
||||
to enter a password often set ECHO False. See waitnoecho().
|
||||
|
||||
Not supported on platforms where ``isatty()`` returns False. '''
|
||||
return self.ptyproc.getecho()
|
||||
|
||||
def setecho(self, state):
|
||||
'''This sets the terminal echo mode on or off. Note that anything the
|
||||
child sent before the echo will be lost, so you should be sure that
|
||||
your input buffer is empty before you call setecho(). For example, the
|
||||
following will work as expected::
|
||||
|
||||
p = pexpect.spawn('cat') # Echo is on by default.
|
||||
p.sendline('1234') # We expect see this twice from the child...
|
||||
p.expect(['1234']) # ... once from the tty echo...
|
||||
p.expect(['1234']) # ... and again from cat itself.
|
||||
p.setecho(False) # Turn off tty echo
|
||||
p.sendline('abcd') # We will set this only once (echoed by cat).
|
||||
p.sendline('wxyz') # We will set this only once (echoed by cat)
|
||||
p.expect(['abcd'])
|
||||
p.expect(['wxyz'])
|
||||
|
||||
The following WILL NOT WORK because the lines sent before the setecho
|
||||
will be lost::
|
||||
|
||||
p = pexpect.spawn('cat')
|
||||
p.sendline('1234')
|
||||
p.setecho(False) # Turn off tty echo
|
||||
p.sendline('abcd') # We will set this only once (echoed by cat).
|
||||
p.sendline('wxyz') # We will set this only once (echoed by cat)
|
||||
p.expect(['1234'])
|
||||
p.expect(['1234'])
|
||||
p.expect(['abcd'])
|
||||
p.expect(['wxyz'])
|
||||
|
||||
|
||||
Not supported on platforms where ``isatty()`` returns False.
|
||||
'''
|
||||
return self.ptyproc.setecho(state)
|
||||
|
||||
def read_nonblocking(self, size=1, timeout=-1):
|
||||
'''This reads at most size characters from the child application. It
|
||||
includes a timeout. If the read does not complete within the timeout
|
||||
period then a TIMEOUT exception is raised. If the end of file is read
|
||||
then an EOF exception will be raised. If a logfile is specified, a
|
||||
copy is written to that log.
|
||||
|
||||
If timeout is None then the read may block indefinitely.
|
||||
If timeout is -1 then the self.timeout value is used. If timeout is 0
|
||||
then the child is polled and if there is no data immediately ready
|
||||
then this will raise a TIMEOUT exception.
|
||||
|
||||
The timeout refers only to the amount of time to read at least one
|
||||
character. This is not affected by the 'size' parameter, so if you call
|
||||
read_nonblocking(size=100, timeout=30) and only one character is
|
||||
available right away then one character will be returned immediately.
|
||||
It will not wait for 30 seconds for another 99 characters to come in.
|
||||
|
||||
This is a wrapper around os.read(). It uses select.select() to
|
||||
implement the timeout. '''
|
||||
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file.')
|
||||
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
|
||||
# Note that some systems such as Solaris do not give an EOF when
|
||||
# the child dies. In fact, you can still try to read
|
||||
# from the child_fd -- it will block forever or until TIMEOUT.
|
||||
# For this case, I test isalive() before doing any reading.
|
||||
# If isalive() is false, then I pretend that this is the same as EOF.
|
||||
if not self.isalive():
|
||||
# timeout of 0 means "poll"
|
||||
r, w, e = select_ignore_interrupts([self.child_fd], [], [], 0)
|
||||
if not r:
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF). Braindead platform.')
|
||||
elif self.__irix_hack:
|
||||
# Irix takes a long time before it realizes a child was terminated.
|
||||
# FIXME So does this mean Irix systems are forced to always have
|
||||
# FIXME a 2 second delay when calling read_nonblocking? That sucks.
|
||||
r, w, e = select_ignore_interrupts([self.child_fd], [], [], 2)
|
||||
if not r and not self.isalive():
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF). Slow platform.')
|
||||
|
||||
r, w, e = select_ignore_interrupts([self.child_fd], [], [], timeout)
|
||||
|
||||
if not r:
|
||||
if not self.isalive():
|
||||
# Some platforms, such as Irix, will claim that their
|
||||
# processes are alive; timeout on the select; and
|
||||
# then finally admit that they are not alive.
|
||||
self.flag_eof = True
|
||||
raise EOF('End of File (EOF). Very slow platform.')
|
||||
else:
|
||||
raise TIMEOUT('Timeout exceeded.')
|
||||
|
||||
if self.child_fd in r:
|
||||
return super(spawn, self).read_nonblocking(size)
|
||||
|
||||
raise ExceptionPexpect('Reached an unexpected state.') # pragma: no cover
|
||||
|
||||
def write(self, s):
|
||||
'''This is similar to send() except that there is no return value.
|
||||
'''
|
||||
|
||||
self.send(s)
|
||||
|
||||
def writelines(self, sequence):
|
||||
'''This calls write() for each element in the sequence. The sequence
|
||||
can be any iterable object producing strings, typically a list of
|
||||
strings. This does not add line separators. There is no return value.
|
||||
'''
|
||||
|
||||
for s in sequence:
|
||||
self.write(s)
|
||||
|
||||
def send(self, s):
|
||||
'''Sends string ``s`` to the child process, returning the number of
|
||||
bytes written. If a logfile is specified, a copy is written to that
|
||||
log.
|
||||
|
||||
The default terminal input mode is canonical processing unless set
|
||||
otherwise by the child process. This allows backspace and other line
|
||||
processing to be performed prior to transmitting to the receiving
|
||||
program. As this is buffered, there is a limited size of such buffer.
|
||||
|
||||
On Linux systems, this is 4096 (defined by N_TTY_BUF_SIZE). All
|
||||
other systems honor the POSIX.1 definition PC_MAX_CANON -- 1024
|
||||
on OSX, 256 on OpenSolaris, and 1920 on FreeBSD.
|
||||
|
||||
This value may be discovered using fpathconf(3)::
|
||||
|
||||
>>> from os import fpathconf
|
||||
>>> print(fpathconf(0, 'PC_MAX_CANON'))
|
||||
256
|
||||
|
||||
On such a system, only 256 bytes may be received per line. Any
|
||||
subsequent bytes received will be discarded. BEL (``'\a'``) is then
|
||||
sent to output if IMAXBEL (termios.h) is set by the tty driver.
|
||||
This is usually enabled by default. Linux does not honor this as
|
||||
an option -- it behaves as though it is always set on.
|
||||
|
||||
Canonical input processing may be disabled altogether by executing
|
||||
a shell, then stty(1), before executing the final program::
|
||||
|
||||
>>> bash = pexpect.spawn('/bin/bash', echo=False)
|
||||
>>> bash.sendline('stty -icanon')
|
||||
>>> bash.sendline('base64')
|
||||
>>> bash.sendline('x' * 5000)
|
||||
'''
|
||||
|
||||
if self.delaybeforesend is not None:
|
||||
time.sleep(self.delaybeforesend)
|
||||
|
||||
s = self._coerce_send_string(s)
|
||||
self._log(s, 'send')
|
||||
|
||||
b = self._encoder.encode(s, final=False)
|
||||
return os.write(self.child_fd, b)
|
||||
|
||||
def sendline(self, s=''):
|
||||
'''Wraps send(), sending string ``s`` to child process, with
|
||||
``os.linesep`` automatically appended. Returns number of bytes
|
||||
written. Only a limited number of bytes may be sent for each
|
||||
line in the default terminal mode, see docstring of :meth:`send`.
|
||||
'''
|
||||
s = self._coerce_send_string(s)
|
||||
return self.send(s + self.linesep)
|
||||
|
||||
def _log_control(self, s):
|
||||
"""Write control characters to the appropriate log files"""
|
||||
if self.encoding is not None:
|
||||
s = s.decode(self.encoding, 'replace')
|
||||
self._log(s, 'send')
|
||||
|
||||
def sendcontrol(self, char):
|
||||
'''Helper method that wraps send() with mnemonic access for sending control
|
||||
character to the child (such as Ctrl-C or Ctrl-D). For example, to send
|
||||
Ctrl-G (ASCII 7, bell, '\a')::
|
||||
|
||||
child.sendcontrol('g')
|
||||
|
||||
See also, sendintr() and sendeof().
|
||||
'''
|
||||
n, byte = self.ptyproc.sendcontrol(char)
|
||||
self._log_control(byte)
|
||||
return n
|
||||
|
||||
def sendeof(self):
|
||||
'''This sends an EOF to the child. This sends a character which causes
|
||||
the pending parent output buffer to be sent to the waiting child
|
||||
program without waiting for end-of-line. If it is the first character
|
||||
of the line, the read() in the user program returns 0, which signifies
|
||||
end-of-file. This means to work as expected a sendeof() has to be
|
||||
called at the beginning of a line. This method does not send a newline.
|
||||
It is the responsibility of the caller to ensure the eof is sent at the
|
||||
beginning of a line. '''
|
||||
|
||||
n, byte = self.ptyproc.sendeof()
|
||||
self._log_control(byte)
|
||||
|
||||
def sendintr(self):
|
||||
'''This sends a SIGINT to the child. It does not require
|
||||
the SIGINT to be the first character on a line. '''
|
||||
|
||||
n, byte = self.ptyproc.sendintr()
|
||||
self._log_control(byte)
|
||||
|
||||
@property
|
||||
def flag_eof(self):
|
||||
return self.ptyproc.flag_eof
|
||||
|
||||
@flag_eof.setter
|
||||
def flag_eof(self, value):
|
||||
self.ptyproc.flag_eof = value
|
||||
|
||||
def eof(self):
|
||||
'''This returns True if the EOF exception was ever raised.
|
||||
'''
|
||||
return self.flag_eof
|
||||
|
||||
def terminate(self, force=False):
|
||||
'''This forces a child process to terminate. It starts nicely with
|
||||
SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This
|
||||
returns True if the child was terminated. This returns False if the
|
||||
child could not be terminated. '''
|
||||
|
||||
if not self.isalive():
|
||||
return True
|
||||
try:
|
||||
self.kill(signal.SIGHUP)
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
self.kill(signal.SIGCONT)
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
self.kill(signal.SIGINT)
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
if force:
|
||||
self.kill(signal.SIGKILL)
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
except OSError:
|
||||
# I think there are kernel timing issues that sometimes cause
|
||||
# this to happen. I think isalive() reports True, but the
|
||||
# process is dead to the kernel.
|
||||
# Make one last attempt to see if the kernel is up to date.
|
||||
time.sleep(self.delayafterterminate)
|
||||
if not self.isalive():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def wait(self):
|
||||
'''This waits until the child exits. This is a blocking call. This will
|
||||
not read any data from the child, so this will block forever if the
|
||||
child has unread output and has terminated. In other words, the child
|
||||
may have printed output then called exit(), but, the child is
|
||||
technically still alive until its output is read by the parent.
|
||||
|
||||
This method is non-blocking if :meth:`wait` has already been called
|
||||
previously or :meth:`isalive` method returns False. It simply returns
|
||||
the previously determined exit status.
|
||||
'''
|
||||
|
||||
ptyproc = self.ptyproc
|
||||
with _wrap_ptyprocess_err():
|
||||
# exception may occur if "Is some other process attempting
|
||||
# "job control with our child pid?"
|
||||
exitstatus = ptyproc.wait()
|
||||
self.status = ptyproc.status
|
||||
self.exitstatus = ptyproc.exitstatus
|
||||
self.signalstatus = ptyproc.signalstatus
|
||||
self.terminated = True
|
||||
|
||||
return exitstatus
|
||||
|
||||
def isalive(self):
|
||||
'''This tests if the child process is running or not. This is
|
||||
non-blocking. If the child was terminated then this will read the
|
||||
exitstatus or signalstatus of the child. This returns True if the child
|
||||
process appears to be running or False if not. It can take literally
|
||||
SECONDS for Solaris to return the right status. '''
|
||||
|
||||
ptyproc = self.ptyproc
|
||||
with _wrap_ptyprocess_err():
|
||||
alive = ptyproc.isalive()
|
||||
|
||||
if not alive:
|
||||
self.status = ptyproc.status
|
||||
self.exitstatus = ptyproc.exitstatus
|
||||
self.signalstatus = ptyproc.signalstatus
|
||||
self.terminated = True
|
||||
|
||||
return alive
|
||||
|
||||
def kill(self, sig):
|
||||
|
||||
'''This sends the given signal to the child application. In keeping
|
||||
with UNIX tradition it has a misleading name. It does not necessarily
|
||||
kill the child unless you send the right signal. '''
|
||||
|
||||
# Same as os.kill, but the pid is given for you.
|
||||
if self.isalive():
|
||||
os.kill(self.pid, sig)
|
||||
|
||||
def getwinsize(self):
|
||||
'''This returns the terminal window size of the child tty. The return
|
||||
value is a tuple of (rows, cols). '''
|
||||
return self.ptyproc.getwinsize()
|
||||
|
||||
def setwinsize(self, rows, cols):
|
||||
'''This sets the terminal window size of the child tty. This will cause
|
||||
a SIGWINCH signal to be sent to the child. This does not change the
|
||||
physical window size. It changes the size reported to TTY-aware
|
||||
applications like vi or curses -- applications that respond to the
|
||||
SIGWINCH signal. '''
|
||||
return self.ptyproc.setwinsize(rows, cols)
|
||||
|
||||
|
||||
def interact(self, escape_character=chr(29),
|
||||
input_filter=None, output_filter=None):
|
||||
|
||||
'''This gives control of the child process to the interactive user (the
|
||||
human at the keyboard). Keystrokes are sent to the child process, and
|
||||
the stdout and stderr output of the child process is printed. This
|
||||
simply echos the child stdout and child stderr to the real stdout and
|
||||
it echos the real stdin to the child stdin. When the user types the
|
||||
escape_character this method will return None. The escape_character
|
||||
will not be transmitted. The default for escape_character is
|
||||
entered as ``Ctrl - ]``, the very same as BSD telnet. To prevent
|
||||
escaping, escape_character may be set to None.
|
||||
|
||||
If a logfile is specified, then the data sent and received from the
|
||||
child process in interact mode is duplicated to the given log.
|
||||
|
||||
You may pass in optional input and output filter functions. These
|
||||
functions should take a string and return a string. The output_filter
|
||||
will be passed all the output from the child process. The input_filter
|
||||
will be passed all the keyboard input from the user. The input_filter
|
||||
is run BEFORE the check for the escape_character.
|
||||
|
||||
Note that if you change the window size of the parent the SIGWINCH
|
||||
signal will not be passed through to the child. If you want the child
|
||||
window size to change when the parent's window size changes then do
|
||||
something like the following example::
|
||||
|
||||
import pexpect, struct, fcntl, termios, signal, sys
|
||||
def sigwinch_passthrough (sig, data):
|
||||
s = struct.pack("HHHH", 0, 0, 0, 0)
|
||||
a = struct.unpack('hhhh', fcntl.ioctl(sys.stdout.fileno(),
|
||||
termios.TIOCGWINSZ , s))
|
||||
global p
|
||||
p.setwinsize(a[0],a[1])
|
||||
# Note this 'p' global and used in sigwinch_passthrough.
|
||||
p = pexpect.spawn('/bin/bash')
|
||||
signal.signal(signal.SIGWINCH, sigwinch_passthrough)
|
||||
p.interact()
|
||||
'''
|
||||
|
||||
# Flush the buffer.
|
||||
self.write_to_stdout(self.buffer)
|
||||
self.stdout.flush()
|
||||
self.buffer = self.string_type()
|
||||
mode = tty.tcgetattr(self.STDIN_FILENO)
|
||||
tty.setraw(self.STDIN_FILENO)
|
||||
if escape_character is not None and PY3:
|
||||
escape_character = escape_character.encode('latin-1')
|
||||
try:
|
||||
self.__interact_copy(escape_character, input_filter, output_filter)
|
||||
finally:
|
||||
tty.tcsetattr(self.STDIN_FILENO, tty.TCSAFLUSH, mode)
|
||||
|
||||
def __interact_writen(self, fd, data):
|
||||
'''This is used by the interact() method.
|
||||
'''
|
||||
|
||||
while data != b'' and self.isalive():
|
||||
n = os.write(fd, data)
|
||||
data = data[n:]
|
||||
|
||||
def __interact_read(self, fd):
|
||||
'''This is used by the interact() method.
|
||||
'''
|
||||
|
||||
return os.read(fd, 1000)
|
||||
|
||||
def __interact_copy(self, escape_character=None,
|
||||
input_filter=None, output_filter=None):
|
||||
|
||||
'''This is used by the interact() method.
|
||||
'''
|
||||
|
||||
while self.isalive():
|
||||
r, w, e = select_ignore_interrupts([self.child_fd, self.STDIN_FILENO], [], [])
|
||||
if self.child_fd in r:
|
||||
try:
|
||||
data = self.__interact_read(self.child_fd)
|
||||
except OSError as err:
|
||||
if err.args[0] == errno.EIO:
|
||||
# Linux-style EOF
|
||||
break
|
||||
raise
|
||||
if data == b'':
|
||||
# BSD-style EOF
|
||||
break
|
||||
if output_filter:
|
||||
data = output_filter(data)
|
||||
self._log(data, 'read')
|
||||
os.write(self.STDOUT_FILENO, data)
|
||||
if self.STDIN_FILENO in r:
|
||||
data = self.__interact_read(self.STDIN_FILENO)
|
||||
if input_filter:
|
||||
data = input_filter(data)
|
||||
i = -1
|
||||
if escape_character is not None:
|
||||
i = data.rfind(escape_character)
|
||||
if i != -1:
|
||||
data = data[:i]
|
||||
if data:
|
||||
self._log(data, 'send')
|
||||
self.__interact_writen(self.child_fd, data)
|
||||
break
|
||||
self._log(data, 'send')
|
||||
self.__interact_writen(self.child_fd, data)
|
||||
|
||||
|
||||
def spawnu(*args, **kwargs):
|
||||
"""Deprecated: pass encoding to spawn() instead."""
|
||||
kwargs.setdefault('encoding', 'utf-8')
|
||||
return spawn(*args, **kwargs)
|
||||
Vendored
-409
@@ -1,409 +0,0 @@
|
||||
'''This class extends pexpect.spawn to specialize setting up SSH connections.
|
||||
This adds methods for login, logout, and expecting the shell prompt.
|
||||
|
||||
PEXPECT LICENSE
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
'''
|
||||
|
||||
from pexpect import ExceptionPexpect, TIMEOUT, EOF, spawn
|
||||
import time
|
||||
import os
|
||||
|
||||
__all__ = ['ExceptionPxssh', 'pxssh']
|
||||
|
||||
# Exception classes used by this module.
|
||||
class ExceptionPxssh(ExceptionPexpect):
|
||||
'''Raised for pxssh exceptions.
|
||||
'''
|
||||
|
||||
class pxssh (spawn):
|
||||
'''This class extends pexpect.spawn to specialize setting up SSH
|
||||
connections. This adds methods for login, logout, and expecting the shell
|
||||
prompt. It does various tricky things to handle many situations in the SSH
|
||||
login process. For example, if the session is your first login, then pxssh
|
||||
automatically accepts the remote certificate; or if you have public key
|
||||
authentication setup then pxssh won't wait for the password prompt.
|
||||
|
||||
pxssh uses the shell prompt to synchronize output from the remote host. In
|
||||
order to make this more robust it sets the shell prompt to something more
|
||||
unique than just $ or #. This should work on most Borne/Bash or Csh style
|
||||
shells.
|
||||
|
||||
Example that runs a few commands on a remote server and prints the result::
|
||||
|
||||
from pexpect import pxssh
|
||||
import getpass
|
||||
try:
|
||||
s = pxssh.pxssh()
|
||||
hostname = raw_input('hostname: ')
|
||||
username = raw_input('username: ')
|
||||
password = getpass.getpass('password: ')
|
||||
s.login(hostname, username, password)
|
||||
s.sendline('uptime') # run a command
|
||||
s.prompt() # match the prompt
|
||||
print(s.before) # print everything before the prompt.
|
||||
s.sendline('ls -l')
|
||||
s.prompt()
|
||||
print(s.before)
|
||||
s.sendline('df')
|
||||
s.prompt()
|
||||
print(s.before)
|
||||
s.logout()
|
||||
except pxssh.ExceptionPxssh as e:
|
||||
print("pxssh failed on login.")
|
||||
print(e)
|
||||
|
||||
Example showing how to specify SSH options::
|
||||
|
||||
from pexpect import pxssh
|
||||
s = pxssh.pxssh(options={
|
||||
"StrictHostKeyChecking": "no",
|
||||
"UserKnownHostsFile": "/dev/null"})
|
||||
...
|
||||
|
||||
Note that if you have ssh-agent running while doing development with pxssh
|
||||
then this can lead to a lot of confusion. Many X display managers (xdm,
|
||||
gdm, kdm, etc.) will automatically start a GUI agent. You may see a GUI
|
||||
dialog box popup asking for a password during development. You should turn
|
||||
off any key agents during testing. The 'force_password' attribute will turn
|
||||
off public key authentication. This will only work if the remote SSH server
|
||||
is configured to allow password logins. Example of using 'force_password'
|
||||
attribute::
|
||||
|
||||
s = pxssh.pxssh()
|
||||
s.force_password = True
|
||||
hostname = raw_input('hostname: ')
|
||||
username = raw_input('username: ')
|
||||
password = getpass.getpass('password: ')
|
||||
s.login (hostname, username, password)
|
||||
'''
|
||||
|
||||
def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None,
|
||||
logfile=None, cwd=None, env=None, ignore_sighup=True, echo=True,
|
||||
options={}, encoding=None, codec_errors='strict'):
|
||||
|
||||
spawn.__init__(self, None, timeout=timeout, maxread=maxread,
|
||||
searchwindowsize=searchwindowsize, logfile=logfile,
|
||||
cwd=cwd, env=env, ignore_sighup=ignore_sighup, echo=echo,
|
||||
encoding=encoding, codec_errors=codec_errors)
|
||||
|
||||
self.name = '<pxssh>'
|
||||
|
||||
#SUBTLE HACK ALERT! Note that the command that SETS the prompt uses a
|
||||
#slightly different string than the regular expression to match it. This
|
||||
#is because when you set the prompt the command will echo back, but we
|
||||
#don't want to match the echoed command. So if we make the set command
|
||||
#slightly different than the regex we eliminate the problem. To make the
|
||||
#set command different we add a backslash in front of $. The $ doesn't
|
||||
#need to be escaped, but it doesn't hurt and serves to make the set
|
||||
#prompt command different than the regex.
|
||||
|
||||
# used to match the command-line prompt
|
||||
self.UNIQUE_PROMPT = "\[PEXPECT\][\$\#] "
|
||||
self.PROMPT = self.UNIQUE_PROMPT
|
||||
|
||||
# used to set shell command-line prompt to UNIQUE_PROMPT.
|
||||
self.PROMPT_SET_SH = "PS1='[PEXPECT]\$ '"
|
||||
self.PROMPT_SET_CSH = "set prompt='[PEXPECT]\$ '"
|
||||
self.SSH_OPTS = ("-o'RSAAuthentication=no'"
|
||||
+ " -o 'PubkeyAuthentication=no'")
|
||||
# Disabling host key checking, makes you vulnerable to MITM attacks.
|
||||
# + " -o 'StrictHostKeyChecking=no'"
|
||||
# + " -o 'UserKnownHostsFile /dev/null' ")
|
||||
# Disabling X11 forwarding gets rid of the annoying SSH_ASKPASS from
|
||||
# displaying a GUI password dialog. I have not figured out how to
|
||||
# disable only SSH_ASKPASS without also disabling X11 forwarding.
|
||||
# Unsetting SSH_ASKPASS on the remote side doesn't disable it! Annoying!
|
||||
#self.SSH_OPTS = "-x -o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'"
|
||||
self.force_password = False
|
||||
|
||||
# User defined SSH options, eg,
|
||||
# ssh.otions = dict(StrictHostKeyChecking="no",UserKnownHostsFile="/dev/null")
|
||||
self.options = options
|
||||
|
||||
def levenshtein_distance(self, a, b):
|
||||
'''This calculates the Levenshtein distance between a and b.
|
||||
'''
|
||||
|
||||
n, m = len(a), len(b)
|
||||
if n > m:
|
||||
a,b = b,a
|
||||
n,m = m,n
|
||||
current = range(n+1)
|
||||
for i in range(1,m+1):
|
||||
previous, current = current, [i]+[0]*n
|
||||
for j in range(1,n+1):
|
||||
add, delete = previous[j]+1, current[j-1]+1
|
||||
change = previous[j-1]
|
||||
if a[j-1] != b[i-1]:
|
||||
change = change + 1
|
||||
current[j] = min(add, delete, change)
|
||||
return current[n]
|
||||
|
||||
def try_read_prompt(self, timeout_multiplier):
|
||||
'''This facilitates using communication timeouts to perform
|
||||
synchronization as quickly as possible, while supporting high latency
|
||||
connections with a tunable worst case performance. Fast connections
|
||||
should be read almost immediately. Worst case performance for this
|
||||
method is timeout_multiplier * 3 seconds.
|
||||
'''
|
||||
|
||||
# maximum time allowed to read the first response
|
||||
first_char_timeout = timeout_multiplier * 0.5
|
||||
|
||||
# maximum time allowed between subsequent characters
|
||||
inter_char_timeout = timeout_multiplier * 0.1
|
||||
|
||||
# maximum time for reading the entire prompt
|
||||
total_timeout = timeout_multiplier * 3.0
|
||||
|
||||
prompt = self.string_type()
|
||||
begin = time.time()
|
||||
expired = 0.0
|
||||
timeout = first_char_timeout
|
||||
|
||||
while expired < total_timeout:
|
||||
try:
|
||||
prompt += self.read_nonblocking(size=1, timeout=timeout)
|
||||
expired = time.time() - begin # updated total time expired
|
||||
timeout = inter_char_timeout
|
||||
except TIMEOUT:
|
||||
break
|
||||
|
||||
return prompt
|
||||
|
||||
def sync_original_prompt (self, sync_multiplier=1.0):
|
||||
'''This attempts to find the prompt. Basically, press enter and record
|
||||
the response; press enter again and record the response; if the two
|
||||
responses are similar then assume we are at the original prompt.
|
||||
This can be a slow function. Worst case with the default sync_multiplier
|
||||
can take 12 seconds. Low latency connections are more likely to fail
|
||||
with a low sync_multiplier. Best case sync time gets worse with a
|
||||
high sync multiplier (500 ms with default). '''
|
||||
|
||||
# All of these timing pace values are magic.
|
||||
# I came up with these based on what seemed reliable for
|
||||
# connecting to a heavily loaded machine I have.
|
||||
self.sendline()
|
||||
time.sleep(0.1)
|
||||
|
||||
try:
|
||||
# Clear the buffer before getting the prompt.
|
||||
self.try_read_prompt(sync_multiplier)
|
||||
except TIMEOUT:
|
||||
pass
|
||||
|
||||
self.sendline()
|
||||
x = self.try_read_prompt(sync_multiplier)
|
||||
|
||||
self.sendline()
|
||||
a = self.try_read_prompt(sync_multiplier)
|
||||
|
||||
self.sendline()
|
||||
b = self.try_read_prompt(sync_multiplier)
|
||||
|
||||
ld = self.levenshtein_distance(a,b)
|
||||
len_a = len(a)
|
||||
if len_a == 0:
|
||||
return False
|
||||
if float(ld)/len_a < 0.4:
|
||||
return True
|
||||
return False
|
||||
|
||||
### TODO: This is getting messy and I'm pretty sure this isn't perfect.
|
||||
### TODO: I need to draw a flow chart for this.
|
||||
def login (self, server, username, password='', terminal_type='ansi',
|
||||
original_prompt=r"[#$]", login_timeout=10, port=None,
|
||||
auto_prompt_reset=True, ssh_key=None, quiet=True,
|
||||
sync_multiplier=1, check_local_ip=True):
|
||||
'''This logs the user into the given server.
|
||||
|
||||
It uses
|
||||
'original_prompt' to try to find the prompt right after login. When it
|
||||
finds the prompt it immediately tries to reset the prompt to something
|
||||
more easily matched. The default 'original_prompt' is very optimistic
|
||||
and is easily fooled. It's more reliable to try to match the original
|
||||
prompt as exactly as possible to prevent false matches by server
|
||||
strings such as the "Message Of The Day". On many systems you can
|
||||
disable the MOTD on the remote server by creating a zero-length file
|
||||
called :file:`~/.hushlogin` on the remote server. If a prompt cannot be found
|
||||
then this will not necessarily cause the login to fail. In the case of
|
||||
a timeout when looking for the prompt we assume that the original
|
||||
prompt was so weird that we could not match it, so we use a few tricks
|
||||
to guess when we have reached the prompt. Then we hope for the best and
|
||||
blindly try to reset the prompt to something more unique. If that fails
|
||||
then login() raises an :class:`ExceptionPxssh` exception.
|
||||
|
||||
In some situations it is not possible or desirable to reset the
|
||||
original prompt. In this case, pass ``auto_prompt_reset=False`` to
|
||||
inhibit setting the prompt to the UNIQUE_PROMPT. Remember that pxssh
|
||||
uses a unique prompt in the :meth:`prompt` method. If the original prompt is
|
||||
not reset then this will disable the :meth:`prompt` method unless you
|
||||
manually set the :attr:`PROMPT` attribute.
|
||||
'''
|
||||
|
||||
ssh_options = ''.join([" -o '%s=%s'" % (o, v) for (o, v) in self.options.items()])
|
||||
if quiet:
|
||||
ssh_options = ssh_options + ' -q'
|
||||
if not check_local_ip:
|
||||
ssh_options = ssh_options + " -o'NoHostAuthenticationForLocalhost=yes'"
|
||||
if self.force_password:
|
||||
ssh_options = ssh_options + ' ' + self.SSH_OPTS
|
||||
if port is not None:
|
||||
ssh_options = ssh_options + ' -p %s'%(str(port))
|
||||
if ssh_key is not None:
|
||||
try:
|
||||
os.path.isfile(ssh_key)
|
||||
except:
|
||||
raise ExceptionPxssh('private ssh key does not exist')
|
||||
ssh_options = ssh_options + ' -i %s' % (ssh_key)
|
||||
cmd = "ssh %s -l %s %s" % (ssh_options, username, server)
|
||||
|
||||
# This does not distinguish between a remote server 'password' prompt
|
||||
# and a local ssh 'passphrase' prompt (for unlocking a private key).
|
||||
spawn._spawn(self, cmd)
|
||||
i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT, "(?i)connection closed by remote host", EOF], timeout=login_timeout)
|
||||
|
||||
# First phase
|
||||
if i==0:
|
||||
# New certificate -- always accept it.
|
||||
# This is what you get if SSH does not have the remote host's
|
||||
# public key stored in the 'known_hosts' cache.
|
||||
self.sendline("yes")
|
||||
i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT])
|
||||
if i==2: # password or passphrase
|
||||
self.sendline(password)
|
||||
i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT])
|
||||
if i==4:
|
||||
self.sendline(terminal_type)
|
||||
i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT])
|
||||
if i==7:
|
||||
self.close()
|
||||
raise ExceptionPxssh('Could not establish connection to host')
|
||||
|
||||
# Second phase
|
||||
if i==0:
|
||||
# This is weird. This should not happen twice in a row.
|
||||
self.close()
|
||||
raise ExceptionPxssh('Weird error. Got "are you sure" prompt twice.')
|
||||
elif i==1: # can occur if you have a public key pair set to authenticate.
|
||||
### TODO: May NOT be OK if expect() got tricked and matched a false prompt.
|
||||
pass
|
||||
elif i==2: # password prompt again
|
||||
# For incorrect passwords, some ssh servers will
|
||||
# ask for the password again, others return 'denied' right away.
|
||||
# If we get the password prompt again then this means
|
||||
# we didn't get the password right the first time.
|
||||
self.close()
|
||||
raise ExceptionPxssh('password refused')
|
||||
elif i==3: # permission denied -- password was bad.
|
||||
self.close()
|
||||
raise ExceptionPxssh('permission denied')
|
||||
elif i==4: # terminal type again? WTF?
|
||||
self.close()
|
||||
raise ExceptionPxssh('Weird error. Got "terminal type" prompt twice.')
|
||||
elif i==5: # Timeout
|
||||
#This is tricky... I presume that we are at the command-line prompt.
|
||||
#It may be that the shell prompt was so weird that we couldn't match
|
||||
#it. Or it may be that we couldn't log in for some other reason. I
|
||||
#can't be sure, but it's safe to guess that we did login because if
|
||||
#I presume wrong and we are not logged in then this should be caught
|
||||
#later when I try to set the shell prompt.
|
||||
pass
|
||||
elif i==6: # Connection closed by remote host
|
||||
self.close()
|
||||
raise ExceptionPxssh('connection closed')
|
||||
else: # Unexpected
|
||||
self.close()
|
||||
raise ExceptionPxssh('unexpected login response')
|
||||
if not self.sync_original_prompt(sync_multiplier):
|
||||
self.close()
|
||||
raise ExceptionPxssh('could not synchronize with original prompt')
|
||||
# We appear to be in.
|
||||
# set shell prompt to something unique.
|
||||
if auto_prompt_reset:
|
||||
if not self.set_unique_prompt():
|
||||
self.close()
|
||||
raise ExceptionPxssh('could not set shell prompt '
|
||||
'(received: %r, expected: %r).' % (
|
||||
self.before, self.PROMPT,))
|
||||
return True
|
||||
|
||||
def logout (self):
|
||||
'''Sends exit to the remote shell.
|
||||
|
||||
If there are stopped jobs then this automatically sends exit twice.
|
||||
'''
|
||||
self.sendline("exit")
|
||||
index = self.expect([EOF, "(?i)there are stopped jobs"])
|
||||
if index==1:
|
||||
self.sendline("exit")
|
||||
self.expect(EOF)
|
||||
self.close()
|
||||
|
||||
def prompt(self, timeout=-1):
|
||||
'''Match the next shell prompt.
|
||||
|
||||
This is little more than a short-cut to the :meth:`~pexpect.spawn.expect`
|
||||
method. Note that if you called :meth:`login` with
|
||||
``auto_prompt_reset=False``, then before calling :meth:`prompt` you must
|
||||
set the :attr:`PROMPT` attribute to a regex that it will use for
|
||||
matching the prompt.
|
||||
|
||||
Calling :meth:`prompt` will erase the contents of the :attr:`before`
|
||||
attribute even if no prompt is ever matched. If timeout is not given or
|
||||
it is set to -1 then self.timeout is used.
|
||||
|
||||
:return: True if the shell prompt was matched, False if the timeout was
|
||||
reached.
|
||||
'''
|
||||
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
i = self.expect([self.PROMPT, TIMEOUT], timeout=timeout)
|
||||
if i==1:
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_unique_prompt(self):
|
||||
'''This sets the remote prompt to something more unique than ``#`` or ``$``.
|
||||
This makes it easier for the :meth:`prompt` method to match the shell prompt
|
||||
unambiguously. This method is called automatically by the :meth:`login`
|
||||
method, but you may want to call it manually if you somehow reset the
|
||||
shell prompt. For example, if you 'su' to a different user then you
|
||||
will need to manually reset the prompt. This sends shell commands to
|
||||
the remote host to set the prompt, so this assumes the remote host is
|
||||
ready to receive commands.
|
||||
|
||||
Alternatively, you may use your own prompt pattern. In this case you
|
||||
should call :meth:`login` with ``auto_prompt_reset=False``; then set the
|
||||
:attr:`PROMPT` attribute to a regular expression. After that, the
|
||||
:meth:`prompt` method will try to match your prompt pattern.
|
||||
'''
|
||||
|
||||
self.sendline("unset PROMPT_COMMAND")
|
||||
self.sendline(self.PROMPT_SET_SH) # sh-style
|
||||
i = self.expect ([TIMEOUT, self.PROMPT], timeout=10)
|
||||
if i == 0: # csh-style
|
||||
self.sendline(self.PROMPT_SET_CSH)
|
||||
i = self.expect([TIMEOUT, self.PROMPT], timeout=10)
|
||||
if i == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
# vi:ts=4:sw=4:expandtab:ft=python:
|
||||
Vendored
-122
@@ -1,122 +0,0 @@
|
||||
"""Generic wrapper for read-eval-print-loops, a.k.a. interactive shells
|
||||
"""
|
||||
import os.path
|
||||
import signal
|
||||
import sys
|
||||
|
||||
import pexpect
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
|
||||
if PY3:
|
||||
basestring = str
|
||||
|
||||
PEXPECT_PROMPT = u'[PEXPECT_PROMPT>'
|
||||
PEXPECT_CONTINUATION_PROMPT = u'[PEXPECT_PROMPT+'
|
||||
|
||||
class REPLWrapper(object):
|
||||
"""Wrapper for a REPL.
|
||||
|
||||
:param cmd_or_spawn: This can either be an instance of :class:`pexpect.spawn`
|
||||
in which a REPL has already been started, or a str command to start a new
|
||||
REPL process.
|
||||
:param str orig_prompt: The prompt to expect at first.
|
||||
:param str prompt_change: A command to change the prompt to something more
|
||||
unique. If this is ``None``, the prompt will not be changed. This will
|
||||
be formatted with the new and continuation prompts as positional
|
||||
parameters, so you can use ``{}`` style formatting to insert them into
|
||||
the command.
|
||||
:param str new_prompt: The more unique prompt to expect after the change.
|
||||
:param str extra_init_cmd: Commands to do extra initialisation, such as
|
||||
disabling pagers.
|
||||
"""
|
||||
def __init__(self, cmd_or_spawn, orig_prompt, prompt_change,
|
||||
new_prompt=PEXPECT_PROMPT,
|
||||
continuation_prompt=PEXPECT_CONTINUATION_PROMPT,
|
||||
extra_init_cmd=None):
|
||||
if isinstance(cmd_or_spawn, basestring):
|
||||
self.child = pexpect.spawn(cmd_or_spawn, echo=False, encoding='utf-8')
|
||||
else:
|
||||
self.child = cmd_or_spawn
|
||||
if self.child.echo:
|
||||
# Existing spawn instance has echo enabled, disable it
|
||||
# to prevent our input from being repeated to output.
|
||||
self.child.setecho(False)
|
||||
self.child.waitnoecho()
|
||||
|
||||
if prompt_change is None:
|
||||
self.prompt = orig_prompt
|
||||
else:
|
||||
self.set_prompt(orig_prompt,
|
||||
prompt_change.format(new_prompt, continuation_prompt))
|
||||
self.prompt = new_prompt
|
||||
self.continuation_prompt = continuation_prompt
|
||||
|
||||
self._expect_prompt()
|
||||
|
||||
if extra_init_cmd is not None:
|
||||
self.run_command(extra_init_cmd)
|
||||
|
||||
def set_prompt(self, orig_prompt, prompt_change):
|
||||
self.child.expect(orig_prompt)
|
||||
self.child.sendline(prompt_change)
|
||||
|
||||
def _expect_prompt(self, timeout=-1):
|
||||
return self.child.expect_exact([self.prompt, self.continuation_prompt],
|
||||
timeout=timeout)
|
||||
|
||||
def run_command(self, command, timeout=-1):
|
||||
"""Send a command to the REPL, wait for and return output.
|
||||
|
||||
:param str command: The command to send. Trailing newlines are not needed.
|
||||
This should be a complete block of input that will trigger execution;
|
||||
if a continuation prompt is found after sending input, :exc:`ValueError`
|
||||
will be raised.
|
||||
:param int timeout: How long to wait for the next prompt. -1 means the
|
||||
default from the :class:`pexpect.spawn` object (default 30 seconds).
|
||||
None means to wait indefinitely.
|
||||
"""
|
||||
# Split up multiline commands and feed them in bit-by-bit
|
||||
cmdlines = command.splitlines()
|
||||
# splitlines ignores trailing newlines - add it back in manually
|
||||
if command.endswith('\n'):
|
||||
cmdlines.append('')
|
||||
if not cmdlines:
|
||||
raise ValueError("No command was given")
|
||||
|
||||
res = []
|
||||
self.child.sendline(cmdlines[0])
|
||||
for line in cmdlines[1:]:
|
||||
self._expect_prompt(timeout=timeout)
|
||||
res.append(self.child.before)
|
||||
self.child.sendline(line)
|
||||
|
||||
# Command was fully submitted, now wait for the next prompt
|
||||
if self._expect_prompt(timeout=timeout) == 1:
|
||||
# We got the continuation prompt - command was incomplete
|
||||
self.child.kill(signal.SIGINT)
|
||||
self._expect_prompt(timeout=1)
|
||||
raise ValueError("Continuation prompt found - input was incomplete:\n"
|
||||
+ command)
|
||||
return u''.join(res + [self.child.before])
|
||||
|
||||
def python(command="python"):
|
||||
"""Start a Python shell and return a :class:`REPLWrapper` object."""
|
||||
return REPLWrapper(command, u">>> ", u"import sys; sys.ps1={0!r}; sys.ps2={1!r}")
|
||||
|
||||
def bash(command="bash"):
|
||||
"""Start a bash shell and return a :class:`REPLWrapper` object."""
|
||||
bashrc = os.path.join(os.path.dirname(__file__), 'bashrc.sh')
|
||||
child = pexpect.spawn(command, ['--rcfile', bashrc], echo=False,
|
||||
encoding='utf-8')
|
||||
|
||||
# If the user runs 'env', the value of PS1 will be in the output. To avoid
|
||||
# replwrap seeing that as the next prompt, we'll embed the marker characters
|
||||
# for invisible characters in the prompt; these show up when inspecting the
|
||||
# environment variable, but not when bash displays the prompt.
|
||||
ps1 = PEXPECT_PROMPT[:5] + u'\[\]' + PEXPECT_PROMPT[5:]
|
||||
ps2 = PEXPECT_CONTINUATION_PROMPT[:5] + u'\[\]' + PEXPECT_CONTINUATION_PROMPT[5:]
|
||||
prompt_change = u"PS1='{0}' PS2='{1}' PROMPT_COMMAND=''".format(ps1, ps2)
|
||||
|
||||
return REPLWrapper(child, u'\$', prompt_change,
|
||||
extra_init_cmd="export PAGER=cat")
|
||||
Vendored
-157
@@ -1,157 +0,0 @@
|
||||
import sys
|
||||
import types
|
||||
|
||||
from .exceptions import EOF, TIMEOUT
|
||||
from .pty_spawn import spawn
|
||||
|
||||
def run(command, timeout=30, withexitstatus=False, events=None,
|
||||
extra_args=None, logfile=None, cwd=None, env=None, **kwargs):
|
||||
|
||||
'''
|
||||
This function runs the given command; waits for it to finish; then
|
||||
returns all output as a string. STDERR is included in output. If the full
|
||||
path to the command is not given then the path is searched.
|
||||
|
||||
Note that lines are terminated by CR/LF (\\r\\n) combination even on
|
||||
UNIX-like systems because this is the standard for pseudottys. If you set
|
||||
'withexitstatus' to true, then run will return a tuple of (command_output,
|
||||
exitstatus). If 'withexitstatus' is false then this returns just
|
||||
command_output.
|
||||
|
||||
The run() function can often be used instead of creating a spawn instance.
|
||||
For example, the following code uses spawn::
|
||||
|
||||
from pexpect import *
|
||||
child = spawn('scp foo user@example.com:.')
|
||||
child.expect('(?i)password')
|
||||
child.sendline(mypassword)
|
||||
|
||||
The previous code can be replace with the following::
|
||||
|
||||
from pexpect import *
|
||||
run('scp foo user@example.com:.', events={'(?i)password': mypassword})
|
||||
|
||||
**Examples**
|
||||
|
||||
Start the apache daemon on the local machine::
|
||||
|
||||
from pexpect import *
|
||||
run("/usr/local/apache/bin/apachectl start")
|
||||
|
||||
Check in a file using SVN::
|
||||
|
||||
from pexpect import *
|
||||
run("svn ci -m 'automatic commit' my_file.py")
|
||||
|
||||
Run a command and capture exit status::
|
||||
|
||||
from pexpect import *
|
||||
(command_output, exitstatus) = run('ls -l /bin', withexitstatus=1)
|
||||
|
||||
The following will run SSH and execute 'ls -l' on the remote machine. The
|
||||
password 'secret' will be sent if the '(?i)password' pattern is ever seen::
|
||||
|
||||
run("ssh username@machine.example.com 'ls -l'",
|
||||
events={'(?i)password':'secret\\n'})
|
||||
|
||||
This will start mencoder to rip a video from DVD. This will also display
|
||||
progress ticks every 5 seconds as it runs. For example::
|
||||
|
||||
from pexpect import *
|
||||
def print_ticks(d):
|
||||
print d['event_count'],
|
||||
run("mencoder dvd://1 -o video.avi -oac copy -ovc copy",
|
||||
events={TIMEOUT:print_ticks}, timeout=5)
|
||||
|
||||
The 'events' argument should be either a dictionary or a tuple list that
|
||||
contains patterns and responses. Whenever one of the patterns is seen
|
||||
in the command output, run() will send the associated response string.
|
||||
So, run() in the above example can be also written as:
|
||||
|
||||
run("mencoder dvd://1 -o video.avi -oac copy -ovc copy",
|
||||
events=[(TIMEOUT,print_ticks)], timeout=5)
|
||||
|
||||
Use a tuple list for events if the command output requires a delicate
|
||||
control over what pattern should be matched, since the tuple list is passed
|
||||
to pexpect() as its pattern list, with the order of patterns preserved.
|
||||
|
||||
Note that you should put newlines in your string if Enter is necessary.
|
||||
|
||||
Like the example above, the responses may also contain a callback, either
|
||||
a function or method. It should accept a dictionary value as an argument.
|
||||
The dictionary contains all the locals from the run() function, so you can
|
||||
access the child spawn object or any other variable defined in run()
|
||||
(event_count, child, and extra_args are the most useful). A callback may
|
||||
return True to stop the current run process. Otherwise run() continues
|
||||
until the next event. A callback may also return a string which will be
|
||||
sent to the child. 'extra_args' is not used by directly run(). It provides
|
||||
a way to pass data to a callback function through run() through the locals
|
||||
dictionary passed to a callback.
|
||||
|
||||
Like :class:`spawn`, passing *encoding* will make it work with unicode
|
||||
instead of bytes. You can pass *codec_errors* to control how errors in
|
||||
encoding and decoding are handled.
|
||||
'''
|
||||
if timeout == -1:
|
||||
child = spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env,
|
||||
**kwargs)
|
||||
else:
|
||||
child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile,
|
||||
cwd=cwd, env=env, **kwargs)
|
||||
if isinstance(events, list):
|
||||
patterns= [x for x,y in events]
|
||||
responses = [y for x,y in events]
|
||||
elif isinstance(events, dict):
|
||||
patterns = list(events.keys())
|
||||
responses = list(events.values())
|
||||
else:
|
||||
# This assumes EOF or TIMEOUT will eventually cause run to terminate.
|
||||
patterns = None
|
||||
responses = None
|
||||
child_result_list = []
|
||||
event_count = 0
|
||||
while True:
|
||||
try:
|
||||
index = child.expect(patterns)
|
||||
if isinstance(child.after, child.allowed_string_types):
|
||||
child_result_list.append(child.before + child.after)
|
||||
else:
|
||||
# child.after may have been a TIMEOUT or EOF,
|
||||
# which we don't want appended to the list.
|
||||
child_result_list.append(child.before)
|
||||
if isinstance(responses[index], child.allowed_string_types):
|
||||
child.send(responses[index])
|
||||
elif (isinstance(responses[index], types.FunctionType) or
|
||||
isinstance(responses[index], types.MethodType)):
|
||||
callback_result = responses[index](locals())
|
||||
sys.stdout.flush()
|
||||
if isinstance(callback_result, child.allowed_string_types):
|
||||
child.send(callback_result)
|
||||
elif callback_result:
|
||||
break
|
||||
else:
|
||||
raise TypeError("parameter `event' at index {index} must be "
|
||||
"a string, method, or function: {value!r}"
|
||||
.format(index=index, value=responses[index]))
|
||||
event_count = event_count + 1
|
||||
except TIMEOUT:
|
||||
child_result_list.append(child.before)
|
||||
break
|
||||
except EOF:
|
||||
child_result_list.append(child.before)
|
||||
break
|
||||
child_result = child.string_type().join(child_result_list)
|
||||
if withexitstatus:
|
||||
child.close()
|
||||
return (child_result, child.exitstatus)
|
||||
else:
|
||||
return child_result
|
||||
|
||||
def runu(command, timeout=30, withexitstatus=False, events=None,
|
||||
extra_args=None, logfile=None, cwd=None, env=None, **kwargs):
|
||||
"""Deprecated: pass encoding to run() instead.
|
||||
"""
|
||||
kwargs.setdefault('encoding', 'utf-8')
|
||||
return run(command, timeout=timeout, withexitstatus=withexitstatus,
|
||||
events=events, extra_args=extra_args, logfile=logfile, cwd=cwd,
|
||||
env=env, **kwargs)
|
||||
Vendored
-431
@@ -1,431 +0,0 @@
|
||||
'''This implements a virtual screen. This is used to support ANSI terminal
|
||||
emulation. The screen representation and state is implemented in this class.
|
||||
Most of the methods are inspired by ANSI screen control codes. The
|
||||
:class:`~pexpect.ANSI.ANSI` class extends this class to add parsing of ANSI
|
||||
escape codes.
|
||||
|
||||
PEXPECT LICENSE
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
'''
|
||||
|
||||
import codecs
|
||||
import copy
|
||||
import sys
|
||||
|
||||
import warnings
|
||||
|
||||
warnings.warn(("pexpect.screen and pexpect.ANSI are deprecated. "
|
||||
"We recommend using pyte to emulate a terminal screen: "
|
||||
"https://pypi.python.org/pypi/pyte"),
|
||||
stacklevel=2)
|
||||
|
||||
NUL = 0 # Fill character; ignored on input.
|
||||
ENQ = 5 # Transmit answerback message.
|
||||
BEL = 7 # Ring the bell.
|
||||
BS = 8 # Move cursor left.
|
||||
HT = 9 # Move cursor to next tab stop.
|
||||
LF = 10 # Line feed.
|
||||
VT = 11 # Same as LF.
|
||||
FF = 12 # Same as LF.
|
||||
CR = 13 # Move cursor to left margin or newline.
|
||||
SO = 14 # Invoke G1 character set.
|
||||
SI = 15 # Invoke G0 character set.
|
||||
XON = 17 # Resume transmission.
|
||||
XOFF = 19 # Halt transmission.
|
||||
CAN = 24 # Cancel escape sequence.
|
||||
SUB = 26 # Same as CAN.
|
||||
ESC = 27 # Introduce a control sequence.
|
||||
DEL = 127 # Fill character; ignored on input.
|
||||
SPACE = u' ' # Space or blank character.
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
if PY3:
|
||||
unicode = str
|
||||
|
||||
def constrain (n, min, max):
|
||||
|
||||
'''This returns a number, n constrained to the min and max bounds. '''
|
||||
|
||||
if n < min:
|
||||
return min
|
||||
if n > max:
|
||||
return max
|
||||
return n
|
||||
|
||||
class screen:
|
||||
'''This object maintains the state of a virtual text screen as a
|
||||
rectangluar array. This maintains a virtual cursor position and handles
|
||||
scrolling as characters are added. This supports most of the methods needed
|
||||
by an ANSI text screen. Row and column indexes are 1-based (not zero-based,
|
||||
like arrays).
|
||||
|
||||
Characters are represented internally using unicode. Methods that accept
|
||||
input characters, when passed 'bytes' (which in Python 2 is equivalent to
|
||||
'str'), convert them from the encoding specified in the 'encoding'
|
||||
parameter to the constructor. Methods that return screen contents return
|
||||
unicode strings, with the exception of __str__() under Python 2. Passing
|
||||
``encoding=None`` limits the API to only accept unicode input, so passing
|
||||
bytes in will raise :exc:`TypeError`.
|
||||
'''
|
||||
def __init__(self, r=24, c=80, encoding='latin-1', encoding_errors='replace'):
|
||||
'''This initializes a blank screen of the given dimensions.'''
|
||||
|
||||
self.rows = r
|
||||
self.cols = c
|
||||
self.encoding = encoding
|
||||
self.encoding_errors = encoding_errors
|
||||
if encoding is not None:
|
||||
self.decoder = codecs.getincrementaldecoder(encoding)(encoding_errors)
|
||||
else:
|
||||
self.decoder = None
|
||||
self.cur_r = 1
|
||||
self.cur_c = 1
|
||||
self.cur_saved_r = 1
|
||||
self.cur_saved_c = 1
|
||||
self.scroll_row_start = 1
|
||||
self.scroll_row_end = self.rows
|
||||
self.w = [ [SPACE] * self.cols for _ in range(self.rows)]
|
||||
|
||||
def _decode(self, s):
|
||||
'''This converts from the external coding system (as passed to
|
||||
the constructor) to the internal one (unicode). '''
|
||||
if self.decoder is not None:
|
||||
return self.decoder.decode(s)
|
||||
else:
|
||||
raise TypeError("This screen was constructed with encoding=None, "
|
||||
"so it does not handle bytes.")
|
||||
|
||||
def _unicode(self):
|
||||
'''This returns a printable representation of the screen as a unicode
|
||||
string (which, under Python 3.x, is the same as 'str'). The end of each
|
||||
screen line is terminated by a newline.'''
|
||||
|
||||
return u'\n'.join ([ u''.join(c) for c in self.w ])
|
||||
|
||||
if PY3:
|
||||
__str__ = _unicode
|
||||
else:
|
||||
__unicode__ = _unicode
|
||||
|
||||
def __str__(self):
|
||||
'''This returns a printable representation of the screen. The end of
|
||||
each screen line is terminated by a newline. '''
|
||||
encoding = self.encoding or 'ascii'
|
||||
return self._unicode().encode(encoding, 'replace')
|
||||
|
||||
def dump (self):
|
||||
'''This returns a copy of the screen as a unicode string. This is similar to
|
||||
__str__/__unicode__ except that lines are not terminated with line
|
||||
feeds.'''
|
||||
|
||||
return u''.join ([ u''.join(c) for c in self.w ])
|
||||
|
||||
def pretty (self):
|
||||
'''This returns a copy of the screen as a unicode string with an ASCII
|
||||
text box around the screen border. This is similar to
|
||||
__str__/__unicode__ except that it adds a box.'''
|
||||
|
||||
top_bot = u'+' + u'-'*self.cols + u'+\n'
|
||||
return top_bot + u'\n'.join([u'|'+line+u'|' for line in unicode(self).split(u'\n')]) + u'\n' + top_bot
|
||||
|
||||
def fill (self, ch=SPACE):
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
self.fill_region (1,1,self.rows,self.cols, ch)
|
||||
|
||||
def fill_region (self, rs,cs, re,ce, ch=SPACE):
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
rs = constrain (rs, 1, self.rows)
|
||||
re = constrain (re, 1, self.rows)
|
||||
cs = constrain (cs, 1, self.cols)
|
||||
ce = constrain (ce, 1, self.cols)
|
||||
if rs > re:
|
||||
rs, re = re, rs
|
||||
if cs > ce:
|
||||
cs, ce = ce, cs
|
||||
for r in range (rs, re+1):
|
||||
for c in range (cs, ce + 1):
|
||||
self.put_abs (r,c,ch)
|
||||
|
||||
def cr (self):
|
||||
'''This moves the cursor to the beginning (col 1) of the current row.
|
||||
'''
|
||||
|
||||
self.cursor_home (self.cur_r, 1)
|
||||
|
||||
def lf (self):
|
||||
'''This moves the cursor down with scrolling.
|
||||
'''
|
||||
|
||||
old_r = self.cur_r
|
||||
self.cursor_down()
|
||||
if old_r == self.cur_r:
|
||||
self.scroll_up ()
|
||||
self.erase_line()
|
||||
|
||||
def crlf (self):
|
||||
'''This advances the cursor with CRLF properties.
|
||||
The cursor will line wrap and the screen may scroll.
|
||||
'''
|
||||
|
||||
self.cr ()
|
||||
self.lf ()
|
||||
|
||||
def newline (self):
|
||||
'''This is an alias for crlf().
|
||||
'''
|
||||
|
||||
self.crlf()
|
||||
|
||||
def put_abs (self, r, c, ch):
|
||||
'''Screen array starts at 1 index.'''
|
||||
|
||||
r = constrain (r, 1, self.rows)
|
||||
c = constrain (c, 1, self.cols)
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)[0]
|
||||
else:
|
||||
ch = ch[0]
|
||||
self.w[r-1][c-1] = ch
|
||||
|
||||
def put (self, ch):
|
||||
'''This puts a characters at the current cursor position.
|
||||
'''
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
self.put_abs (self.cur_r, self.cur_c, ch)
|
||||
|
||||
def insert_abs (self, r, c, ch):
|
||||
'''This inserts a character at (r,c). Everything under
|
||||
and to the right is shifted right one character.
|
||||
The last character of the line is lost.
|
||||
'''
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
r = constrain (r, 1, self.rows)
|
||||
c = constrain (c, 1, self.cols)
|
||||
for ci in range (self.cols, c, -1):
|
||||
self.put_abs (r,ci, self.get_abs(r,ci-1))
|
||||
self.put_abs (r,c,ch)
|
||||
|
||||
def insert (self, ch):
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
self.insert_abs (self.cur_r, self.cur_c, ch)
|
||||
|
||||
def get_abs (self, r, c):
|
||||
|
||||
r = constrain (r, 1, self.rows)
|
||||
c = constrain (c, 1, self.cols)
|
||||
return self.w[r-1][c-1]
|
||||
|
||||
def get (self):
|
||||
|
||||
self.get_abs (self.cur_r, self.cur_c)
|
||||
|
||||
def get_region (self, rs,cs, re,ce):
|
||||
'''This returns a list of lines representing the region.
|
||||
'''
|
||||
|
||||
rs = constrain (rs, 1, self.rows)
|
||||
re = constrain (re, 1, self.rows)
|
||||
cs = constrain (cs, 1, self.cols)
|
||||
ce = constrain (ce, 1, self.cols)
|
||||
if rs > re:
|
||||
rs, re = re, rs
|
||||
if cs > ce:
|
||||
cs, ce = ce, cs
|
||||
sc = []
|
||||
for r in range (rs, re+1):
|
||||
line = u''
|
||||
for c in range (cs, ce + 1):
|
||||
ch = self.get_abs (r,c)
|
||||
line = line + ch
|
||||
sc.append (line)
|
||||
return sc
|
||||
|
||||
def cursor_constrain (self):
|
||||
'''This keeps the cursor within the screen area.
|
||||
'''
|
||||
|
||||
self.cur_r = constrain (self.cur_r, 1, self.rows)
|
||||
self.cur_c = constrain (self.cur_c, 1, self.cols)
|
||||
|
||||
def cursor_home (self, r=1, c=1): # <ESC>[{ROW};{COLUMN}H
|
||||
|
||||
self.cur_r = r
|
||||
self.cur_c = c
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_back (self,count=1): # <ESC>[{COUNT}D (not confused with down)
|
||||
|
||||
self.cur_c = self.cur_c - count
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_down (self,count=1): # <ESC>[{COUNT}B (not confused with back)
|
||||
|
||||
self.cur_r = self.cur_r + count
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_forward (self,count=1): # <ESC>[{COUNT}C
|
||||
|
||||
self.cur_c = self.cur_c + count
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_up (self,count=1): # <ESC>[{COUNT}A
|
||||
|
||||
self.cur_r = self.cur_r - count
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_up_reverse (self): # <ESC> M (called RI -- Reverse Index)
|
||||
|
||||
old_r = self.cur_r
|
||||
self.cursor_up()
|
||||
if old_r == self.cur_r:
|
||||
self.scroll_up()
|
||||
|
||||
def cursor_force_position (self, r, c): # <ESC>[{ROW};{COLUMN}f
|
||||
'''Identical to Cursor Home.'''
|
||||
|
||||
self.cursor_home (r, c)
|
||||
|
||||
def cursor_save (self): # <ESC>[s
|
||||
'''Save current cursor position.'''
|
||||
|
||||
self.cursor_save_attrs()
|
||||
|
||||
def cursor_unsave (self): # <ESC>[u
|
||||
'''Restores cursor position after a Save Cursor.'''
|
||||
|
||||
self.cursor_restore_attrs()
|
||||
|
||||
def cursor_save_attrs (self): # <ESC>7
|
||||
'''Save current cursor position.'''
|
||||
|
||||
self.cur_saved_r = self.cur_r
|
||||
self.cur_saved_c = self.cur_c
|
||||
|
||||
def cursor_restore_attrs (self): # <ESC>8
|
||||
'''Restores cursor position after a Save Cursor.'''
|
||||
|
||||
self.cursor_home (self.cur_saved_r, self.cur_saved_c)
|
||||
|
||||
def scroll_constrain (self):
|
||||
'''This keeps the scroll region within the screen region.'''
|
||||
|
||||
if self.scroll_row_start <= 0:
|
||||
self.scroll_row_start = 1
|
||||
if self.scroll_row_end > self.rows:
|
||||
self.scroll_row_end = self.rows
|
||||
|
||||
def scroll_screen (self): # <ESC>[r
|
||||
'''Enable scrolling for entire display.'''
|
||||
|
||||
self.scroll_row_start = 1
|
||||
self.scroll_row_end = self.rows
|
||||
|
||||
def scroll_screen_rows (self, rs, re): # <ESC>[{start};{end}r
|
||||
'''Enable scrolling from row {start} to row {end}.'''
|
||||
|
||||
self.scroll_row_start = rs
|
||||
self.scroll_row_end = re
|
||||
self.scroll_constrain()
|
||||
|
||||
def scroll_down (self): # <ESC>D
|
||||
'''Scroll display down one line.'''
|
||||
|
||||
# Screen is indexed from 1, but arrays are indexed from 0.
|
||||
s = self.scroll_row_start - 1
|
||||
e = self.scroll_row_end - 1
|
||||
self.w[s+1:e+1] = copy.deepcopy(self.w[s:e])
|
||||
|
||||
def scroll_up (self): # <ESC>M
|
||||
'''Scroll display up one line.'''
|
||||
|
||||
# Screen is indexed from 1, but arrays are indexed from 0.
|
||||
s = self.scroll_row_start - 1
|
||||
e = self.scroll_row_end - 1
|
||||
self.w[s:e] = copy.deepcopy(self.w[s+1:e+1])
|
||||
|
||||
def erase_end_of_line (self): # <ESC>[0K -or- <ESC>[K
|
||||
'''Erases from the current cursor position to the end of the current
|
||||
line.'''
|
||||
|
||||
self.fill_region (self.cur_r, self.cur_c, self.cur_r, self.cols)
|
||||
|
||||
def erase_start_of_line (self): # <ESC>[1K
|
||||
'''Erases from the current cursor position to the start of the current
|
||||
line.'''
|
||||
|
||||
self.fill_region (self.cur_r, 1, self.cur_r, self.cur_c)
|
||||
|
||||
def erase_line (self): # <ESC>[2K
|
||||
'''Erases the entire current line.'''
|
||||
|
||||
self.fill_region (self.cur_r, 1, self.cur_r, self.cols)
|
||||
|
||||
def erase_down (self): # <ESC>[0J -or- <ESC>[J
|
||||
'''Erases the screen from the current line down to the bottom of the
|
||||
screen.'''
|
||||
|
||||
self.erase_end_of_line ()
|
||||
self.fill_region (self.cur_r + 1, 1, self.rows, self.cols)
|
||||
|
||||
def erase_up (self): # <ESC>[1J
|
||||
'''Erases the screen from the current line up to the top of the
|
||||
screen.'''
|
||||
|
||||
self.erase_start_of_line ()
|
||||
self.fill_region (self.cur_r-1, 1, 1, self.cols)
|
||||
|
||||
def erase_screen (self): # <ESC>[2J
|
||||
'''Erases the screen with the background color.'''
|
||||
|
||||
self.fill ()
|
||||
|
||||
def set_tab (self): # <ESC>H
|
||||
'''Sets a tab at the current position.'''
|
||||
|
||||
pass
|
||||
|
||||
def clear_tab (self): # <ESC>[g
|
||||
'''Clears tab at the current position.'''
|
||||
|
||||
pass
|
||||
|
||||
def clear_all_tabs (self): # <ESC>[3g
|
||||
'''Clears all tabs.'''
|
||||
|
||||
pass
|
||||
|
||||
# Insert line Esc [ Pn L
|
||||
# Delete line Esc [ Pn M
|
||||
# Delete character Esc [ Pn P
|
||||
# Scrolling region Esc [ Pn(top);Pn(bot) r
|
||||
|
||||
Vendored
-494
@@ -1,494 +0,0 @@
|
||||
import codecs
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import errno
|
||||
from .exceptions import ExceptionPexpect, EOF, TIMEOUT
|
||||
from .expect import Expecter, searcher_string, searcher_re
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
text_type = str if PY3 else unicode
|
||||
|
||||
class _NullCoder(object):
|
||||
"""Pass bytes through unchanged."""
|
||||
@staticmethod
|
||||
def encode(b, final=False):
|
||||
return b
|
||||
|
||||
@staticmethod
|
||||
def decode(b, final=False):
|
||||
return b
|
||||
|
||||
class SpawnBase(object):
|
||||
"""A base class providing the backwards-compatible spawn API for Pexpect.
|
||||
|
||||
This should not be instantiated directly: use :class:`pexpect.spawn` or
|
||||
:class:`pexpect.fdpexpect.fdspawn`.
|
||||
"""
|
||||
encoding = None
|
||||
pid = None
|
||||
flag_eof = False
|
||||
|
||||
def __init__(self, timeout=30, maxread=2000, searchwindowsize=None,
|
||||
logfile=None, encoding=None, codec_errors='strict'):
|
||||
self.stdin = sys.stdin
|
||||
self.stdout = sys.stdout
|
||||
self.stderr = sys.stderr
|
||||
|
||||
self.searcher = None
|
||||
self.ignorecase = False
|
||||
self.before = None
|
||||
self.after = None
|
||||
self.match = None
|
||||
self.match_index = None
|
||||
self.terminated = True
|
||||
self.exitstatus = None
|
||||
self.signalstatus = None
|
||||
# status returned by os.waitpid
|
||||
self.status = None
|
||||
# the child file descriptor is initially closed
|
||||
self.child_fd = -1
|
||||
self.timeout = timeout
|
||||
self.delimiter = EOF
|
||||
self.logfile = logfile
|
||||
# input from child (read_nonblocking)
|
||||
self.logfile_read = None
|
||||
# output to send (send, sendline)
|
||||
self.logfile_send = None
|
||||
# max bytes to read at one time into buffer
|
||||
self.maxread = maxread
|
||||
# This is the read buffer. See maxread.
|
||||
self.buffer = bytes() if (encoding is None) else text_type()
|
||||
# Data before searchwindowsize point is preserved, but not searched.
|
||||
self.searchwindowsize = searchwindowsize
|
||||
# Delay used before sending data to child. Time in seconds.
|
||||
# Set this to None to skip the time.sleep() call completely.
|
||||
self.delaybeforesend = 0.05
|
||||
# Used by close() to give kernel time to update process status.
|
||||
# Time in seconds.
|
||||
self.delayafterclose = 0.1
|
||||
# Used by terminate() to give kernel time to update process status.
|
||||
# Time in seconds.
|
||||
self.delayafterterminate = 0.1
|
||||
# Delay in seconds to sleep after each call to read_nonblocking().
|
||||
# Set this to None to skip the time.sleep() call completely: that
|
||||
# would restore the behavior from pexpect-2.0 (for performance
|
||||
# reasons or because you don't want to release Python's global
|
||||
# interpreter lock).
|
||||
self.delayafterread = 0.0001
|
||||
self.softspace = False
|
||||
self.name = '<' + repr(self) + '>'
|
||||
self.closed = True
|
||||
|
||||
# Unicode interface
|
||||
self.encoding = encoding
|
||||
self.codec_errors = codec_errors
|
||||
if encoding is None:
|
||||
# bytes mode (accepts some unicode for backwards compatibility)
|
||||
self._encoder = self._decoder = _NullCoder()
|
||||
self.string_type = bytes
|
||||
self.crlf = b'\r\n'
|
||||
if PY3:
|
||||
self.allowed_string_types = (bytes, str)
|
||||
self.linesep = os.linesep.encode('ascii')
|
||||
def write_to_stdout(b):
|
||||
try:
|
||||
return sys.stdout.buffer.write(b)
|
||||
except AttributeError:
|
||||
# If stdout has been replaced, it may not have .buffer
|
||||
return sys.stdout.write(b.decode('ascii', 'replace'))
|
||||
self.write_to_stdout = write_to_stdout
|
||||
else:
|
||||
self.allowed_string_types = (basestring,) # analysis:ignore
|
||||
self.linesep = os.linesep
|
||||
self.write_to_stdout = sys.stdout.write
|
||||
else:
|
||||
# unicode mode
|
||||
self._encoder = codecs.getincrementalencoder(encoding)(codec_errors)
|
||||
self._decoder = codecs.getincrementaldecoder(encoding)(codec_errors)
|
||||
self.string_type = text_type
|
||||
self.crlf = u'\r\n'
|
||||
self.allowed_string_types = (text_type, )
|
||||
if PY3:
|
||||
self.linesep = os.linesep
|
||||
else:
|
||||
self.linesep = os.linesep.decode('ascii')
|
||||
# This can handle unicode in both Python 2 and 3
|
||||
self.write_to_stdout = sys.stdout.write
|
||||
|
||||
def _log(self, s, direction):
|
||||
if self.logfile is not None:
|
||||
self.logfile.write(s)
|
||||
self.logfile.flush()
|
||||
second_log = self.logfile_send if (direction=='send') else self.logfile_read
|
||||
if second_log is not None:
|
||||
second_log.write(s)
|
||||
second_log.flush()
|
||||
|
||||
# For backwards compatibility, in bytes mode (when encoding is None)
|
||||
# unicode is accepted for send and expect. Unicode mode is strictly unicode
|
||||
# only.
|
||||
def _coerce_expect_string(self, s):
|
||||
if self.encoding is None and not isinstance(s, bytes):
|
||||
return s.encode('ascii')
|
||||
return s
|
||||
|
||||
def _coerce_send_string(self, s):
|
||||
if self.encoding is None and not isinstance(s, bytes):
|
||||
return s.encode('utf-8')
|
||||
return s
|
||||
|
||||
def read_nonblocking(self, size=1, timeout=None):
|
||||
"""This reads data from the file descriptor.
|
||||
|
||||
This is a simple implementation suitable for a regular file. Subclasses using ptys or pipes should override it.
|
||||
|
||||
The timeout parameter is ignored.
|
||||
"""
|
||||
|
||||
try:
|
||||
s = os.read(self.child_fd, size)
|
||||
except OSError as err:
|
||||
if err.args[0] == errno.EIO:
|
||||
# Linux-style EOF
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF). Exception style platform.')
|
||||
raise
|
||||
if s == b'':
|
||||
# BSD-style EOF
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF). Empty string style platform.')
|
||||
|
||||
s = self._decoder.decode(s, final=False)
|
||||
self._log(s, 'read')
|
||||
return s
|
||||
|
||||
def _pattern_type_err(self, pattern):
|
||||
raise TypeError('got {badtype} ({badobj!r}) as pattern, must be one'
|
||||
' of: {goodtypes}, pexpect.EOF, pexpect.TIMEOUT'\
|
||||
.format(badtype=type(pattern),
|
||||
badobj=pattern,
|
||||
goodtypes=', '.join([str(ast)\
|
||||
for ast in self.allowed_string_types])
|
||||
)
|
||||
)
|
||||
|
||||
def compile_pattern_list(self, patterns):
|
||||
'''This compiles a pattern-string or a list of pattern-strings.
|
||||
Patterns must be a StringType, EOF, TIMEOUT, SRE_Pattern, or a list of
|
||||
those. Patterns may also be None which results in an empty list (you
|
||||
might do this if waiting for an EOF or TIMEOUT condition without
|
||||
expecting any pattern).
|
||||
|
||||
This is used by expect() when calling expect_list(). Thus expect() is
|
||||
nothing more than::
|
||||
|
||||
cpl = self.compile_pattern_list(pl)
|
||||
return self.expect_list(cpl, timeout)
|
||||
|
||||
If you are using expect() within a loop it may be more
|
||||
efficient to compile the patterns first and then call expect_list().
|
||||
This avoid calls in a loop to compile_pattern_list()::
|
||||
|
||||
cpl = self.compile_pattern_list(my_pattern)
|
||||
while some_condition:
|
||||
...
|
||||
i = self.expect_list(cpl, timeout)
|
||||
...
|
||||
'''
|
||||
|
||||
if patterns is None:
|
||||
return []
|
||||
if not isinstance(patterns, list):
|
||||
patterns = [patterns]
|
||||
|
||||
# Allow dot to match \n
|
||||
compile_flags = re.DOTALL
|
||||
if self.ignorecase:
|
||||
compile_flags = compile_flags | re.IGNORECASE
|
||||
compiled_pattern_list = []
|
||||
for idx, p in enumerate(patterns):
|
||||
if isinstance(p, self.allowed_string_types):
|
||||
p = self._coerce_expect_string(p)
|
||||
compiled_pattern_list.append(re.compile(p, compile_flags))
|
||||
elif p is EOF:
|
||||
compiled_pattern_list.append(EOF)
|
||||
elif p is TIMEOUT:
|
||||
compiled_pattern_list.append(TIMEOUT)
|
||||
elif isinstance(p, type(re.compile(''))):
|
||||
compiled_pattern_list.append(p)
|
||||
else:
|
||||
self._pattern_type_err(p)
|
||||
return compiled_pattern_list
|
||||
|
||||
def expect(self, pattern, timeout=-1, searchwindowsize=-1, async=False):
|
||||
'''This seeks through the stream until a pattern is matched. The
|
||||
pattern is overloaded and may take several types. The pattern can be a
|
||||
StringType, EOF, a compiled re, or a list of any of those types.
|
||||
Strings will be compiled to re types. This returns the index into the
|
||||
pattern list. If the pattern was not a list this returns index 0 on a
|
||||
successful match. This may raise exceptions for EOF or TIMEOUT. To
|
||||
avoid the EOF or TIMEOUT exceptions add EOF or TIMEOUT to the pattern
|
||||
list. That will cause expect to match an EOF or TIMEOUT condition
|
||||
instead of raising an exception.
|
||||
|
||||
If you pass a list of patterns and more than one matches, the first
|
||||
match in the stream is chosen. If more than one pattern matches at that
|
||||
point, the leftmost in the pattern list is chosen. For example::
|
||||
|
||||
# the input is 'foobar'
|
||||
index = p.expect(['bar', 'foo', 'foobar'])
|
||||
# returns 1('foo') even though 'foobar' is a "better" match
|
||||
|
||||
Please note, however, that buffering can affect this behavior, since
|
||||
input arrives in unpredictable chunks. For example::
|
||||
|
||||
# the input is 'foobar'
|
||||
index = p.expect(['foobar', 'foo'])
|
||||
# returns 0('foobar') if all input is available at once,
|
||||
# but returs 1('foo') if parts of the final 'bar' arrive late
|
||||
|
||||
When a match is found for the given pattern, the class instance
|
||||
attribute *match* becomes an re.MatchObject result. Should an EOF
|
||||
or TIMEOUT pattern match, then the match attribute will be an instance
|
||||
of that exception class. The pairing before and after class
|
||||
instance attributes are views of the data preceding and following
|
||||
the matching pattern. On general exception, class attribute
|
||||
*before* is all data received up to the exception, while *match* and
|
||||
*after* attributes are value None.
|
||||
|
||||
When the keyword argument timeout is -1 (default), then TIMEOUT will
|
||||
raise after the default value specified by the class timeout
|
||||
attribute. When None, TIMEOUT will not be raised and may block
|
||||
indefinitely until match.
|
||||
|
||||
When the keyword argument searchwindowsize is -1 (default), then the
|
||||
value specified by the class maxread attribute is used.
|
||||
|
||||
A list entry may be EOF or TIMEOUT instead of a string. This will
|
||||
catch these exceptions and return the index of the list entry instead
|
||||
of raising the exception. The attribute 'after' will be set to the
|
||||
exception type. The attribute 'match' will be None. This allows you to
|
||||
write code like this::
|
||||
|
||||
index = p.expect(['good', 'bad', pexpect.EOF, pexpect.TIMEOUT])
|
||||
if index == 0:
|
||||
do_something()
|
||||
elif index == 1:
|
||||
do_something_else()
|
||||
elif index == 2:
|
||||
do_some_other_thing()
|
||||
elif index == 3:
|
||||
do_something_completely_different()
|
||||
|
||||
instead of code like this::
|
||||
|
||||
try:
|
||||
index = p.expect(['good', 'bad'])
|
||||
if index == 0:
|
||||
do_something()
|
||||
elif index == 1:
|
||||
do_something_else()
|
||||
except EOF:
|
||||
do_some_other_thing()
|
||||
except TIMEOUT:
|
||||
do_something_completely_different()
|
||||
|
||||
These two forms are equivalent. It all depends on what you want. You
|
||||
can also just expect the EOF if you are waiting for all output of a
|
||||
child to finish. For example::
|
||||
|
||||
p = pexpect.spawn('/bin/ls')
|
||||
p.expect(pexpect.EOF)
|
||||
print p.before
|
||||
|
||||
If you are trying to optimize for speed then see expect_list().
|
||||
|
||||
On Python 3.4, or Python 3.3 with asyncio installed, passing
|
||||
``async=True`` will make this return an :mod:`asyncio` coroutine,
|
||||
which you can yield from to get the same result that this method would
|
||||
normally give directly. So, inside a coroutine, you can replace this code::
|
||||
|
||||
index = p.expect(patterns)
|
||||
|
||||
With this non-blocking form::
|
||||
|
||||
index = yield from p.expect(patterns, async=True)
|
||||
'''
|
||||
|
||||
compiled_pattern_list = self.compile_pattern_list(pattern)
|
||||
return self.expect_list(compiled_pattern_list,
|
||||
timeout, searchwindowsize, async)
|
||||
|
||||
def expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1,
|
||||
async=False):
|
||||
'''This takes a list of compiled regular expressions and returns the
|
||||
index into the pattern_list that matched the child output. The list may
|
||||
also contain EOF or TIMEOUT(which are not compiled regular
|
||||
expressions). This method is similar to the expect() method except that
|
||||
expect_list() does not recompile the pattern list on every call. This
|
||||
may help if you are trying to optimize for speed, otherwise just use
|
||||
the expect() method. This is called by expect().
|
||||
|
||||
|
||||
Like :meth:`expect`, passing ``async=True`` will make this return an
|
||||
asyncio coroutine.
|
||||
'''
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
|
||||
exp = Expecter(self, searcher_re(pattern_list), searchwindowsize)
|
||||
if async:
|
||||
from .async import expect_async
|
||||
return expect_async(exp, timeout)
|
||||
else:
|
||||
return exp.expect_loop(timeout)
|
||||
|
||||
def expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1,
|
||||
async=False):
|
||||
|
||||
'''This is similar to expect(), but uses plain string matching instead
|
||||
of compiled regular expressions in 'pattern_list'. The 'pattern_list'
|
||||
may be a string; a list or other sequence of strings; or TIMEOUT and
|
||||
EOF.
|
||||
|
||||
This call might be faster than expect() for two reasons: string
|
||||
searching is faster than RE matching and it is possible to limit the
|
||||
search to just the end of the input buffer.
|
||||
|
||||
This method is also useful when you don't want to have to worry about
|
||||
escaping regular expression characters that you want to match.
|
||||
|
||||
Like :meth:`expect`, passing ``async=True`` will make this return an
|
||||
asyncio coroutine.
|
||||
'''
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
|
||||
if (isinstance(pattern_list, self.allowed_string_types) or
|
||||
pattern_list in (TIMEOUT, EOF)):
|
||||
pattern_list = [pattern_list]
|
||||
|
||||
def prepare_pattern(pattern):
|
||||
if pattern in (TIMEOUT, EOF):
|
||||
return pattern
|
||||
if isinstance(pattern, self.allowed_string_types):
|
||||
return self._coerce_expect_string(pattern)
|
||||
self._pattern_type_err(pattern)
|
||||
|
||||
try:
|
||||
pattern_list = iter(pattern_list)
|
||||
except TypeError:
|
||||
self._pattern_type_err(pattern_list)
|
||||
pattern_list = [prepare_pattern(p) for p in pattern_list]
|
||||
|
||||
exp = Expecter(self, searcher_string(pattern_list), searchwindowsize)
|
||||
if async:
|
||||
from .async import expect_async
|
||||
return expect_async(exp, timeout)
|
||||
else:
|
||||
return exp.expect_loop(timeout)
|
||||
|
||||
def expect_loop(self, searcher, timeout=-1, searchwindowsize=-1):
|
||||
'''This is the common loop used inside expect. The 'searcher' should be
|
||||
an instance of searcher_re or searcher_string, which describes how and
|
||||
what to search for in the input.
|
||||
|
||||
See expect() for other arguments, return value and exceptions. '''
|
||||
|
||||
exp = Expecter(self, searcher, searchwindowsize)
|
||||
return exp.expect_loop(timeout)
|
||||
|
||||
def read(self, size=-1):
|
||||
'''This reads at most "size" bytes from the file (less if the read hits
|
||||
EOF before obtaining size bytes). If the size argument is negative or
|
||||
omitted, read all data until EOF is reached. The bytes are returned as
|
||||
a string object. An empty string is returned when EOF is encountered
|
||||
immediately. '''
|
||||
|
||||
if size == 0:
|
||||
return self.string_type()
|
||||
if size < 0:
|
||||
# delimiter default is EOF
|
||||
self.expect(self.delimiter)
|
||||
return self.before
|
||||
|
||||
# I could have done this more directly by not using expect(), but
|
||||
# I deliberately decided to couple read() to expect() so that
|
||||
# I would catch any bugs early and ensure consistant behavior.
|
||||
# It's a little less efficient, but there is less for me to
|
||||
# worry about if I have to later modify read() or expect().
|
||||
# Note, it's OK if size==-1 in the regex. That just means it
|
||||
# will never match anything in which case we stop only on EOF.
|
||||
cre = re.compile(self._coerce_expect_string('.{%d}' % size), re.DOTALL)
|
||||
# delimiter default is EOF
|
||||
index = self.expect([cre, self.delimiter])
|
||||
if index == 0:
|
||||
### FIXME self.before should be ''. Should I assert this?
|
||||
return self.after
|
||||
return self.before
|
||||
|
||||
def readline(self, size=-1):
|
||||
'''This reads and returns one entire line. The newline at the end of
|
||||
line is returned as part of the string, unless the file ends without a
|
||||
newline. An empty string is returned if EOF is encountered immediately.
|
||||
This looks for a newline as a CR/LF pair (\\r\\n) even on UNIX because
|
||||
this is what the pseudotty device returns. So contrary to what you may
|
||||
expect you will receive newlines as \\r\\n.
|
||||
|
||||
If the size argument is 0 then an empty string is returned. In all
|
||||
other cases the size argument is ignored, which is not standard
|
||||
behavior for a file-like object. '''
|
||||
|
||||
if size == 0:
|
||||
return self.string_type()
|
||||
# delimiter default is EOF
|
||||
index = self.expect([self.crlf, self.delimiter])
|
||||
if index == 0:
|
||||
return self.before + self.crlf
|
||||
else:
|
||||
return self.before
|
||||
|
||||
def __iter__(self):
|
||||
'''This is to support iterators over a file-like object.
|
||||
'''
|
||||
return iter(self.readline, self.string_type())
|
||||
|
||||
def readlines(self, sizehint=-1):
|
||||
'''This reads until EOF using readline() and returns a list containing
|
||||
the lines thus read. The optional 'sizehint' argument is ignored.
|
||||
Remember, because this reads until EOF that means the child
|
||||
process should have closed its stdout. If you run this method on
|
||||
a child that is still running with its stdout open then this
|
||||
method will block until it timesout.'''
|
||||
|
||||
lines = []
|
||||
while True:
|
||||
line = self.readline()
|
||||
if not line:
|
||||
break
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def fileno(self):
|
||||
'''Expose file descriptor for a file-like interface
|
||||
'''
|
||||
return self.child_fd
|
||||
|
||||
def flush(self):
|
||||
'''This does nothing. It is here to support the interface for a
|
||||
File-like object. '''
|
||||
pass
|
||||
|
||||
def isatty(self):
|
||||
"""Overridden in subclass using tty"""
|
||||
return False
|
||||
|
||||
# For 'with spawn(...) as child:'
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, etype, evalue, tb):
|
||||
# We rely on subclasses to implement close(). If they don't, it's not
|
||||
# clear what a context manager should do.
|
||||
self.close()
|
||||
Vendored
-151
@@ -1,151 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import stat
|
||||
import select
|
||||
import time
|
||||
import errno
|
||||
|
||||
try:
|
||||
InterruptedError
|
||||
except NameError:
|
||||
# Alias Python2 exception to Python3
|
||||
InterruptedError = select.error
|
||||
|
||||
|
||||
def is_executable_file(path):
|
||||
"""Checks that path is an executable regular file, or a symlink towards one.
|
||||
|
||||
This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``.
|
||||
"""
|
||||
# follow symlinks,
|
||||
fpath = os.path.realpath(path)
|
||||
|
||||
if not os.path.isfile(fpath):
|
||||
# non-files (directories, fifo, etc.)
|
||||
return False
|
||||
|
||||
mode = os.stat(fpath).st_mode
|
||||
|
||||
if (sys.platform.startswith('sunos')
|
||||
and os.getuid() == 0):
|
||||
# When root on Solaris, os.X_OK is True for *all* files, irregardless
|
||||
# of their executability -- instead, any permission bit of any user,
|
||||
# group, or other is fine enough.
|
||||
#
|
||||
# (This may be true for other "Unix98" OS's such as HP-UX and AIX)
|
||||
return bool(mode & (stat.S_IXUSR |
|
||||
stat.S_IXGRP |
|
||||
stat.S_IXOTH))
|
||||
|
||||
return os.access(fpath, os.X_OK)
|
||||
|
||||
|
||||
def which(filename, env=None):
|
||||
'''This takes a given filename; tries to find it in the environment path;
|
||||
then checks if it is executable. This returns the full path to the filename
|
||||
if found and executable. Otherwise this returns None.'''
|
||||
|
||||
# Special case where filename contains an explicit path.
|
||||
if os.path.dirname(filename) != '' and is_executable_file(filename):
|
||||
return filename
|
||||
if env is None:
|
||||
env = os.environ
|
||||
p = env.get('PATH')
|
||||
if not p:
|
||||
p = os.defpath
|
||||
pathlist = p.split(os.pathsep)
|
||||
for path in pathlist:
|
||||
ff = os.path.join(path, filename)
|
||||
if is_executable_file(ff):
|
||||
return ff
|
||||
return None
|
||||
|
||||
|
||||
def split_command_line(command_line):
|
||||
|
||||
'''This splits a command line into a list of arguments. It splits arguments
|
||||
on spaces, but handles embedded quotes, doublequotes, and escaped
|
||||
characters. It's impossible to do this with a regular expression, so I
|
||||
wrote a little state machine to parse the command line. '''
|
||||
|
||||
arg_list = []
|
||||
arg = ''
|
||||
|
||||
# Constants to name the states we can be in.
|
||||
state_basic = 0
|
||||
state_esc = 1
|
||||
state_singlequote = 2
|
||||
state_doublequote = 3
|
||||
# The state when consuming whitespace between commands.
|
||||
state_whitespace = 4
|
||||
state = state_basic
|
||||
|
||||
for c in command_line:
|
||||
if state == state_basic or state == state_whitespace:
|
||||
if c == '\\':
|
||||
# Escape the next character
|
||||
state = state_esc
|
||||
elif c == r"'":
|
||||
# Handle single quote
|
||||
state = state_singlequote
|
||||
elif c == r'"':
|
||||
# Handle double quote
|
||||
state = state_doublequote
|
||||
elif c.isspace():
|
||||
# Add arg to arg_list if we aren't in the middle of whitespace.
|
||||
if state == state_whitespace:
|
||||
# Do nothing.
|
||||
None
|
||||
else:
|
||||
arg_list.append(arg)
|
||||
arg = ''
|
||||
state = state_whitespace
|
||||
else:
|
||||
arg = arg + c
|
||||
state = state_basic
|
||||
elif state == state_esc:
|
||||
arg = arg + c
|
||||
state = state_basic
|
||||
elif state == state_singlequote:
|
||||
if c == r"'":
|
||||
state = state_basic
|
||||
else:
|
||||
arg = arg + c
|
||||
elif state == state_doublequote:
|
||||
if c == r'"':
|
||||
state = state_basic
|
||||
else:
|
||||
arg = arg + c
|
||||
|
||||
if arg != '':
|
||||
arg_list.append(arg)
|
||||
return arg_list
|
||||
|
||||
|
||||
def select_ignore_interrupts(iwtd, owtd, ewtd, timeout=None):
|
||||
|
||||
'''This is a wrapper around select.select() that ignores signals. If
|
||||
select.select raises a select.error exception and errno is an EINTR
|
||||
error then it is ignored. Mainly this is used to ignore sigwinch
|
||||
(terminal resize). '''
|
||||
|
||||
# if select() is interrupted by a signal (errno==EINTR) then
|
||||
# we loop back and enter the select() again.
|
||||
if timeout is not None:
|
||||
end_time = time.time() + timeout
|
||||
while True:
|
||||
try:
|
||||
return select.select(iwtd, owtd, ewtd, timeout)
|
||||
except InterruptedError:
|
||||
err = sys.exc_info()[1]
|
||||
if err.args[0] == errno.EINTR:
|
||||
# if we loop back we have to subtract the
|
||||
# amount of time we already waited.
|
||||
if timeout is not None:
|
||||
timeout = end_time - time.time()
|
||||
if timeout < 0:
|
||||
return([], [], [])
|
||||
else:
|
||||
# something else caused the select.error, so
|
||||
# this actually is an exception.
|
||||
raise
|
||||
Vendored
-2376
File diff suppressed because it is too large
Load Diff
Vendored
-449
@@ -1,449 +0,0 @@
|
||||
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Common objects shared by __init__.py and _ps*.py modules."""
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import contextlib
|
||||
import errno
|
||||
import functools
|
||||
import os
|
||||
import socket
|
||||
import stat
|
||||
import sys
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
from socket import AF_INET
|
||||
from socket import SOCK_DGRAM
|
||||
from socket import SOCK_STREAM
|
||||
try:
|
||||
from socket import AF_INET6
|
||||
except ImportError:
|
||||
AF_INET6 = None
|
||||
try:
|
||||
from socket import AF_UNIX
|
||||
except ImportError:
|
||||
AF_UNIX = None
|
||||
|
||||
if sys.version_info >= (3, 4):
|
||||
import enum
|
||||
else:
|
||||
enum = None
|
||||
|
||||
__all__ = [
|
||||
# OS constants
|
||||
'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'OSX', 'POSIX', 'SUNOS',
|
||||
'WINDOWS',
|
||||
# connection constants
|
||||
'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED',
|
||||
'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN',
|
||||
'CONN_NONE', 'CONN_SYN_RECV', 'CONN_SYN_SENT', 'CONN_TIME_WAIT',
|
||||
# net constants
|
||||
'NIC_DUPLEX_FULL', 'NIC_DUPLEX_HALF', 'NIC_DUPLEX_UNKNOWN',
|
||||
# process status constants
|
||||
'STATUS_DEAD', 'STATUS_DISK_SLEEP', 'STATUS_IDLE', 'STATUS_LOCKED',
|
||||
'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED',
|
||||
'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL',
|
||||
'STATUS_WAKING', 'STATUS_ZOMBIE',
|
||||
# named tuples
|
||||
'pconn', 'pcputimes', 'pctxsw', 'pgids', 'pio', 'pionice', 'popenfile',
|
||||
'pthread', 'puids', 'sconn', 'scpustats', 'sdiskio', 'sdiskpart',
|
||||
'sdiskusage', 'snetio', 'snic', 'snicstats', 'sswap', 'suser',
|
||||
# utility functions
|
||||
'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize',
|
||||
'parse_environ_block', 'path_exists_strict', 'usage_percent',
|
||||
'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum',
|
||||
]
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# --- OS constants
|
||||
# ===================================================================
|
||||
|
||||
|
||||
POSIX = os.name == "posix"
|
||||
WINDOWS = os.name == "nt"
|
||||
LINUX = sys.platform.startswith("linux")
|
||||
OSX = sys.platform.startswith("darwin")
|
||||
FREEBSD = sys.platform.startswith("freebsd")
|
||||
OPENBSD = sys.platform.startswith("openbsd")
|
||||
NETBSD = sys.platform.startswith("netbsd")
|
||||
BSD = FREEBSD or OPENBSD or NETBSD
|
||||
SUNOS = sys.platform.startswith("sunos") or sys.platform.startswith("solaris")
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# --- API constants
|
||||
# ===================================================================
|
||||
|
||||
|
||||
# Process.status()
|
||||
STATUS_RUNNING = "running"
|
||||
STATUS_SLEEPING = "sleeping"
|
||||
STATUS_DISK_SLEEP = "disk-sleep"
|
||||
STATUS_STOPPED = "stopped"
|
||||
STATUS_TRACING_STOP = "tracing-stop"
|
||||
STATUS_ZOMBIE = "zombie"
|
||||
STATUS_DEAD = "dead"
|
||||
STATUS_WAKE_KILL = "wake-kill"
|
||||
STATUS_WAKING = "waking"
|
||||
STATUS_IDLE = "idle" # FreeBSD, OSX
|
||||
STATUS_LOCKED = "locked" # FreeBSD
|
||||
STATUS_WAITING = "waiting" # FreeBSD
|
||||
STATUS_SUSPENDED = "suspended" # NetBSD
|
||||
|
||||
# Process.connections() and psutil.net_connections()
|
||||
CONN_ESTABLISHED = "ESTABLISHED"
|
||||
CONN_SYN_SENT = "SYN_SENT"
|
||||
CONN_SYN_RECV = "SYN_RECV"
|
||||
CONN_FIN_WAIT1 = "FIN_WAIT1"
|
||||
CONN_FIN_WAIT2 = "FIN_WAIT2"
|
||||
CONN_TIME_WAIT = "TIME_WAIT"
|
||||
CONN_CLOSE = "CLOSE"
|
||||
CONN_CLOSE_WAIT = "CLOSE_WAIT"
|
||||
CONN_LAST_ACK = "LAST_ACK"
|
||||
CONN_LISTEN = "LISTEN"
|
||||
CONN_CLOSING = "CLOSING"
|
||||
CONN_NONE = "NONE"
|
||||
|
||||
# net_if_stats()
|
||||
if enum is None:
|
||||
NIC_DUPLEX_FULL = 2
|
||||
NIC_DUPLEX_HALF = 1
|
||||
NIC_DUPLEX_UNKNOWN = 0
|
||||
else:
|
||||
class NicDuplex(enum.IntEnum):
|
||||
NIC_DUPLEX_FULL = 2
|
||||
NIC_DUPLEX_HALF = 1
|
||||
NIC_DUPLEX_UNKNOWN = 0
|
||||
|
||||
globals().update(NicDuplex.__members__)
|
||||
|
||||
# sensors_battery()
|
||||
if enum is None:
|
||||
POWER_TIME_UNKNOWN = -1
|
||||
POWER_TIME_UNLIMITED = -2
|
||||
else:
|
||||
class BatteryTime(enum.IntEnum):
|
||||
POWER_TIME_UNKNOWN = -1
|
||||
POWER_TIME_UNLIMITED = -2
|
||||
|
||||
globals().update(BatteryTime.__members__)
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# --- namedtuples
|
||||
# ===================================================================
|
||||
|
||||
# --- for system functions
|
||||
|
||||
# psutil.swap_memory()
|
||||
sswap = namedtuple('sswap', ['total', 'used', 'free', 'percent', 'sin',
|
||||
'sout'])
|
||||
# psutil.disk_usage()
|
||||
sdiskusage = namedtuple('sdiskusage', ['total', 'used', 'free', 'percent'])
|
||||
# psutil.disk_io_counters()
|
||||
sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
|
||||
'read_bytes', 'write_bytes',
|
||||
'read_time', 'write_time'])
|
||||
# psutil.disk_partitions()
|
||||
sdiskpart = namedtuple('sdiskpart', ['device', 'mountpoint', 'fstype', 'opts'])
|
||||
# psutil.net_io_counters()
|
||||
snetio = namedtuple('snetio', ['bytes_sent', 'bytes_recv',
|
||||
'packets_sent', 'packets_recv',
|
||||
'errin', 'errout',
|
||||
'dropin', 'dropout'])
|
||||
# psutil.users()
|
||||
suser = namedtuple('suser', ['name', 'terminal', 'host', 'started'])
|
||||
# psutil.net_connections()
|
||||
sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr',
|
||||
'status', 'pid'])
|
||||
# psutil.net_if_addrs()
|
||||
snic = namedtuple('snic', ['family', 'address', 'netmask', 'broadcast', 'ptp'])
|
||||
# psutil.net_if_stats()
|
||||
snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu'])
|
||||
# psutil.cpu_stats()
|
||||
scpustats = namedtuple(
|
||||
'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls'])
|
||||
# psutil.cpu_freq()
|
||||
scpufreq = namedtuple('scpufreq', ['current', 'min', 'max'])
|
||||
# psutil.sensors_temperatures()
|
||||
shwtemp = namedtuple(
|
||||
'shwtemp', ['label', 'current', 'high', 'critical'])
|
||||
# psutil.sensors_battery()
|
||||
sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged'])
|
||||
# psutil.sensors_battery()
|
||||
sfan = namedtuple('sfan', ['label', 'current'])
|
||||
|
||||
# --- for Process methods
|
||||
|
||||
# psutil.Process.cpu_times()
|
||||
pcputimes = namedtuple('pcputimes',
|
||||
['user', 'system', 'children_user', 'children_system'])
|
||||
# psutil.Process.open_files()
|
||||
popenfile = namedtuple('popenfile', ['path', 'fd'])
|
||||
# psutil.Process.threads()
|
||||
pthread = namedtuple('pthread', ['id', 'user_time', 'system_time'])
|
||||
# psutil.Process.uids()
|
||||
puids = namedtuple('puids', ['real', 'effective', 'saved'])
|
||||
# psutil.Process.gids()
|
||||
pgids = namedtuple('pgids', ['real', 'effective', 'saved'])
|
||||
# psutil.Process.io_counters()
|
||||
pio = namedtuple('pio', ['read_count', 'write_count',
|
||||
'read_bytes', 'write_bytes'])
|
||||
# psutil.Process.ionice()
|
||||
pionice = namedtuple('pionice', ['ioclass', 'value'])
|
||||
# psutil.Process.ctx_switches()
|
||||
pctxsw = namedtuple('pctxsw', ['voluntary', 'involuntary'])
|
||||
# psutil.Process.connections()
|
||||
pconn = namedtuple('pconn', ['fd', 'family', 'type', 'laddr', 'raddr',
|
||||
'status'])
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# --- Process.connections() 'kind' parameter mapping
|
||||
# ===================================================================
|
||||
|
||||
|
||||
conn_tmap = {
|
||||
"all": ([AF_INET, AF_INET6, AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
|
||||
"tcp": ([AF_INET, AF_INET6], [SOCK_STREAM]),
|
||||
"tcp4": ([AF_INET], [SOCK_STREAM]),
|
||||
"udp": ([AF_INET, AF_INET6], [SOCK_DGRAM]),
|
||||
"udp4": ([AF_INET], [SOCK_DGRAM]),
|
||||
"inet": ([AF_INET, AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
|
||||
"inet4": ([AF_INET], [SOCK_STREAM, SOCK_DGRAM]),
|
||||
"inet6": ([AF_INET6], [SOCK_STREAM, SOCK_DGRAM]),
|
||||
}
|
||||
|
||||
if AF_INET6 is not None:
|
||||
conn_tmap.update({
|
||||
"tcp6": ([AF_INET6], [SOCK_STREAM]),
|
||||
"udp6": ([AF_INET6], [SOCK_DGRAM]),
|
||||
})
|
||||
|
||||
if AF_UNIX is not None:
|
||||
conn_tmap.update({
|
||||
"unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]),
|
||||
})
|
||||
|
||||
del AF_INET, AF_INET6, AF_UNIX, SOCK_STREAM, SOCK_DGRAM
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# --- utils
|
||||
# ===================================================================
|
||||
|
||||
|
||||
def usage_percent(used, total, _round=None):
|
||||
"""Calculate percentage usage of 'used' against 'total'."""
|
||||
try:
|
||||
ret = (used / total) * 100
|
||||
except ZeroDivisionError:
|
||||
ret = 0.0 if isinstance(used, float) or isinstance(total, float) else 0
|
||||
if _round is not None:
|
||||
return round(ret, _round)
|
||||
else:
|
||||
return ret
|
||||
|
||||
|
||||
def memoize(fun):
|
||||
"""A simple memoize decorator for functions supporting (hashable)
|
||||
positional arguments.
|
||||
It also provides a cache_clear() function for clearing the cache:
|
||||
|
||||
>>> @memoize
|
||||
... def foo()
|
||||
... return 1
|
||||
...
|
||||
>>> foo()
|
||||
1
|
||||
>>> foo.cache_clear()
|
||||
>>>
|
||||
"""
|
||||
@functools.wraps(fun)
|
||||
def wrapper(*args, **kwargs):
|
||||
key = (args, frozenset(sorted(kwargs.items())))
|
||||
try:
|
||||
return cache[key]
|
||||
except KeyError:
|
||||
ret = cache[key] = fun(*args, **kwargs)
|
||||
return ret
|
||||
|
||||
def cache_clear():
|
||||
"""Clear cache."""
|
||||
cache.clear()
|
||||
|
||||
cache = {}
|
||||
wrapper.cache_clear = cache_clear
|
||||
return wrapper
|
||||
|
||||
|
||||
def memoize_when_activated(fun):
|
||||
"""A memoize decorator which is disabled by default. It can be
|
||||
activated and deactivated on request.
|
||||
For efficiency reasons it can be used only against class methods
|
||||
accepting no arguments.
|
||||
|
||||
>>> class Foo:
|
||||
... @memoize
|
||||
... def foo()
|
||||
... print(1)
|
||||
...
|
||||
>>> f = Foo()
|
||||
>>> # deactivated (default)
|
||||
>>> foo()
|
||||
1
|
||||
>>> foo()
|
||||
1
|
||||
>>>
|
||||
>>> # activated
|
||||
>>> foo.cache_activate()
|
||||
>>> foo()
|
||||
1
|
||||
>>> foo()
|
||||
>>> foo()
|
||||
>>>
|
||||
"""
|
||||
@functools.wraps(fun)
|
||||
def wrapper(self):
|
||||
if not wrapper.cache_activated:
|
||||
return fun(self)
|
||||
else:
|
||||
try:
|
||||
ret = cache[fun]
|
||||
except KeyError:
|
||||
ret = cache[fun] = fun(self)
|
||||
return ret
|
||||
|
||||
def cache_activate():
|
||||
"""Activate cache."""
|
||||
wrapper.cache_activated = True
|
||||
|
||||
def cache_deactivate():
|
||||
"""Deactivate and clear cache."""
|
||||
wrapper.cache_activated = False
|
||||
cache.clear()
|
||||
|
||||
cache = {}
|
||||
wrapper.cache_activated = False
|
||||
wrapper.cache_activate = cache_activate
|
||||
wrapper.cache_deactivate = cache_deactivate
|
||||
return wrapper
|
||||
|
||||
|
||||
def isfile_strict(path):
|
||||
"""Same as os.path.isfile() but does not swallow EACCES / EPERM
|
||||
exceptions, see:
|
||||
http://mail.python.org/pipermail/python-dev/2012-June/120787.html
|
||||
"""
|
||||
try:
|
||||
st = os.stat(path)
|
||||
except OSError as err:
|
||||
if err.errno in (errno.EPERM, errno.EACCES):
|
||||
raise
|
||||
return False
|
||||
else:
|
||||
return stat.S_ISREG(st.st_mode)
|
||||
|
||||
|
||||
def path_exists_strict(path):
|
||||
"""Same as os.path.exists() but does not swallow EACCES / EPERM
|
||||
exceptions, see:
|
||||
http://mail.python.org/pipermail/python-dev/2012-June/120787.html
|
||||
"""
|
||||
try:
|
||||
os.stat(path)
|
||||
except OSError as err:
|
||||
if err.errno in (errno.EPERM, errno.EACCES):
|
||||
raise
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def supports_ipv6():
|
||||
"""Return True if IPv6 is supported on this platform."""
|
||||
if not socket.has_ipv6 or not hasattr(socket, "AF_INET6"):
|
||||
return False
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
with contextlib.closing(sock):
|
||||
sock.bind(("::1", 0))
|
||||
return True
|
||||
except socket.error:
|
||||
return False
|
||||
|
||||
|
||||
def parse_environ_block(data):
|
||||
"""Parse a C environ block of environment variables into a dictionary."""
|
||||
# The block is usually raw data from the target process. It might contain
|
||||
# trailing garbage and lines that do not look like assignments.
|
||||
ret = {}
|
||||
pos = 0
|
||||
|
||||
# localize global variable to speed up access.
|
||||
WINDOWS_ = WINDOWS
|
||||
while True:
|
||||
next_pos = data.find("\0", pos)
|
||||
# nul byte at the beginning or double nul byte means finish
|
||||
if next_pos <= pos:
|
||||
break
|
||||
# there might not be an equals sign
|
||||
equal_pos = data.find("=", pos, next_pos)
|
||||
if equal_pos > pos:
|
||||
key = data[pos:equal_pos]
|
||||
value = data[equal_pos + 1:next_pos]
|
||||
# Windows expects environment variables to be uppercase only
|
||||
if WINDOWS_:
|
||||
key = key.upper()
|
||||
ret[key] = value
|
||||
pos = next_pos + 1
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def sockfam_to_enum(num):
|
||||
"""Convert a numeric socket family value to an IntEnum member.
|
||||
If it's not a known member, return the numeric value itself.
|
||||
"""
|
||||
if enum is None:
|
||||
return num
|
||||
else: # pragma: no cover
|
||||
try:
|
||||
return socket.AddressFamily(num)
|
||||
except (ValueError, AttributeError):
|
||||
return num
|
||||
|
||||
|
||||
def socktype_to_enum(num):
|
||||
"""Convert a numeric socket type value to an IntEnum member.
|
||||
If it's not a known member, return the numeric value itself.
|
||||
"""
|
||||
if enum is None:
|
||||
return num
|
||||
else: # pragma: no cover
|
||||
try:
|
||||
return socket.AddressType(num)
|
||||
except (ValueError, AttributeError):
|
||||
return num
|
||||
|
||||
|
||||
def deprecated_method(replacement):
|
||||
"""A decorator which can be used to mark a method as deprecated
|
||||
'replcement' is the method name which will be called instead.
|
||||
"""
|
||||
def outer(fun):
|
||||
msg = "%s() is deprecated; use %s() instead" % (
|
||||
fun.__name__, replacement)
|
||||
if fun.__doc__ is None:
|
||||
fun.__doc__ = msg
|
||||
|
||||
@functools.wraps(fun)
|
||||
def inner(self, *args, **kwargs):
|
||||
warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
|
||||
return getattr(self, replacement)(*args, **kwargs)
|
||||
return inner
|
||||
return outer
|
||||
Vendored
-249
@@ -1,249 +0,0 @@
|
||||
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Module which provides compatibility with older Python versions."""
|
||||
|
||||
import collections
|
||||
import functools
|
||||
import os
|
||||
import sys
|
||||
|
||||
__all__ = ["PY3", "long", "xrange", "unicode", "basestring", "u", "b",
|
||||
"callable", "lru_cache", "which"]
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
|
||||
if PY3:
|
||||
long = int
|
||||
xrange = range
|
||||
unicode = str
|
||||
basestring = str
|
||||
|
||||
def u(s):
|
||||
return s
|
||||
|
||||
def b(s):
|
||||
return s.encode("latin-1")
|
||||
else:
|
||||
long = long
|
||||
xrange = xrange
|
||||
unicode = unicode
|
||||
basestring = basestring
|
||||
|
||||
def u(s):
|
||||
return unicode(s, "unicode_escape")
|
||||
|
||||
def b(s):
|
||||
return s
|
||||
|
||||
|
||||
# removed in 3.0, reintroduced in 3.2
|
||||
try:
|
||||
callable = callable
|
||||
except NameError:
|
||||
def callable(obj):
|
||||
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
|
||||
|
||||
|
||||
# --- stdlib additions
|
||||
|
||||
|
||||
# py 3.2 functools.lru_cache
|
||||
# Taken from: http://code.activestate.com/recipes/578078
|
||||
# Credit: Raymond Hettinger
|
||||
try:
|
||||
from functools import lru_cache
|
||||
except ImportError:
|
||||
try:
|
||||
from threading import RLock
|
||||
except ImportError:
|
||||
from dummy_threading import RLock
|
||||
|
||||
_CacheInfo = collections.namedtuple(
|
||||
"CacheInfo", ["hits", "misses", "maxsize", "currsize"])
|
||||
|
||||
class _HashedSeq(list):
|
||||
__slots__ = 'hashvalue'
|
||||
|
||||
def __init__(self, tup, hash=hash):
|
||||
self[:] = tup
|
||||
self.hashvalue = hash(tup)
|
||||
|
||||
def __hash__(self):
|
||||
return self.hashvalue
|
||||
|
||||
def _make_key(args, kwds, typed,
|
||||
kwd_mark=(object(), ),
|
||||
fasttypes=set((int, str, frozenset, type(None))),
|
||||
sorted=sorted, tuple=tuple, type=type, len=len):
|
||||
key = args
|
||||
if kwds:
|
||||
sorted_items = sorted(kwds.items())
|
||||
key += kwd_mark
|
||||
for item in sorted_items:
|
||||
key += item
|
||||
if typed:
|
||||
key += tuple(type(v) for v in args)
|
||||
if kwds:
|
||||
key += tuple(type(v) for k, v in sorted_items)
|
||||
elif len(key) == 1 and type(key[0]) in fasttypes:
|
||||
return key[0]
|
||||
return _HashedSeq(key)
|
||||
|
||||
def lru_cache(maxsize=100, typed=False):
|
||||
"""Least-recently-used cache decorator, see:
|
||||
http://docs.python.org/3/library/functools.html#functools.lru_cache
|
||||
"""
|
||||
def decorating_function(user_function):
|
||||
cache = dict()
|
||||
stats = [0, 0]
|
||||
HITS, MISSES = 0, 1
|
||||
make_key = _make_key
|
||||
cache_get = cache.get
|
||||
_len = len
|
||||
lock = RLock()
|
||||
root = []
|
||||
root[:] = [root, root, None, None]
|
||||
nonlocal_root = [root]
|
||||
PREV, NEXT, KEY, RESULT = 0, 1, 2, 3
|
||||
if maxsize == 0:
|
||||
def wrapper(*args, **kwds):
|
||||
result = user_function(*args, **kwds)
|
||||
stats[MISSES] += 1
|
||||
return result
|
||||
elif maxsize is None:
|
||||
def wrapper(*args, **kwds):
|
||||
key = make_key(args, kwds, typed)
|
||||
result = cache_get(key, root)
|
||||
if result is not root:
|
||||
stats[HITS] += 1
|
||||
return result
|
||||
result = user_function(*args, **kwds)
|
||||
cache[key] = result
|
||||
stats[MISSES] += 1
|
||||
return result
|
||||
else:
|
||||
def wrapper(*args, **kwds):
|
||||
if kwds or typed:
|
||||
key = make_key(args, kwds, typed)
|
||||
else:
|
||||
key = args
|
||||
lock.acquire()
|
||||
try:
|
||||
link = cache_get(key)
|
||||
if link is not None:
|
||||
root, = nonlocal_root
|
||||
link_prev, link_next, key, result = link
|
||||
link_prev[NEXT] = link_next
|
||||
link_next[PREV] = link_prev
|
||||
last = root[PREV]
|
||||
last[NEXT] = root[PREV] = link
|
||||
link[PREV] = last
|
||||
link[NEXT] = root
|
||||
stats[HITS] += 1
|
||||
return result
|
||||
finally:
|
||||
lock.release()
|
||||
result = user_function(*args, **kwds)
|
||||
lock.acquire()
|
||||
try:
|
||||
root, = nonlocal_root
|
||||
if key in cache:
|
||||
pass
|
||||
elif _len(cache) >= maxsize:
|
||||
oldroot = root
|
||||
oldroot[KEY] = key
|
||||
oldroot[RESULT] = result
|
||||
root = nonlocal_root[0] = oldroot[NEXT]
|
||||
oldkey = root[KEY]
|
||||
root[KEY] = root[RESULT] = None
|
||||
del cache[oldkey]
|
||||
cache[key] = oldroot
|
||||
else:
|
||||
last = root[PREV]
|
||||
link = [last, root, key, result]
|
||||
last[NEXT] = root[PREV] = cache[key] = link
|
||||
stats[MISSES] += 1
|
||||
finally:
|
||||
lock.release()
|
||||
return result
|
||||
|
||||
def cache_info():
|
||||
"""Report cache statistics"""
|
||||
lock.acquire()
|
||||
try:
|
||||
return _CacheInfo(stats[HITS], stats[MISSES], maxsize,
|
||||
len(cache))
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def cache_clear():
|
||||
"""Clear the cache and cache statistics"""
|
||||
lock.acquire()
|
||||
try:
|
||||
cache.clear()
|
||||
root = nonlocal_root[0]
|
||||
root[:] = [root, root, None, None]
|
||||
stats[:] = [0, 0]
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
wrapper.__wrapped__ = user_function
|
||||
wrapper.cache_info = cache_info
|
||||
wrapper.cache_clear = cache_clear
|
||||
return functools.update_wrapper(wrapper, user_function)
|
||||
|
||||
return decorating_function
|
||||
|
||||
|
||||
# python 3.3
|
||||
try:
|
||||
from shutil import which
|
||||
except ImportError:
|
||||
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
||||
"""Given a command, mode, and a PATH string, return the path which
|
||||
conforms to the given mode on the PATH, or None if there is no such
|
||||
file.
|
||||
|
||||
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
|
||||
of os.environ.get("PATH"), or can be overridden with a custom search
|
||||
path.
|
||||
"""
|
||||
def _access_check(fn, mode):
|
||||
return (os.path.exists(fn) and os.access(fn, mode) and
|
||||
not os.path.isdir(fn))
|
||||
|
||||
if os.path.dirname(cmd):
|
||||
if _access_check(cmd, mode):
|
||||
return cmd
|
||||
return None
|
||||
|
||||
if path is None:
|
||||
path = os.environ.get("PATH", os.defpath)
|
||||
if not path:
|
||||
return None
|
||||
path = path.split(os.pathsep)
|
||||
|
||||
if sys.platform == "win32":
|
||||
if os.curdir not in path:
|
||||
path.insert(0, os.curdir)
|
||||
|
||||
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
|
||||
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
|
||||
files = [cmd]
|
||||
else:
|
||||
files = [cmd + ext for ext in pathext]
|
||||
else:
|
||||
files = [cmd]
|
||||
|
||||
seen = set()
|
||||
for dir in path:
|
||||
normdir = os.path.normcase(dir)
|
||||
if normdir not in seen:
|
||||
seen.add(normdir)
|
||||
for thefile in files:
|
||||
name = os.path.join(dir, thefile)
|
||||
if _access_check(name, mode):
|
||||
return name
|
||||
return None
|
||||
Vendored
-855
@@ -1,855 +0,0 @@
|
||||
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""FreeBSD, OpenBSD and NetBSD platforms implementation."""
|
||||
|
||||
import contextlib
|
||||
import errno
|
||||
import functools
|
||||
import os
|
||||
import xml.etree.ElementTree as ET
|
||||
from collections import namedtuple
|
||||
|
||||
from . import _common
|
||||
from . import _psposix
|
||||
from . import _psutil_bsd as cext
|
||||
from . import _psutil_posix as cext_posix
|
||||
from ._common import conn_tmap
|
||||
from ._common import FREEBSD
|
||||
from ._common import memoize
|
||||
from ._common import memoize_when_activated
|
||||
from ._common import NETBSD
|
||||
from ._common import OPENBSD
|
||||
from ._common import sockfam_to_enum
|
||||
from ._common import socktype_to_enum
|
||||
from ._common import usage_percent
|
||||
from ._compat import which
|
||||
|
||||
__extra__all__ = []
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- globals
|
||||
# =====================================================================
|
||||
|
||||
|
||||
if FREEBSD:
|
||||
PROC_STATUSES = {
|
||||
cext.SIDL: _common.STATUS_IDLE,
|
||||
cext.SRUN: _common.STATUS_RUNNING,
|
||||
cext.SSLEEP: _common.STATUS_SLEEPING,
|
||||
cext.SSTOP: _common.STATUS_STOPPED,
|
||||
cext.SZOMB: _common.STATUS_ZOMBIE,
|
||||
cext.SWAIT: _common.STATUS_WAITING,
|
||||
cext.SLOCK: _common.STATUS_LOCKED,
|
||||
}
|
||||
elif OPENBSD or NETBSD:
|
||||
PROC_STATUSES = {
|
||||
cext.SIDL: _common.STATUS_IDLE,
|
||||
cext.SSLEEP: _common.STATUS_SLEEPING,
|
||||
cext.SSTOP: _common.STATUS_STOPPED,
|
||||
# According to /usr/include/sys/proc.h SZOMB is unused.
|
||||
# test_zombie_process() shows that SDEAD is the right
|
||||
# equivalent. Also it appears there's no equivalent of
|
||||
# psutil.STATUS_DEAD. SDEAD really means STATUS_ZOMBIE.
|
||||
# cext.SZOMB: _common.STATUS_ZOMBIE,
|
||||
cext.SDEAD: _common.STATUS_ZOMBIE,
|
||||
cext.SZOMB: _common.STATUS_ZOMBIE,
|
||||
# From http://www.eecs.harvard.edu/~margo/cs161/videos/proc.h.txt
|
||||
# OpenBSD has SRUN and SONPROC: SRUN indicates that a process
|
||||
# is runnable but *not* yet running, i.e. is on a run queue.
|
||||
# SONPROC indicates that the process is actually executing on
|
||||
# a CPU, i.e. it is no longer on a run queue.
|
||||
# As such we'll map SRUN to STATUS_WAKING and SONPROC to
|
||||
# STATUS_RUNNING
|
||||
cext.SRUN: _common.STATUS_WAKING,
|
||||
cext.SONPROC: _common.STATUS_RUNNING,
|
||||
}
|
||||
elif NETBSD:
|
||||
PROC_STATUSES = {
|
||||
cext.SIDL: _common.STATUS_IDLE,
|
||||
cext.SACTIVE: _common.STATUS_RUNNING,
|
||||
cext.SDYING: _common.STATUS_ZOMBIE,
|
||||
cext.SSTOP: _common.STATUS_STOPPED,
|
||||
cext.SZOMB: _common.STATUS_ZOMBIE,
|
||||
cext.SDEAD: _common.STATUS_DEAD,
|
||||
cext.SSUSPENDED: _common.STATUS_SUSPENDED, # unique to NetBSD
|
||||
}
|
||||
|
||||
TCP_STATUSES = {
|
||||
cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
|
||||
cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
|
||||
cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV,
|
||||
cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
|
||||
cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
|
||||
cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
|
||||
cext.TCPS_CLOSED: _common.CONN_CLOSE,
|
||||
cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
|
||||
cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
|
||||
cext.TCPS_LISTEN: _common.CONN_LISTEN,
|
||||
cext.TCPS_CLOSING: _common.CONN_CLOSING,
|
||||
cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
|
||||
}
|
||||
|
||||
if NETBSD:
|
||||
PAGESIZE = os.sysconf("SC_PAGESIZE")
|
||||
else:
|
||||
PAGESIZE = os.sysconf("SC_PAGE_SIZE")
|
||||
AF_LINK = cext_posix.AF_LINK
|
||||
|
||||
kinfo_proc_map = dict(
|
||||
ppid=0,
|
||||
status=1,
|
||||
real_uid=2,
|
||||
effective_uid=3,
|
||||
saved_uid=4,
|
||||
real_gid=5,
|
||||
effective_gid=6,
|
||||
saved_gid=7,
|
||||
ttynr=8,
|
||||
create_time=9,
|
||||
ctx_switches_vol=10,
|
||||
ctx_switches_unvol=11,
|
||||
read_io_count=12,
|
||||
write_io_count=13,
|
||||
user_time=14,
|
||||
sys_time=15,
|
||||
ch_user_time=16,
|
||||
ch_sys_time=17,
|
||||
rss=18,
|
||||
vms=19,
|
||||
memtext=20,
|
||||
memdata=21,
|
||||
memstack=22,
|
||||
cpunum=23,
|
||||
name=24,
|
||||
)
|
||||
|
||||
# these get overwritten on "import psutil" from the __init__.py file
|
||||
NoSuchProcess = None
|
||||
ZombieProcess = None
|
||||
AccessDenied = None
|
||||
TimeoutExpired = None
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- named tuples
|
||||
# =====================================================================
|
||||
|
||||
|
||||
# psutil.virtual_memory()
|
||||
svmem = namedtuple(
|
||||
'svmem', ['total', 'available', 'percent', 'used', 'free',
|
||||
'active', 'inactive', 'buffers', 'cached', 'shared', 'wired'])
|
||||
# psutil.cpu_times()
|
||||
scputimes = namedtuple(
|
||||
'scputimes', ['user', 'nice', 'system', 'idle', 'irq'])
|
||||
# psutil.Process.memory_info()
|
||||
pmem = namedtuple('pmem', ['rss', 'vms', 'text', 'data', 'stack'])
|
||||
# psutil.Process.memory_full_info()
|
||||
pfullmem = pmem
|
||||
# psutil.Process.cpu_times()
|
||||
pcputimes = namedtuple('pcputimes',
|
||||
['user', 'system', 'children_user', 'children_system'])
|
||||
# psutil.Process.memory_maps(grouped=True)
|
||||
pmmap_grouped = namedtuple(
|
||||
'pmmap_grouped', 'path rss, private, ref_count, shadow_count')
|
||||
# psutil.Process.memory_maps(grouped=False)
|
||||
pmmap_ext = namedtuple(
|
||||
'pmmap_ext', 'addr, perms path rss, private, ref_count, shadow_count')
|
||||
# psutil.disk_io_counters()
|
||||
if FREEBSD:
|
||||
sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
|
||||
'read_bytes', 'write_bytes',
|
||||
'read_time', 'write_time',
|
||||
'busy_time'])
|
||||
else:
|
||||
sdiskio = namedtuple('sdiskio', ['read_count', 'write_count',
|
||||
'read_bytes', 'write_bytes'])
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- memory
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def virtual_memory():
|
||||
"""System virtual memory as a namedtuple."""
|
||||
mem = cext.virtual_mem()
|
||||
total, free, active, inactive, wired, cached, buffers, shared = mem
|
||||
if NETBSD:
|
||||
# On NetBSD buffers and shared mem is determined via /proc.
|
||||
# The C ext set them to 0.
|
||||
with open('/proc/meminfo', 'rb') as f:
|
||||
for line in f:
|
||||
if line.startswith(b'Buffers:'):
|
||||
buffers = int(line.split()[1]) * 1024
|
||||
elif line.startswith(b'MemShared:'):
|
||||
shared = int(line.split()[1]) * 1024
|
||||
avail = inactive + cached + free
|
||||
used = active + wired + cached
|
||||
percent = usage_percent((total - avail), total, _round=1)
|
||||
return svmem(total, avail, percent, used, free,
|
||||
active, inactive, buffers, cached, shared, wired)
|
||||
|
||||
|
||||
def swap_memory():
|
||||
"""System swap memory as (total, used, free, sin, sout) namedtuple."""
|
||||
total, used, free, sin, sout = cext.swap_mem()
|
||||
percent = usage_percent(used, total, _round=1)
|
||||
return _common.sswap(total, used, free, percent, sin, sout)
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- CPU
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def cpu_times():
|
||||
"""Return system per-CPU times as a namedtuple"""
|
||||
user, nice, system, idle, irq = cext.cpu_times()
|
||||
return scputimes(user, nice, system, idle, irq)
|
||||
|
||||
|
||||
if hasattr(cext, "per_cpu_times"):
|
||||
def per_cpu_times():
|
||||
"""Return system CPU times as a namedtuple"""
|
||||
ret = []
|
||||
for cpu_t in cext.per_cpu_times():
|
||||
user, nice, system, idle, irq = cpu_t
|
||||
item = scputimes(user, nice, system, idle, irq)
|
||||
ret.append(item)
|
||||
return ret
|
||||
else:
|
||||
# XXX
|
||||
# Ok, this is very dirty.
|
||||
# On FreeBSD < 8 we cannot gather per-cpu information, see:
|
||||
# https://github.com/giampaolo/psutil/issues/226
|
||||
# If num cpus > 1, on first call we return single cpu times to avoid a
|
||||
# crash at psutil import time.
|
||||
# Next calls will fail with NotImplementedError
|
||||
def per_cpu_times():
|
||||
"""Return system CPU times as a namedtuple"""
|
||||
if cpu_count_logical() == 1:
|
||||
return [cpu_times()]
|
||||
if per_cpu_times.__called__:
|
||||
raise NotImplementedError("supported only starting from FreeBSD 8")
|
||||
per_cpu_times.__called__ = True
|
||||
return [cpu_times()]
|
||||
|
||||
per_cpu_times.__called__ = False
|
||||
|
||||
|
||||
def cpu_count_logical():
|
||||
"""Return the number of logical CPUs in the system."""
|
||||
return cext.cpu_count_logical()
|
||||
|
||||
|
||||
if OPENBSD or NETBSD:
|
||||
def cpu_count_physical():
|
||||
# OpenBSD and NetBSD do not implement this.
|
||||
return 1 if cpu_count_logical() == 1 else None
|
||||
else:
|
||||
def cpu_count_physical():
|
||||
"""Return the number of physical CPUs in the system."""
|
||||
# From the C module we'll get an XML string similar to this:
|
||||
# http://manpages.ubuntu.com/manpages/precise/man4/smp.4freebsd.html
|
||||
# We may get None in case "sysctl kern.sched.topology_spec"
|
||||
# is not supported on this BSD version, in which case we'll mimic
|
||||
# os.cpu_count() and return None.
|
||||
ret = None
|
||||
s = cext.cpu_count_phys()
|
||||
if s is not None:
|
||||
# get rid of padding chars appended at the end of the string
|
||||
index = s.rfind("</groups>")
|
||||
if index != -1:
|
||||
s = s[:index + 9]
|
||||
root = ET.fromstring(s)
|
||||
try:
|
||||
ret = len(root.findall('group/children/group/cpu')) or None
|
||||
finally:
|
||||
# needed otherwise it will memleak
|
||||
root.clear()
|
||||
if not ret:
|
||||
# If logical CPUs are 1 it's obvious we'll have only 1
|
||||
# physical CPU.
|
||||
if cpu_count_logical() == 1:
|
||||
return 1
|
||||
return ret
|
||||
|
||||
|
||||
def cpu_stats():
|
||||
"""Return various CPU stats as a named tuple."""
|
||||
if FREEBSD:
|
||||
# Note: the C ext is returning some metrics we are not exposing:
|
||||
# traps.
|
||||
ctxsw, intrs, soft_intrs, syscalls, traps = cext.cpu_stats()
|
||||
elif NETBSD:
|
||||
# XXX
|
||||
# Note about intrs: the C extension returns 0. intrs
|
||||
# can be determined via /proc/stat; it has the same value as
|
||||
# soft_intrs thought so the kernel is faking it (?).
|
||||
#
|
||||
# Note about syscalls: the C extension always sets it to 0 (?).
|
||||
#
|
||||
# Note: the C ext is returning some metrics we are not exposing:
|
||||
# traps, faults and forks.
|
||||
ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = \
|
||||
cext.cpu_stats()
|
||||
with open('/proc/stat', 'rb') as f:
|
||||
for line in f:
|
||||
if line.startswith(b'intr'):
|
||||
intrs = int(line.split()[1])
|
||||
elif OPENBSD:
|
||||
# Note: the C ext is returning some metrics we are not exposing:
|
||||
# traps, faults and forks.
|
||||
ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = \
|
||||
cext.cpu_stats()
|
||||
return _common.scpustats(ctxsw, intrs, soft_intrs, syscalls)
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- disks
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def disk_partitions(all=False):
|
||||
"""Return mounted disk partitions as a list of namedtuples.
|
||||
'all' argument is ignored, see:
|
||||
https://github.com/giampaolo/psutil/issues/906
|
||||
"""
|
||||
retlist = []
|
||||
partitions = cext.disk_partitions()
|
||||
for partition in partitions:
|
||||
device, mountpoint, fstype, opts = partition
|
||||
ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
|
||||
retlist.append(ntuple)
|
||||
return retlist
|
||||
|
||||
|
||||
disk_usage = _psposix.disk_usage
|
||||
disk_io_counters = cext.disk_io_counters
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- network
|
||||
# =====================================================================
|
||||
|
||||
|
||||
net_io_counters = cext.net_io_counters
|
||||
net_if_addrs = cext_posix.net_if_addrs
|
||||
|
||||
|
||||
def net_if_stats():
|
||||
"""Get NIC stats (isup, duplex, speed, mtu)."""
|
||||
names = net_io_counters().keys()
|
||||
ret = {}
|
||||
for name in names:
|
||||
mtu = cext_posix.net_if_mtu(name)
|
||||
isup = cext_posix.net_if_flags(name)
|
||||
duplex, speed = cext_posix.net_if_duplex_speed(name)
|
||||
if hasattr(_common, 'NicDuplex'):
|
||||
duplex = _common.NicDuplex(duplex)
|
||||
ret[name] = _common.snicstats(isup, duplex, speed, mtu)
|
||||
return ret
|
||||
|
||||
|
||||
def net_connections(kind):
|
||||
"""System-wide network connections."""
|
||||
if OPENBSD:
|
||||
ret = []
|
||||
for pid in pids():
|
||||
try:
|
||||
cons = Process(pid).connections(kind)
|
||||
except (NoSuchProcess, ZombieProcess):
|
||||
continue
|
||||
else:
|
||||
for conn in cons:
|
||||
conn = list(conn)
|
||||
conn.append(pid)
|
||||
ret.append(_common.sconn(*conn))
|
||||
return ret
|
||||
|
||||
if kind not in _common.conn_tmap:
|
||||
raise ValueError("invalid %r kind argument; choose between %s"
|
||||
% (kind, ', '.join([repr(x) for x in conn_tmap])))
|
||||
families, types = conn_tmap[kind]
|
||||
ret = set()
|
||||
if NETBSD:
|
||||
rawlist = cext.net_connections(-1)
|
||||
else:
|
||||
rawlist = cext.net_connections()
|
||||
for item in rawlist:
|
||||
fd, fam, type, laddr, raddr, status, pid = item
|
||||
# TODO: apply filter at C level
|
||||
if fam in families and type in types:
|
||||
try:
|
||||
status = TCP_STATUSES[status]
|
||||
except KeyError:
|
||||
# XXX: Not sure why this happens. I saw this occurring
|
||||
# with IPv6 sockets opened by 'vim'. Those sockets
|
||||
# have a very short lifetime so maybe the kernel
|
||||
# can't initialize their status?
|
||||
status = TCP_STATUSES[cext.PSUTIL_CONN_NONE]
|
||||
fam = sockfam_to_enum(fam)
|
||||
type = socktype_to_enum(type)
|
||||
nt = _common.sconn(fd, fam, type, laddr, raddr, status, pid)
|
||||
ret.add(nt)
|
||||
return list(ret)
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- sensors
|
||||
# =====================================================================
|
||||
|
||||
|
||||
if FREEBSD:
|
||||
|
||||
def sensors_battery():
|
||||
"""Return battery info."""
|
||||
percent, minsleft, power_plugged = cext.sensors_battery()
|
||||
power_plugged = power_plugged == 1
|
||||
if power_plugged:
|
||||
secsleft = _common.POWER_TIME_UNLIMITED
|
||||
elif minsleft == -1:
|
||||
secsleft = _common.POWER_TIME_UNKNOWN
|
||||
else:
|
||||
secsleft = minsleft * 60
|
||||
return _common.sbattery(percent, secsleft, power_plugged)
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- other system functions
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def boot_time():
|
||||
"""The system boot time expressed in seconds since the epoch."""
|
||||
return cext.boot_time()
|
||||
|
||||
|
||||
def users():
|
||||
"""Return currently connected users as a list of namedtuples."""
|
||||
retlist = []
|
||||
rawlist = cext.users()
|
||||
for item in rawlist:
|
||||
user, tty, hostname, tstamp = item
|
||||
if tty == '~':
|
||||
continue # reboot or shutdown
|
||||
nt = _common.suser(user, tty or None, hostname, tstamp)
|
||||
retlist.append(nt)
|
||||
return retlist
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- processes
|
||||
# =====================================================================
|
||||
|
||||
|
||||
@memoize
|
||||
def _pid_0_exists():
|
||||
try:
|
||||
Process(0).name()
|
||||
except NoSuchProcess:
|
||||
return False
|
||||
except AccessDenied:
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def pids():
|
||||
"""Returns a list of PIDs currently running on the system."""
|
||||
ret = cext.pids()
|
||||
if OPENBSD and (0 not in ret) and _pid_0_exists():
|
||||
# On OpenBSD the kernel does not return PID 0 (neither does
|
||||
# ps) but it's actually querable (Process(0) will succeed).
|
||||
ret.insert(0, 0)
|
||||
return ret
|
||||
|
||||
|
||||
if OPENBSD or NETBSD:
|
||||
def pid_exists(pid):
|
||||
"""Return True if pid exists."""
|
||||
exists = _psposix.pid_exists(pid)
|
||||
if not exists:
|
||||
# We do this because _psposix.pid_exists() lies in case of
|
||||
# zombie processes.
|
||||
return pid in pids()
|
||||
else:
|
||||
return True
|
||||
else:
|
||||
pid_exists = _psposix.pid_exists
|
||||
|
||||
|
||||
def wrap_exceptions(fun):
|
||||
"""Decorator which translates bare OSError exceptions into
|
||||
NoSuchProcess and AccessDenied.
|
||||
"""
|
||||
@functools.wraps(fun)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
try:
|
||||
return fun(self, *args, **kwargs)
|
||||
except OSError as err:
|
||||
if self.pid == 0:
|
||||
if 0 in pids():
|
||||
raise AccessDenied(self.pid, self._name)
|
||||
else:
|
||||
raise
|
||||
if err.errno == errno.ESRCH:
|
||||
if not pid_exists(self.pid):
|
||||
raise NoSuchProcess(self.pid, self._name)
|
||||
else:
|
||||
raise ZombieProcess(self.pid, self._name, self._ppid)
|
||||
if err.errno in (errno.EPERM, errno.EACCES):
|
||||
raise AccessDenied(self.pid, self._name)
|
||||
raise
|
||||
return wrapper
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def wrap_exceptions_procfs(inst):
|
||||
"""Same as above, for routines relying on reading /proc fs."""
|
||||
try:
|
||||
yield
|
||||
except EnvironmentError as err:
|
||||
# ENOENT (no such file or directory) gets raised on open().
|
||||
# ESRCH (no such process) can get raised on read() if
|
||||
# process is gone in meantime.
|
||||
if err.errno in (errno.ENOENT, errno.ESRCH):
|
||||
if not pid_exists(inst.pid):
|
||||
raise NoSuchProcess(inst.pid, inst._name)
|
||||
else:
|
||||
raise ZombieProcess(inst.pid, inst._name, inst._ppid)
|
||||
if err.errno in (errno.EPERM, errno.EACCES):
|
||||
raise AccessDenied(inst.pid, inst._name)
|
||||
raise
|
||||
|
||||
|
||||
class Process(object):
|
||||
"""Wrapper class around underlying C implementation."""
|
||||
|
||||
__slots__ = ["pid", "_name", "_ppid"]
|
||||
|
||||
def __init__(self, pid):
|
||||
self.pid = pid
|
||||
self._name = None
|
||||
self._ppid = None
|
||||
|
||||
@memoize_when_activated
|
||||
def oneshot(self):
|
||||
"""Retrieves multiple process info in one shot as a raw tuple."""
|
||||
ret = cext.proc_oneshot_info(self.pid)
|
||||
assert len(ret) == len(kinfo_proc_map)
|
||||
return ret
|
||||
|
||||
def oneshot_enter(self):
|
||||
self.oneshot.cache_activate()
|
||||
|
||||
def oneshot_exit(self):
|
||||
self.oneshot.cache_deactivate()
|
||||
|
||||
@wrap_exceptions
|
||||
def name(self):
|
||||
name = self.oneshot()[kinfo_proc_map['name']]
|
||||
return name if name is not None else cext.proc_name(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def exe(self):
|
||||
if FREEBSD:
|
||||
return cext.proc_exe(self.pid)
|
||||
elif NETBSD:
|
||||
if self.pid == 0:
|
||||
# /proc/0 dir exists but /proc/0/exe doesn't
|
||||
return ""
|
||||
with wrap_exceptions_procfs(self):
|
||||
return os.readlink("/proc/%s/exe" % self.pid)
|
||||
else:
|
||||
# OpenBSD: exe cannot be determined; references:
|
||||
# https://chromium.googlesource.com/chromium/src/base/+/
|
||||
# master/base_paths_posix.cc
|
||||
# We try our best guess by using which against the first
|
||||
# cmdline arg (may return None).
|
||||
cmdline = self.cmdline()
|
||||
if cmdline:
|
||||
return which(cmdline[0])
|
||||
else:
|
||||
return ""
|
||||
|
||||
@wrap_exceptions
|
||||
def cmdline(self):
|
||||
if OPENBSD and self.pid == 0:
|
||||
return [] # ...else it crashes
|
||||
elif NETBSD:
|
||||
# XXX - most of the times the underlying sysctl() call on Net
|
||||
# and Open BSD returns a truncated string.
|
||||
# Also /proc/pid/cmdline behaves the same so it looks
|
||||
# like this is a kernel bug.
|
||||
try:
|
||||
return cext.proc_cmdline(self.pid)
|
||||
except OSError as err:
|
||||
if err.errno == errno.EINVAL:
|
||||
if not pid_exists(self.pid):
|
||||
raise NoSuchProcess(self.pid, self._name)
|
||||
else:
|
||||
raise ZombieProcess(self.pid, self._name, self._ppid)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
return cext.proc_cmdline(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def terminal(self):
|
||||
tty_nr = self.oneshot()[kinfo_proc_map['ttynr']]
|
||||
tmap = _psposix.get_terminal_map()
|
||||
try:
|
||||
return tmap[tty_nr]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
@wrap_exceptions
|
||||
def ppid(self):
|
||||
self._ppid = self.oneshot()[kinfo_proc_map['ppid']]
|
||||
return self._ppid
|
||||
|
||||
@wrap_exceptions
|
||||
def uids(self):
|
||||
rawtuple = self.oneshot()
|
||||
return _common.puids(
|
||||
rawtuple[kinfo_proc_map['real_uid']],
|
||||
rawtuple[kinfo_proc_map['effective_uid']],
|
||||
rawtuple[kinfo_proc_map['saved_uid']])
|
||||
|
||||
@wrap_exceptions
|
||||
def gids(self):
|
||||
rawtuple = self.oneshot()
|
||||
return _common.pgids(
|
||||
rawtuple[kinfo_proc_map['real_gid']],
|
||||
rawtuple[kinfo_proc_map['effective_gid']],
|
||||
rawtuple[kinfo_proc_map['saved_gid']])
|
||||
|
||||
@wrap_exceptions
|
||||
def cpu_times(self):
|
||||
rawtuple = self.oneshot()
|
||||
return _common.pcputimes(
|
||||
rawtuple[kinfo_proc_map['user_time']],
|
||||
rawtuple[kinfo_proc_map['sys_time']],
|
||||
rawtuple[kinfo_proc_map['ch_user_time']],
|
||||
rawtuple[kinfo_proc_map['ch_sys_time']])
|
||||
|
||||
if FREEBSD:
|
||||
@wrap_exceptions
|
||||
def cpu_num(self):
|
||||
return self.oneshot()[kinfo_proc_map['cpunum']]
|
||||
|
||||
@wrap_exceptions
|
||||
def memory_info(self):
|
||||
rawtuple = self.oneshot()
|
||||
return pmem(
|
||||
rawtuple[kinfo_proc_map['rss']],
|
||||
rawtuple[kinfo_proc_map['vms']],
|
||||
rawtuple[kinfo_proc_map['memtext']],
|
||||
rawtuple[kinfo_proc_map['memdata']],
|
||||
rawtuple[kinfo_proc_map['memstack']])
|
||||
|
||||
memory_full_info = memory_info
|
||||
|
||||
@wrap_exceptions
|
||||
def create_time(self):
|
||||
return self.oneshot()[kinfo_proc_map['create_time']]
|
||||
|
||||
@wrap_exceptions
|
||||
def num_threads(self):
|
||||
if hasattr(cext, "proc_num_threads"):
|
||||
# FreeBSD
|
||||
return cext.proc_num_threads(self.pid)
|
||||
else:
|
||||
return len(self.threads())
|
||||
|
||||
@wrap_exceptions
|
||||
def num_ctx_switches(self):
|
||||
rawtuple = self.oneshot()
|
||||
return _common.pctxsw(
|
||||
rawtuple[kinfo_proc_map['ctx_switches_vol']],
|
||||
rawtuple[kinfo_proc_map['ctx_switches_unvol']])
|
||||
|
||||
@wrap_exceptions
|
||||
def threads(self):
|
||||
# Note: on OpenSBD this (/dev/mem) requires root access.
|
||||
rawlist = cext.proc_threads(self.pid)
|
||||
retlist = []
|
||||
for thread_id, utime, stime in rawlist:
|
||||
ntuple = _common.pthread(thread_id, utime, stime)
|
||||
retlist.append(ntuple)
|
||||
if OPENBSD:
|
||||
# On OpenBSD the underlying C function does not raise NSP
|
||||
# in case the process is gone (and the returned list may
|
||||
# incomplete).
|
||||
self.name() # raise NSP if the process disappeared on us
|
||||
return retlist
|
||||
|
||||
@wrap_exceptions
|
||||
def connections(self, kind='inet'):
|
||||
if kind not in conn_tmap:
|
||||
raise ValueError("invalid %r kind argument; choose between %s"
|
||||
% (kind, ', '.join([repr(x) for x in conn_tmap])))
|
||||
|
||||
if NETBSD:
|
||||
families, types = conn_tmap[kind]
|
||||
ret = set()
|
||||
rawlist = cext.net_connections(self.pid)
|
||||
for item in rawlist:
|
||||
fd, fam, type, laddr, raddr, status, pid = item
|
||||
assert pid == self.pid
|
||||
if fam in families and type in types:
|
||||
try:
|
||||
status = TCP_STATUSES[status]
|
||||
except KeyError:
|
||||
status = TCP_STATUSES[cext.PSUTIL_CONN_NONE]
|
||||
fam = sockfam_to_enum(fam)
|
||||
type = socktype_to_enum(type)
|
||||
nt = _common.pconn(fd, fam, type, laddr, raddr, status)
|
||||
ret.add(nt)
|
||||
# On NetBSD the underlying C function does not raise NSP
|
||||
# in case the process is gone (and the returned list may
|
||||
# incomplete).
|
||||
self.name() # raise NSP if the process disappeared on us
|
||||
return list(ret)
|
||||
|
||||
families, types = conn_tmap[kind]
|
||||
rawlist = cext.proc_connections(self.pid, families, types)
|
||||
ret = []
|
||||
for item in rawlist:
|
||||
fd, fam, type, laddr, raddr, status = item
|
||||
fam = sockfam_to_enum(fam)
|
||||
type = socktype_to_enum(type)
|
||||
status = TCP_STATUSES[status]
|
||||
nt = _common.pconn(fd, fam, type, laddr, raddr, status)
|
||||
ret.append(nt)
|
||||
if OPENBSD:
|
||||
# On OpenBSD the underlying C function does not raise NSP
|
||||
# in case the process is gone (and the returned list may
|
||||
# incomplete).
|
||||
self.name() # raise NSP if the process disappeared on us
|
||||
return ret
|
||||
|
||||
@wrap_exceptions
|
||||
def wait(self, timeout=None):
|
||||
try:
|
||||
return _psposix.wait_pid(self.pid, timeout)
|
||||
except _psposix.TimeoutExpired:
|
||||
raise TimeoutExpired(timeout, self.pid, self._name)
|
||||
|
||||
@wrap_exceptions
|
||||
def nice_get(self):
|
||||
return cext_posix.getpriority(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def nice_set(self, value):
|
||||
return cext_posix.setpriority(self.pid, value)
|
||||
|
||||
@wrap_exceptions
|
||||
def status(self):
|
||||
code = self.oneshot()[kinfo_proc_map['status']]
|
||||
# XXX is '?' legit? (we're not supposed to return it anyway)
|
||||
return PROC_STATUSES.get(code, '?')
|
||||
|
||||
@wrap_exceptions
|
||||
def io_counters(self):
|
||||
rawtuple = self.oneshot()
|
||||
return _common.pio(
|
||||
rawtuple[kinfo_proc_map['read_io_count']],
|
||||
rawtuple[kinfo_proc_map['write_io_count']],
|
||||
-1,
|
||||
-1)
|
||||
|
||||
@wrap_exceptions
|
||||
def cwd(self):
|
||||
"""Return process current working directory."""
|
||||
# sometimes we get an empty string, in which case we turn
|
||||
# it into None
|
||||
if OPENBSD and self.pid == 0:
|
||||
return None # ...else it would raise EINVAL
|
||||
elif NETBSD:
|
||||
with wrap_exceptions_procfs(self):
|
||||
return os.readlink("/proc/%s/cwd" % self.pid)
|
||||
elif hasattr(cext, 'proc_open_files'):
|
||||
# FreeBSD < 8 does not support functions based on
|
||||
# kinfo_getfile() and kinfo_getvmmap()
|
||||
return cext.proc_cwd(self.pid) or None
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"supported only starting from FreeBSD 8" if
|
||||
FREEBSD else "")
|
||||
|
||||
nt_mmap_grouped = namedtuple(
|
||||
'mmap', 'path rss, private, ref_count, shadow_count')
|
||||
nt_mmap_ext = namedtuple(
|
||||
'mmap', 'addr, perms path rss, private, ref_count, shadow_count')
|
||||
|
||||
def _not_implemented(self):
|
||||
raise NotImplementedError
|
||||
|
||||
# FreeBSD < 8 does not support functions based on kinfo_getfile()
|
||||
# and kinfo_getvmmap()
|
||||
if hasattr(cext, 'proc_open_files'):
|
||||
@wrap_exceptions
|
||||
def open_files(self):
|
||||
"""Return files opened by process as a list of namedtuples."""
|
||||
rawlist = cext.proc_open_files(self.pid)
|
||||
return [_common.popenfile(path, fd) for path, fd in rawlist]
|
||||
else:
|
||||
open_files = _not_implemented
|
||||
|
||||
# FreeBSD < 8 does not support functions based on kinfo_getfile()
|
||||
# and kinfo_getvmmap()
|
||||
if hasattr(cext, 'proc_num_fds'):
|
||||
@wrap_exceptions
|
||||
def num_fds(self):
|
||||
"""Return the number of file descriptors opened by this process."""
|
||||
ret = cext.proc_num_fds(self.pid)
|
||||
if NETBSD:
|
||||
# On NetBSD the underlying C function does not raise NSP
|
||||
# in case the process is gone.
|
||||
self.name() # raise NSP if the process disappeared on us
|
||||
return ret
|
||||
else:
|
||||
num_fds = _not_implemented
|
||||
|
||||
# --- FreeBSD only APIs
|
||||
|
||||
if FREEBSD:
|
||||
|
||||
@wrap_exceptions
|
||||
def cpu_affinity_get(self):
|
||||
return cext.proc_cpu_affinity_get(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def cpu_affinity_set(self, cpus):
|
||||
# Pre-emptively check if CPUs are valid because the C
|
||||
# function has a weird behavior in case of invalid CPUs,
|
||||
# see: https://github.com/giampaolo/psutil/issues/586
|
||||
allcpus = tuple(range(len(per_cpu_times())))
|
||||
for cpu in cpus:
|
||||
if cpu not in allcpus:
|
||||
raise ValueError("invalid CPU #%i (choose between %s)"
|
||||
% (cpu, allcpus))
|
||||
try:
|
||||
cext.proc_cpu_affinity_set(self.pid, cpus)
|
||||
except OSError as err:
|
||||
# 'man cpuset_setaffinity' about EDEADLK:
|
||||
# <<the call would leave a thread without a valid CPU to run
|
||||
# on because the set does not overlap with the thread's
|
||||
# anonymous mask>>
|
||||
if err.errno in (errno.EINVAL, errno.EDEADLK):
|
||||
for cpu in cpus:
|
||||
if cpu not in allcpus:
|
||||
raise ValueError(
|
||||
"invalid CPU #%i (choose between %s)" % (
|
||||
cpu, allcpus))
|
||||
raise
|
||||
|
||||
@wrap_exceptions
|
||||
def memory_maps(self):
|
||||
return cext.proc_memory_maps(self.pid)
|
||||
Vendored
-1872
File diff suppressed because it is too large
Load Diff
Vendored
-506
@@ -1,506 +0,0 @@
|
||||
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""OSX platform implementation."""
|
||||
|
||||
import errno
|
||||
import functools
|
||||
import os
|
||||
from collections import namedtuple
|
||||
|
||||
from . import _common
|
||||
from . import _psposix
|
||||
from . import _psutil_osx as cext
|
||||
from . import _psutil_posix as cext_posix
|
||||
from ._common import conn_tmap
|
||||
from ._common import isfile_strict
|
||||
from ._common import memoize_when_activated
|
||||
from ._common import parse_environ_block
|
||||
from ._common import sockfam_to_enum
|
||||
from ._common import socktype_to_enum
|
||||
from ._common import usage_percent
|
||||
|
||||
|
||||
__extra__all__ = []
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- globals
|
||||
# =====================================================================
|
||||
|
||||
|
||||
PAGESIZE = os.sysconf("SC_PAGE_SIZE")
|
||||
AF_LINK = cext_posix.AF_LINK
|
||||
|
||||
# http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h
|
||||
TCP_STATUSES = {
|
||||
cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
|
||||
cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
|
||||
cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV,
|
||||
cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
|
||||
cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
|
||||
cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
|
||||
cext.TCPS_CLOSED: _common.CONN_CLOSE,
|
||||
cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
|
||||
cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
|
||||
cext.TCPS_LISTEN: _common.CONN_LISTEN,
|
||||
cext.TCPS_CLOSING: _common.CONN_CLOSING,
|
||||
cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
|
||||
}
|
||||
|
||||
PROC_STATUSES = {
|
||||
cext.SIDL: _common.STATUS_IDLE,
|
||||
cext.SRUN: _common.STATUS_RUNNING,
|
||||
cext.SSLEEP: _common.STATUS_SLEEPING,
|
||||
cext.SSTOP: _common.STATUS_STOPPED,
|
||||
cext.SZOMB: _common.STATUS_ZOMBIE,
|
||||
}
|
||||
|
||||
kinfo_proc_map = dict(
|
||||
ppid=0,
|
||||
ruid=1,
|
||||
euid=2,
|
||||
suid=3,
|
||||
rgid=4,
|
||||
egid=5,
|
||||
sgid=6,
|
||||
ttynr=7,
|
||||
ctime=8,
|
||||
status=9,
|
||||
name=10,
|
||||
)
|
||||
|
||||
pidtaskinfo_map = dict(
|
||||
cpuutime=0,
|
||||
cpustime=1,
|
||||
rss=2,
|
||||
vms=3,
|
||||
pfaults=4,
|
||||
pageins=5,
|
||||
numthreads=6,
|
||||
volctxsw=7,
|
||||
)
|
||||
|
||||
# these get overwritten on "import psutil" from the __init__.py file
|
||||
NoSuchProcess = None
|
||||
ZombieProcess = None
|
||||
AccessDenied = None
|
||||
TimeoutExpired = None
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- named tuples
|
||||
# =====================================================================
|
||||
|
||||
|
||||
# psutil.cpu_times()
|
||||
scputimes = namedtuple('scputimes', ['user', 'nice', 'system', 'idle'])
|
||||
# psutil.virtual_memory()
|
||||
svmem = namedtuple(
|
||||
'svmem', ['total', 'available', 'percent', 'used', 'free',
|
||||
'active', 'inactive', 'wired'])
|
||||
# psutil.Process.memory_info()
|
||||
pmem = namedtuple('pmem', ['rss', 'vms', 'pfaults', 'pageins'])
|
||||
# psutil.Process.memory_full_info()
|
||||
pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', ))
|
||||
# psutil.Process.memory_maps(grouped=True)
|
||||
pmmap_grouped = namedtuple(
|
||||
'pmmap_grouped',
|
||||
'path rss private swapped dirtied ref_count shadow_depth')
|
||||
# psutil.Process.memory_maps(grouped=False)
|
||||
pmmap_ext = namedtuple(
|
||||
'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields))
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- memory
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def virtual_memory():
|
||||
"""System virtual memory as a namedtuple."""
|
||||
total, active, inactive, wired, free = cext.virtual_mem()
|
||||
avail = inactive + free
|
||||
used = active + inactive + wired
|
||||
percent = usage_percent((total - avail), total, _round=1)
|
||||
return svmem(total, avail, percent, used, free,
|
||||
active, inactive, wired)
|
||||
|
||||
|
||||
def swap_memory():
|
||||
"""Swap system memory as a (total, used, free, sin, sout) tuple."""
|
||||
total, used, free, sin, sout = cext.swap_mem()
|
||||
percent = usage_percent(used, total, _round=1)
|
||||
return _common.sswap(total, used, free, percent, sin, sout)
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- CPU
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def cpu_times():
|
||||
"""Return system CPU times as a namedtuple."""
|
||||
user, nice, system, idle = cext.cpu_times()
|
||||
return scputimes(user, nice, system, idle)
|
||||
|
||||
|
||||
def per_cpu_times():
|
||||
"""Return system CPU times as a named tuple"""
|
||||
ret = []
|
||||
for cpu_t in cext.per_cpu_times():
|
||||
user, nice, system, idle = cpu_t
|
||||
item = scputimes(user, nice, system, idle)
|
||||
ret.append(item)
|
||||
return ret
|
||||
|
||||
|
||||
def cpu_count_logical():
|
||||
"""Return the number of logical CPUs in the system."""
|
||||
return cext.cpu_count_logical()
|
||||
|
||||
|
||||
def cpu_count_physical():
|
||||
"""Return the number of physical CPUs in the system."""
|
||||
return cext.cpu_count_phys()
|
||||
|
||||
|
||||
def cpu_stats():
|
||||
ctx_switches, interrupts, soft_interrupts, syscalls, traps = \
|
||||
cext.cpu_stats()
|
||||
return _common.scpustats(
|
||||
ctx_switches, interrupts, soft_interrupts, syscalls)
|
||||
|
||||
|
||||
def cpu_freq():
|
||||
"""Return CPU frequency.
|
||||
On OSX per-cpu frequency is not supported.
|
||||
Also, the returned frequency never changes, see:
|
||||
https://arstechnica.com/civis/viewtopic.php?f=19&t=465002
|
||||
"""
|
||||
curr, min_, max_ = cext.cpu_freq()
|
||||
return [_common.scpufreq(curr, min_, max_)]
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- disks
|
||||
# =====================================================================
|
||||
|
||||
|
||||
disk_usage = _psposix.disk_usage
|
||||
disk_io_counters = cext.disk_io_counters
|
||||
|
||||
|
||||
def disk_partitions(all=False):
|
||||
"""Return mounted disk partitions as a list of namedtuples."""
|
||||
retlist = []
|
||||
partitions = cext.disk_partitions()
|
||||
for partition in partitions:
|
||||
device, mountpoint, fstype, opts = partition
|
||||
if device == 'none':
|
||||
device = ''
|
||||
if not all:
|
||||
if not os.path.isabs(device) or not os.path.exists(device):
|
||||
continue
|
||||
ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
|
||||
retlist.append(ntuple)
|
||||
return retlist
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- network
|
||||
# =====================================================================
|
||||
|
||||
|
||||
net_io_counters = cext.net_io_counters
|
||||
net_if_addrs = cext_posix.net_if_addrs
|
||||
|
||||
|
||||
def net_connections(kind='inet'):
|
||||
"""System-wide network connections."""
|
||||
# Note: on OSX this will fail with AccessDenied unless
|
||||
# the process is owned by root.
|
||||
ret = []
|
||||
for pid in pids():
|
||||
try:
|
||||
cons = Process(pid).connections(kind)
|
||||
except NoSuchProcess:
|
||||
continue
|
||||
else:
|
||||
if cons:
|
||||
for c in cons:
|
||||
c = list(c) + [pid]
|
||||
ret.append(_common.sconn(*c))
|
||||
return ret
|
||||
|
||||
|
||||
def net_if_stats():
|
||||
"""Get NIC stats (isup, duplex, speed, mtu)."""
|
||||
names = net_io_counters().keys()
|
||||
ret = {}
|
||||
for name in names:
|
||||
mtu = cext_posix.net_if_mtu(name)
|
||||
isup = cext_posix.net_if_flags(name)
|
||||
duplex, speed = cext_posix.net_if_duplex_speed(name)
|
||||
if hasattr(_common, 'NicDuplex'):
|
||||
duplex = _common.NicDuplex(duplex)
|
||||
ret[name] = _common.snicstats(isup, duplex, speed, mtu)
|
||||
return ret
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- other system functions
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def boot_time():
|
||||
"""The system boot time expressed in seconds since the epoch."""
|
||||
return cext.boot_time()
|
||||
|
||||
|
||||
def users():
|
||||
"""Return currently connected users as a list of namedtuples."""
|
||||
retlist = []
|
||||
rawlist = cext.users()
|
||||
for item in rawlist:
|
||||
user, tty, hostname, tstamp = item
|
||||
if tty == '~':
|
||||
continue # reboot or shutdown
|
||||
if not tstamp:
|
||||
continue
|
||||
nt = _common.suser(user, tty or None, hostname or None, tstamp)
|
||||
retlist.append(nt)
|
||||
return retlist
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- processes
|
||||
# =====================================================================
|
||||
|
||||
|
||||
pids = cext.pids
|
||||
pid_exists = _psposix.pid_exists
|
||||
|
||||
|
||||
def wrap_exceptions(fun):
|
||||
"""Decorator which translates bare OSError exceptions into
|
||||
NoSuchProcess and AccessDenied.
|
||||
"""
|
||||
@functools.wraps(fun)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
try:
|
||||
return fun(self, *args, **kwargs)
|
||||
except OSError as err:
|
||||
if self.pid == 0:
|
||||
if 0 in pids():
|
||||
raise AccessDenied(self.pid, self._name)
|
||||
else:
|
||||
raise
|
||||
if err.errno == errno.ESRCH:
|
||||
if not pid_exists(self.pid):
|
||||
raise NoSuchProcess(self.pid, self._name)
|
||||
else:
|
||||
raise ZombieProcess(self.pid, self._name, self._ppid)
|
||||
if err.errno in (errno.EPERM, errno.EACCES):
|
||||
raise AccessDenied(self.pid, self._name)
|
||||
raise
|
||||
return wrapper
|
||||
|
||||
|
||||
class Process(object):
|
||||
"""Wrapper class around underlying C implementation."""
|
||||
|
||||
__slots__ = ["pid", "_name", "_ppid"]
|
||||
|
||||
def __init__(self, pid):
|
||||
self.pid = pid
|
||||
self._name = None
|
||||
self._ppid = None
|
||||
|
||||
@memoize_when_activated
|
||||
def _get_kinfo_proc(self):
|
||||
# Note: should work with all PIDs without permission issues.
|
||||
ret = cext.proc_kinfo_oneshot(self.pid)
|
||||
assert len(ret) == len(kinfo_proc_map)
|
||||
return ret
|
||||
|
||||
@memoize_when_activated
|
||||
def _get_pidtaskinfo(self):
|
||||
# Note: should work for PIDs owned by user only.
|
||||
ret = cext.proc_pidtaskinfo_oneshot(self.pid)
|
||||
assert len(ret) == len(pidtaskinfo_map)
|
||||
return ret
|
||||
|
||||
def oneshot_enter(self):
|
||||
self._get_kinfo_proc.cache_activate()
|
||||
self._get_pidtaskinfo.cache_activate()
|
||||
|
||||
def oneshot_exit(self):
|
||||
self._get_kinfo_proc.cache_deactivate()
|
||||
self._get_pidtaskinfo.cache_deactivate()
|
||||
|
||||
@wrap_exceptions
|
||||
def name(self):
|
||||
name = self._get_kinfo_proc()[kinfo_proc_map['name']]
|
||||
return name if name is not None else cext.proc_name(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def exe(self):
|
||||
return cext.proc_exe(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def cmdline(self):
|
||||
if not pid_exists(self.pid):
|
||||
raise NoSuchProcess(self.pid, self._name)
|
||||
return cext.proc_cmdline(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def environ(self):
|
||||
if not pid_exists(self.pid):
|
||||
raise NoSuchProcess(self.pid, self._name)
|
||||
return parse_environ_block(cext.proc_environ(self.pid))
|
||||
|
||||
@wrap_exceptions
|
||||
def ppid(self):
|
||||
self._ppid = self._get_kinfo_proc()[kinfo_proc_map['ppid']]
|
||||
return self._ppid
|
||||
|
||||
@wrap_exceptions
|
||||
def cwd(self):
|
||||
return cext.proc_cwd(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def uids(self):
|
||||
rawtuple = self._get_kinfo_proc()
|
||||
return _common.puids(
|
||||
rawtuple[kinfo_proc_map['ruid']],
|
||||
rawtuple[kinfo_proc_map['euid']],
|
||||
rawtuple[kinfo_proc_map['suid']])
|
||||
|
||||
@wrap_exceptions
|
||||
def gids(self):
|
||||
rawtuple = self._get_kinfo_proc()
|
||||
return _common.puids(
|
||||
rawtuple[kinfo_proc_map['rgid']],
|
||||
rawtuple[kinfo_proc_map['egid']],
|
||||
rawtuple[kinfo_proc_map['sgid']])
|
||||
|
||||
@wrap_exceptions
|
||||
def terminal(self):
|
||||
tty_nr = self._get_kinfo_proc()[kinfo_proc_map['ttynr']]
|
||||
tmap = _psposix.get_terminal_map()
|
||||
try:
|
||||
return tmap[tty_nr]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
@wrap_exceptions
|
||||
def memory_info(self):
|
||||
rawtuple = self._get_pidtaskinfo()
|
||||
return pmem(
|
||||
rawtuple[pidtaskinfo_map['rss']],
|
||||
rawtuple[pidtaskinfo_map['vms']],
|
||||
rawtuple[pidtaskinfo_map['pfaults']],
|
||||
rawtuple[pidtaskinfo_map['pageins']],
|
||||
)
|
||||
|
||||
@wrap_exceptions
|
||||
def memory_full_info(self):
|
||||
basic_mem = self.memory_info()
|
||||
uss = cext.proc_memory_uss(self.pid)
|
||||
return pfullmem(*basic_mem + (uss, ))
|
||||
|
||||
@wrap_exceptions
|
||||
def cpu_times(self):
|
||||
rawtuple = self._get_pidtaskinfo()
|
||||
return _common.pcputimes(
|
||||
rawtuple[pidtaskinfo_map['cpuutime']],
|
||||
rawtuple[pidtaskinfo_map['cpustime']],
|
||||
# children user / system times are not retrievable (set to 0)
|
||||
0, 0)
|
||||
|
||||
@wrap_exceptions
|
||||
def create_time(self):
|
||||
return self._get_kinfo_proc()[kinfo_proc_map['ctime']]
|
||||
|
||||
@wrap_exceptions
|
||||
def num_ctx_switches(self):
|
||||
# Unvoluntary value seems not to be available;
|
||||
# getrusage() numbers seems to confirm this theory.
|
||||
# We set it to 0.
|
||||
vol = self._get_pidtaskinfo()[pidtaskinfo_map['volctxsw']]
|
||||
return _common.pctxsw(vol, 0)
|
||||
|
||||
@wrap_exceptions
|
||||
def num_threads(self):
|
||||
return self._get_pidtaskinfo()[pidtaskinfo_map['numthreads']]
|
||||
|
||||
@wrap_exceptions
|
||||
def open_files(self):
|
||||
if self.pid == 0:
|
||||
return []
|
||||
files = []
|
||||
rawlist = cext.proc_open_files(self.pid)
|
||||
for path, fd in rawlist:
|
||||
if isfile_strict(path):
|
||||
ntuple = _common.popenfile(path, fd)
|
||||
files.append(ntuple)
|
||||
return files
|
||||
|
||||
@wrap_exceptions
|
||||
def connections(self, kind='inet'):
|
||||
if kind not in conn_tmap:
|
||||
raise ValueError("invalid %r kind argument; choose between %s"
|
||||
% (kind, ', '.join([repr(x) for x in conn_tmap])))
|
||||
families, types = conn_tmap[kind]
|
||||
rawlist = cext.proc_connections(self.pid, families, types)
|
||||
ret = []
|
||||
for item in rawlist:
|
||||
fd, fam, type, laddr, raddr, status = item
|
||||
status = TCP_STATUSES[status]
|
||||
fam = sockfam_to_enum(fam)
|
||||
type = socktype_to_enum(type)
|
||||
nt = _common.pconn(fd, fam, type, laddr, raddr, status)
|
||||
ret.append(nt)
|
||||
return ret
|
||||
|
||||
@wrap_exceptions
|
||||
def num_fds(self):
|
||||
if self.pid == 0:
|
||||
return 0
|
||||
return cext.proc_num_fds(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def wait(self, timeout=None):
|
||||
try:
|
||||
return _psposix.wait_pid(self.pid, timeout)
|
||||
except _psposix.TimeoutExpired:
|
||||
raise TimeoutExpired(timeout, self.pid, self._name)
|
||||
|
||||
@wrap_exceptions
|
||||
def nice_get(self):
|
||||
return cext_posix.getpriority(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def nice_set(self, value):
|
||||
return cext_posix.setpriority(self.pid, value)
|
||||
|
||||
@wrap_exceptions
|
||||
def status(self):
|
||||
code = self._get_kinfo_proc()[kinfo_proc_map['status']]
|
||||
# XXX is '?' legit? (we're not supposed to return it anyway)
|
||||
return PROC_STATUSES.get(code, '?')
|
||||
|
||||
@wrap_exceptions
|
||||
def threads(self):
|
||||
rawlist = cext.proc_threads(self.pid)
|
||||
retlist = []
|
||||
for thread_id, utime, stime in rawlist:
|
||||
ntuple = _common.pthread(thread_id, utime, stime)
|
||||
retlist.append(ntuple)
|
||||
return retlist
|
||||
|
||||
@wrap_exceptions
|
||||
def memory_maps(self):
|
||||
return cext.proc_memory_maps(self.pid)
|
||||
Vendored
-184
@@ -1,184 +0,0 @@
|
||||
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Routines common to all posix systems."""
|
||||
|
||||
import errno
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from ._common import memoize
|
||||
from ._common import sdiskusage
|
||||
from ._common import usage_percent
|
||||
from ._compat import PY3
|
||||
from ._compat import unicode
|
||||
|
||||
|
||||
__all__ = ['TimeoutExpired', 'pid_exists', 'wait_pid', 'disk_usage',
|
||||
'get_terminal_map']
|
||||
|
||||
|
||||
class TimeoutExpired(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def pid_exists(pid):
|
||||
"""Check whether pid exists in the current process table."""
|
||||
if pid == 0:
|
||||
# According to "man 2 kill" PID 0 has a special meaning:
|
||||
# it refers to <<every process in the process group of the
|
||||
# calling process>> so we don't want to go any further.
|
||||
# If we get here it means this UNIX platform *does* have
|
||||
# a process with id 0.
|
||||
return True
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
except OSError as err:
|
||||
if err.errno == errno.ESRCH:
|
||||
# ESRCH == No such process
|
||||
return False
|
||||
elif err.errno == errno.EPERM:
|
||||
# EPERM clearly means there's a process to deny access to
|
||||
return True
|
||||
else:
|
||||
# According to "man 2 kill" possible error values are
|
||||
# (EINVAL, EPERM, ESRCH) therefore we should never get
|
||||
# here. If we do let's be explicit in considering this
|
||||
# an error.
|
||||
raise err
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def wait_pid(pid, timeout=None):
|
||||
"""Wait for process with pid 'pid' to terminate and return its
|
||||
exit status code as an integer.
|
||||
|
||||
If pid is not a children of os.getpid() (current process) just
|
||||
waits until the process disappears and return None.
|
||||
|
||||
If pid does not exist at all return None immediately.
|
||||
|
||||
Raise TimeoutExpired on timeout expired.
|
||||
"""
|
||||
def check_timeout(delay):
|
||||
if timeout is not None:
|
||||
if timer() >= stop_at:
|
||||
raise TimeoutExpired()
|
||||
time.sleep(delay)
|
||||
return min(delay * 2, 0.04)
|
||||
|
||||
timer = getattr(time, 'monotonic', time.time)
|
||||
if timeout is not None:
|
||||
def waitcall():
|
||||
return os.waitpid(pid, os.WNOHANG)
|
||||
stop_at = timer() + timeout
|
||||
else:
|
||||
def waitcall():
|
||||
return os.waitpid(pid, 0)
|
||||
|
||||
delay = 0.0001
|
||||
while True:
|
||||
try:
|
||||
retpid, status = waitcall()
|
||||
except OSError as err:
|
||||
if err.errno == errno.EINTR:
|
||||
delay = check_timeout(delay)
|
||||
continue
|
||||
elif err.errno == errno.ECHILD:
|
||||
# This has two meanings:
|
||||
# - pid is not a child of os.getpid() in which case
|
||||
# we keep polling until it's gone
|
||||
# - pid never existed in the first place
|
||||
# In both cases we'll eventually return None as we
|
||||
# can't determine its exit status code.
|
||||
while True:
|
||||
if pid_exists(pid):
|
||||
delay = check_timeout(delay)
|
||||
else:
|
||||
return
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
if retpid == 0:
|
||||
# WNOHANG was used, pid is still running
|
||||
delay = check_timeout(delay)
|
||||
continue
|
||||
# process exited due to a signal; return the integer of
|
||||
# that signal
|
||||
if os.WIFSIGNALED(status):
|
||||
return -os.WTERMSIG(status)
|
||||
# process exited using exit(2) system call; return the
|
||||
# integer exit(2) system call has been called with
|
||||
elif os.WIFEXITED(status):
|
||||
return os.WEXITSTATUS(status)
|
||||
else:
|
||||
# should never happen
|
||||
raise ValueError("unknown process exit status %r" % status)
|
||||
|
||||
|
||||
def disk_usage(path):
|
||||
"""Return disk usage associated with path.
|
||||
Note: UNIX usually reserves 5% disk space which is not accessible
|
||||
by user. In this function "total" and "used" values reflect the
|
||||
total and used disk space whereas "free" and "percent" represent
|
||||
the "free" and "used percent" user disk space.
|
||||
"""
|
||||
try:
|
||||
st = os.statvfs(path)
|
||||
except UnicodeEncodeError:
|
||||
if not PY3 and isinstance(path, unicode):
|
||||
# this is a bug with os.statvfs() and unicode on
|
||||
# Python 2, see:
|
||||
# - https://github.com/giampaolo/psutil/issues/416
|
||||
# - http://bugs.python.org/issue18695
|
||||
try:
|
||||
path = path.encode(sys.getfilesystemencoding())
|
||||
except UnicodeEncodeError:
|
||||
pass
|
||||
st = os.statvfs(path)
|
||||
else:
|
||||
raise
|
||||
|
||||
# Total space which is only available to root (unless changed
|
||||
# at system level).
|
||||
total = (st.f_blocks * st.f_frsize)
|
||||
# Remaining free space usable by root.
|
||||
avail_to_root = (st.f_bfree * st.f_frsize)
|
||||
# Remaining free space usable by user.
|
||||
avail_to_user = (st.f_bavail * st.f_frsize)
|
||||
# Total space being used in general.
|
||||
used = (total - avail_to_root)
|
||||
# Total space which is available to user (same as 'total' but
|
||||
# for the user).
|
||||
total_user = used + avail_to_user
|
||||
# User usage percent compared to the total amount of space
|
||||
# the user can use. This number would be higher if compared
|
||||
# to root's because the user has less space (usually -5%).
|
||||
usage_percent_user = usage_percent(used, total_user, _round=1)
|
||||
|
||||
# NB: the percentage is -5% than what shown by df due to
|
||||
# reserved blocks that we are currently not considering:
|
||||
# https://github.com/giampaolo/psutil/issues/829#issuecomment-223750462
|
||||
return sdiskusage(
|
||||
total=total, used=used, free=avail_to_user, percent=usage_percent_user)
|
||||
|
||||
|
||||
@memoize
|
||||
def get_terminal_map():
|
||||
"""Get a map of device-id -> path as a dict.
|
||||
Used by Process.terminal()
|
||||
"""
|
||||
ret = {}
|
||||
ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*')
|
||||
for name in ls:
|
||||
assert name not in ret, name
|
||||
try:
|
||||
ret[os.stat(name).st_rdev] = name
|
||||
except OSError as err:
|
||||
if err.errno != errno.ENOENT:
|
||||
raise
|
||||
return ret
|
||||
Vendored
-702
@@ -1,702 +0,0 @@
|
||||
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Sun OS Solaris platform implementation."""
|
||||
|
||||
import errno
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
from . import _common
|
||||
from . import _psposix
|
||||
from . import _psutil_posix as cext_posix
|
||||
from . import _psutil_sunos as cext
|
||||
from ._common import isfile_strict
|
||||
from ._common import memoize_when_activated
|
||||
from ._common import sockfam_to_enum
|
||||
from ._common import socktype_to_enum
|
||||
from ._common import usage_percent
|
||||
from ._compat import b
|
||||
from ._compat import PY3
|
||||
|
||||
|
||||
__extra__all__ = ["CONN_IDLE", "CONN_BOUND", "PROCFS_PATH"]
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- globals
|
||||
# =====================================================================
|
||||
|
||||
|
||||
PAGE_SIZE = os.sysconf('SC_PAGE_SIZE')
|
||||
AF_LINK = cext_posix.AF_LINK
|
||||
IS_64_BIT = sys.maxsize > 2**32
|
||||
|
||||
CONN_IDLE = "IDLE"
|
||||
CONN_BOUND = "BOUND"
|
||||
|
||||
PROC_STATUSES = {
|
||||
cext.SSLEEP: _common.STATUS_SLEEPING,
|
||||
cext.SRUN: _common.STATUS_RUNNING,
|
||||
cext.SZOMB: _common.STATUS_ZOMBIE,
|
||||
cext.SSTOP: _common.STATUS_STOPPED,
|
||||
cext.SIDL: _common.STATUS_IDLE,
|
||||
cext.SONPROC: _common.STATUS_RUNNING, # same as run
|
||||
cext.SWAIT: _common.STATUS_WAITING,
|
||||
}
|
||||
|
||||
TCP_STATUSES = {
|
||||
cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
|
||||
cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
|
||||
cext.TCPS_SYN_RCVD: _common.CONN_SYN_RECV,
|
||||
cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
|
||||
cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
|
||||
cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
|
||||
cext.TCPS_CLOSED: _common.CONN_CLOSE,
|
||||
cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
|
||||
cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
|
||||
cext.TCPS_LISTEN: _common.CONN_LISTEN,
|
||||
cext.TCPS_CLOSING: _common.CONN_CLOSING,
|
||||
cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
|
||||
cext.TCPS_IDLE: CONN_IDLE, # sunos specific
|
||||
cext.TCPS_BOUND: CONN_BOUND, # sunos specific
|
||||
}
|
||||
|
||||
# these get overwritten on "import psutil" from the __init__.py file
|
||||
NoSuchProcess = None
|
||||
ZombieProcess = None
|
||||
AccessDenied = None
|
||||
TimeoutExpired = None
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- named tuples
|
||||
# =====================================================================
|
||||
|
||||
|
||||
# psutil.cpu_times()
|
||||
scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait'])
|
||||
# psutil.cpu_times(percpu=True)
|
||||
pcputimes = namedtuple('pcputimes',
|
||||
['user', 'system', 'children_user', 'children_system'])
|
||||
# psutil.virtual_memory()
|
||||
svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free'])
|
||||
# psutil.Process.memory_info()
|
||||
pmem = namedtuple('pmem', ['rss', 'vms'])
|
||||
pfullmem = pmem
|
||||
# psutil.Process.memory_maps(grouped=True)
|
||||
pmmap_grouped = namedtuple('pmmap_grouped',
|
||||
['path', 'rss', 'anonymous', 'locked'])
|
||||
# psutil.Process.memory_maps(grouped=False)
|
||||
pmmap_ext = namedtuple(
|
||||
'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields))
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- utils
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def get_procfs_path():
|
||||
"""Return updated psutil.PROCFS_PATH constant."""
|
||||
return sys.modules['psutil'].PROCFS_PATH
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- memory
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def virtual_memory():
|
||||
"""Report virtual memory metrics."""
|
||||
# we could have done this with kstat, but IMHO this is good enough
|
||||
total = os.sysconf('SC_PHYS_PAGES') * PAGE_SIZE
|
||||
# note: there's no difference on Solaris
|
||||
free = avail = os.sysconf('SC_AVPHYS_PAGES') * PAGE_SIZE
|
||||
used = total - free
|
||||
percent = usage_percent(used, total, _round=1)
|
||||
return svmem(total, avail, percent, used, free)
|
||||
|
||||
|
||||
def swap_memory():
|
||||
"""Report swap memory metrics."""
|
||||
sin, sout = cext.swap_mem()
|
||||
# XXX
|
||||
# we are supposed to get total/free by doing so:
|
||||
# http://cvs.opensolaris.org/source/xref/onnv/onnv-gate/
|
||||
# usr/src/cmd/swap/swap.c
|
||||
# ...nevertheless I can't manage to obtain the same numbers as 'swap'
|
||||
# cmdline utility, so let's parse its output (sigh!)
|
||||
p = subprocess.Popen(['/usr/bin/env', 'PATH=/usr/sbin:/sbin:%s' %
|
||||
os.environ['PATH'], 'swap', '-l'],
|
||||
stdout=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
if PY3:
|
||||
stdout = stdout.decode(sys.stdout.encoding)
|
||||
if p.returncode != 0:
|
||||
raise RuntimeError("'swap -l' failed (retcode=%s)" % p.returncode)
|
||||
|
||||
lines = stdout.strip().split('\n')[1:]
|
||||
if not lines:
|
||||
raise RuntimeError('no swap device(s) configured')
|
||||
total = free = 0
|
||||
for line in lines:
|
||||
line = line.split()
|
||||
t, f = line[-2:]
|
||||
total += int(int(t) * 512)
|
||||
free += int(int(f) * 512)
|
||||
used = total - free
|
||||
percent = usage_percent(used, total, _round=1)
|
||||
return _common.sswap(total, used, free, percent,
|
||||
sin * PAGE_SIZE, sout * PAGE_SIZE)
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- CPU
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def cpu_times():
|
||||
"""Return system-wide CPU times as a named tuple"""
|
||||
ret = cext.per_cpu_times()
|
||||
return scputimes(*[sum(x) for x in zip(*ret)])
|
||||
|
||||
|
||||
def per_cpu_times():
|
||||
"""Return system per-CPU times as a list of named tuples"""
|
||||
ret = cext.per_cpu_times()
|
||||
return [scputimes(*x) for x in ret]
|
||||
|
||||
|
||||
def cpu_count_logical():
|
||||
"""Return the number of logical CPUs in the system."""
|
||||
try:
|
||||
return os.sysconf("SC_NPROCESSORS_ONLN")
|
||||
except ValueError:
|
||||
# mimic os.cpu_count() behavior
|
||||
return None
|
||||
|
||||
|
||||
def cpu_count_physical():
|
||||
"""Return the number of physical CPUs in the system."""
|
||||
return cext.cpu_count_phys()
|
||||
|
||||
|
||||
def cpu_stats():
|
||||
"""Return various CPU stats as a named tuple."""
|
||||
ctx_switches, interrupts, syscalls, traps = cext.cpu_stats()
|
||||
soft_interrupts = 0
|
||||
return _common.scpustats(ctx_switches, interrupts, soft_interrupts,
|
||||
syscalls)
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- disks
|
||||
# =====================================================================
|
||||
|
||||
|
||||
disk_io_counters = cext.disk_io_counters
|
||||
disk_usage = _psposix.disk_usage
|
||||
|
||||
|
||||
def disk_partitions(all=False):
|
||||
"""Return system disk partitions."""
|
||||
# TODO - the filtering logic should be better checked so that
|
||||
# it tries to reflect 'df' as much as possible
|
||||
retlist = []
|
||||
partitions = cext.disk_partitions()
|
||||
for partition in partitions:
|
||||
device, mountpoint, fstype, opts = partition
|
||||
if device == 'none':
|
||||
device = ''
|
||||
if not all:
|
||||
# Differently from, say, Linux, we don't have a list of
|
||||
# common fs types so the best we can do, AFAIK, is to
|
||||
# filter by filesystem having a total size > 0.
|
||||
if not disk_usage(mountpoint).total:
|
||||
continue
|
||||
ntuple = _common.sdiskpart(device, mountpoint, fstype, opts)
|
||||
retlist.append(ntuple)
|
||||
return retlist
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- network
|
||||
# =====================================================================
|
||||
|
||||
|
||||
net_io_counters = cext.net_io_counters
|
||||
net_if_addrs = cext_posix.net_if_addrs
|
||||
|
||||
|
||||
def net_connections(kind, _pid=-1):
|
||||
"""Return socket connections. If pid == -1 return system-wide
|
||||
connections (as opposed to connections opened by one process only).
|
||||
Only INET sockets are returned (UNIX are not).
|
||||
"""
|
||||
cmap = _common.conn_tmap.copy()
|
||||
if _pid == -1:
|
||||
cmap.pop('unix', 0)
|
||||
if kind not in cmap:
|
||||
raise ValueError("invalid %r kind argument; choose between %s"
|
||||
% (kind, ', '.join([repr(x) for x in cmap])))
|
||||
families, types = _common.conn_tmap[kind]
|
||||
rawlist = cext.net_connections(_pid)
|
||||
ret = set()
|
||||
for item in rawlist:
|
||||
fd, fam, type_, laddr, raddr, status, pid = item
|
||||
if fam not in families:
|
||||
continue
|
||||
if type_ not in types:
|
||||
continue
|
||||
status = TCP_STATUSES[status]
|
||||
fam = sockfam_to_enum(fam)
|
||||
type_ = socktype_to_enum(type_)
|
||||
if _pid == -1:
|
||||
nt = _common.sconn(fd, fam, type_, laddr, raddr, status, pid)
|
||||
else:
|
||||
nt = _common.pconn(fd, fam, type_, laddr, raddr, status)
|
||||
ret.add(nt)
|
||||
return list(ret)
|
||||
|
||||
|
||||
def net_if_stats():
|
||||
"""Get NIC stats (isup, duplex, speed, mtu)."""
|
||||
ret = cext.net_if_stats()
|
||||
for name, items in ret.items():
|
||||
isup, duplex, speed, mtu = items
|
||||
if hasattr(_common, 'NicDuplex'):
|
||||
duplex = _common.NicDuplex(duplex)
|
||||
ret[name] = _common.snicstats(isup, duplex, speed, mtu)
|
||||
return ret
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- other system functions
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def boot_time():
|
||||
"""The system boot time expressed in seconds since the epoch."""
|
||||
return cext.boot_time()
|
||||
|
||||
|
||||
def users():
|
||||
"""Return currently connected users as a list of namedtuples."""
|
||||
retlist = []
|
||||
rawlist = cext.users()
|
||||
localhost = (':0.0', ':0')
|
||||
for item in rawlist:
|
||||
user, tty, hostname, tstamp, user_process = item
|
||||
# note: the underlying C function includes entries about
|
||||
# system boot, run level and others. We might want
|
||||
# to use them in the future.
|
||||
if not user_process:
|
||||
continue
|
||||
if hostname in localhost:
|
||||
hostname = 'localhost'
|
||||
nt = _common.suser(user, tty, hostname, tstamp)
|
||||
retlist.append(nt)
|
||||
return retlist
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- processes
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def pids():
|
||||
"""Returns a list of PIDs currently running on the system."""
|
||||
return [int(x) for x in os.listdir(b(get_procfs_path())) if x.isdigit()]
|
||||
|
||||
|
||||
def pid_exists(pid):
|
||||
"""Check for the existence of a unix pid."""
|
||||
return _psposix.pid_exists(pid)
|
||||
|
||||
|
||||
def wrap_exceptions(fun):
|
||||
"""Call callable into a try/except clause and translate ENOENT,
|
||||
EACCES and EPERM in NoSuchProcess or AccessDenied exceptions.
|
||||
"""
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
try:
|
||||
return fun(self, *args, **kwargs)
|
||||
except EnvironmentError as err:
|
||||
if self.pid == 0:
|
||||
if 0 in pids():
|
||||
raise AccessDenied(self.pid, self._name)
|
||||
else:
|
||||
raise
|
||||
# ENOENT (no such file or directory) gets raised on open().
|
||||
# ESRCH (no such process) can get raised on read() if
|
||||
# process is gone in meantime.
|
||||
if err.errno in (errno.ENOENT, errno.ESRCH):
|
||||
if not pid_exists(self.pid):
|
||||
raise NoSuchProcess(self.pid, self._name)
|
||||
else:
|
||||
raise ZombieProcess(self.pid, self._name, self._ppid)
|
||||
if err.errno in (errno.EPERM, errno.EACCES):
|
||||
raise AccessDenied(self.pid, self._name)
|
||||
raise
|
||||
return wrapper
|
||||
|
||||
|
||||
class Process(object):
|
||||
"""Wrapper class around underlying C implementation."""
|
||||
|
||||
__slots__ = ["pid", "_name", "_ppid", "_procfs_path"]
|
||||
|
||||
def __init__(self, pid):
|
||||
self.pid = pid
|
||||
self._name = None
|
||||
self._ppid = None
|
||||
self._procfs_path = get_procfs_path()
|
||||
|
||||
def oneshot_enter(self):
|
||||
self._proc_name_and_args.cache_activate()
|
||||
self._proc_basic_info.cache_activate()
|
||||
self._proc_cred.cache_activate()
|
||||
|
||||
def oneshot_exit(self):
|
||||
self._proc_name_and_args.cache_deactivate()
|
||||
self._proc_basic_info.cache_deactivate()
|
||||
self._proc_cred.cache_deactivate()
|
||||
|
||||
@memoize_when_activated
|
||||
def _proc_name_and_args(self):
|
||||
return cext.proc_name_and_args(self.pid, self._procfs_path)
|
||||
|
||||
@memoize_when_activated
|
||||
def _proc_basic_info(self):
|
||||
return cext.proc_basic_info(self.pid, self._procfs_path)
|
||||
|
||||
@memoize_when_activated
|
||||
def _proc_cred(self):
|
||||
return cext.proc_cred(self.pid, self._procfs_path)
|
||||
|
||||
@wrap_exceptions
|
||||
def name(self):
|
||||
# note: max len == 15
|
||||
return self._proc_name_and_args()[0]
|
||||
|
||||
@wrap_exceptions
|
||||
def exe(self):
|
||||
try:
|
||||
return os.readlink(
|
||||
"%s/%s/path/a.out" % (self._procfs_path, self.pid))
|
||||
except OSError:
|
||||
pass # continue and guess the exe name from the cmdline
|
||||
# Will be guessed later from cmdline but we want to explicitly
|
||||
# invoke cmdline here in order to get an AccessDenied
|
||||
# exception if the user has not enough privileges.
|
||||
self.cmdline()
|
||||
return ""
|
||||
|
||||
@wrap_exceptions
|
||||
def cmdline(self):
|
||||
return self._proc_name_and_args()[1].split(' ')
|
||||
|
||||
@wrap_exceptions
|
||||
def create_time(self):
|
||||
return self._proc_basic_info()[3]
|
||||
|
||||
@wrap_exceptions
|
||||
def num_threads(self):
|
||||
return self._proc_basic_info()[5]
|
||||
|
||||
@wrap_exceptions
|
||||
def nice_get(self):
|
||||
# For some reason getpriority(3) return ESRCH (no such process)
|
||||
# for certain low-pid processes, no matter what (even as root).
|
||||
# The process actually exists though, as it has a name,
|
||||
# creation time, etc.
|
||||
# The best thing we can do here appears to be raising AD.
|
||||
# Note: tested on Solaris 11; on Open Solaris 5 everything is
|
||||
# fine.
|
||||
try:
|
||||
return cext_posix.getpriority(self.pid)
|
||||
except EnvironmentError as err:
|
||||
# 48 is 'operation not supported' but errno does not expose
|
||||
# it. It occurs for low system pids.
|
||||
if err.errno in (errno.ENOENT, errno.ESRCH, 48):
|
||||
if pid_exists(self.pid):
|
||||
raise AccessDenied(self.pid, self._name)
|
||||
raise
|
||||
|
||||
@wrap_exceptions
|
||||
def nice_set(self, value):
|
||||
if self.pid in (2, 3):
|
||||
# Special case PIDs: internally setpriority(3) return ESRCH
|
||||
# (no such process), no matter what.
|
||||
# The process actually exists though, as it has a name,
|
||||
# creation time, etc.
|
||||
raise AccessDenied(self.pid, self._name)
|
||||
return cext_posix.setpriority(self.pid, value)
|
||||
|
||||
@wrap_exceptions
|
||||
def ppid(self):
|
||||
self._ppid = self._proc_basic_info()[0]
|
||||
return self._ppid
|
||||
|
||||
@wrap_exceptions
|
||||
def uids(self):
|
||||
real, effective, saved, _, _, _ = self._proc_cred()
|
||||
return _common.puids(real, effective, saved)
|
||||
|
||||
@wrap_exceptions
|
||||
def gids(self):
|
||||
_, _, _, real, effective, saved = self._proc_cred()
|
||||
return _common.puids(real, effective, saved)
|
||||
|
||||
@wrap_exceptions
|
||||
def cpu_times(self):
|
||||
try:
|
||||
times = cext.proc_cpu_times(self.pid, self._procfs_path)
|
||||
except OSError as err:
|
||||
if err.errno == errno.EOVERFLOW and not IS_64_BIT:
|
||||
# We may get here if we attempt to query a 64bit process
|
||||
# with a 32bit python.
|
||||
# Error originates from read() and also tools like "cat"
|
||||
# fail in the same way (!).
|
||||
# Since there simply is no way to determine CPU times we
|
||||
# return 0.0 as a fallback. See:
|
||||
# https://github.com/giampaolo/psutil/issues/857
|
||||
times = (0.0, 0.0, 0.0, 0.0)
|
||||
else:
|
||||
raise
|
||||
return _common.pcputimes(*times)
|
||||
|
||||
@wrap_exceptions
|
||||
def cpu_num(self):
|
||||
return cext.proc_cpu_num(self.pid, self._procfs_path)
|
||||
|
||||
@wrap_exceptions
|
||||
def terminal(self):
|
||||
procfs_path = self._procfs_path
|
||||
hit_enoent = False
|
||||
tty = wrap_exceptions(
|
||||
self._proc_basic_info()[0])
|
||||
if tty != cext.PRNODEV:
|
||||
for x in (0, 1, 2, 255):
|
||||
try:
|
||||
return os.readlink(
|
||||
'%s/%d/path/%d' % (procfs_path, self.pid, x))
|
||||
except OSError as err:
|
||||
if err.errno == errno.ENOENT:
|
||||
hit_enoent = True
|
||||
continue
|
||||
raise
|
||||
if hit_enoent:
|
||||
# raise NSP if the process disappeared on us
|
||||
os.stat('%s/%s' % (procfs_path, self.pid))
|
||||
|
||||
@wrap_exceptions
|
||||
def cwd(self):
|
||||
# /proc/PID/path/cwd may not be resolved by readlink() even if
|
||||
# it exists (ls shows it). If that's the case and the process
|
||||
# is still alive return None (we can return None also on BSD).
|
||||
# Reference: http://goo.gl/55XgO
|
||||
procfs_path = self._procfs_path
|
||||
try:
|
||||
return os.readlink("%s/%s/path/cwd" % (procfs_path, self.pid))
|
||||
except OSError as err:
|
||||
if err.errno == errno.ENOENT:
|
||||
os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD
|
||||
return None
|
||||
raise
|
||||
|
||||
@wrap_exceptions
|
||||
def memory_info(self):
|
||||
ret = self._proc_basic_info()
|
||||
rss, vms = ret[1] * 1024, ret[2] * 1024
|
||||
return pmem(rss, vms)
|
||||
|
||||
memory_full_info = memory_info
|
||||
|
||||
@wrap_exceptions
|
||||
def status(self):
|
||||
code = self._proc_basic_info()[6]
|
||||
# XXX is '?' legit? (we're not supposed to return it anyway)
|
||||
return PROC_STATUSES.get(code, '?')
|
||||
|
||||
@wrap_exceptions
|
||||
def threads(self):
|
||||
procfs_path = self._procfs_path
|
||||
ret = []
|
||||
tids = os.listdir('%s/%d/lwp' % (procfs_path, self.pid))
|
||||
hit_enoent = False
|
||||
for tid in tids:
|
||||
tid = int(tid)
|
||||
try:
|
||||
utime, stime = cext.query_process_thread(
|
||||
self.pid, tid, procfs_path)
|
||||
except EnvironmentError as err:
|
||||
if err.errno == errno.EOVERFLOW and not IS_64_BIT:
|
||||
# We may get here if we attempt to query a 64bit process
|
||||
# with a 32bit python.
|
||||
# Error originates from read() and also tools like "cat"
|
||||
# fail in the same way (!).
|
||||
# Since there simply is no way to determine CPU times we
|
||||
# return 0.0 as a fallback. See:
|
||||
# https://github.com/giampaolo/psutil/issues/857
|
||||
continue
|
||||
# ENOENT == thread gone in meantime
|
||||
if err.errno == errno.ENOENT:
|
||||
hit_enoent = True
|
||||
continue
|
||||
raise
|
||||
else:
|
||||
nt = _common.pthread(tid, utime, stime)
|
||||
ret.append(nt)
|
||||
if hit_enoent:
|
||||
# raise NSP if the process disappeared on us
|
||||
os.stat('%s/%s' % (procfs_path, self.pid))
|
||||
return ret
|
||||
|
||||
@wrap_exceptions
|
||||
def open_files(self):
|
||||
retlist = []
|
||||
hit_enoent = False
|
||||
procfs_path = self._procfs_path
|
||||
pathdir = '%s/%d/path' % (procfs_path, self.pid)
|
||||
for fd in os.listdir('%s/%d/fd' % (procfs_path, self.pid)):
|
||||
path = os.path.join(pathdir, fd)
|
||||
if os.path.islink(path):
|
||||
try:
|
||||
file = os.readlink(path)
|
||||
except OSError as err:
|
||||
# ENOENT == file which is gone in the meantime
|
||||
if err.errno == errno.ENOENT:
|
||||
hit_enoent = True
|
||||
continue
|
||||
raise
|
||||
else:
|
||||
if isfile_strict(file):
|
||||
retlist.append(_common.popenfile(file, int(fd)))
|
||||
if hit_enoent:
|
||||
# raise NSP if the process disappeared on us
|
||||
os.stat('%s/%s' % (procfs_path, self.pid))
|
||||
return retlist
|
||||
|
||||
def _get_unix_sockets(self, pid):
|
||||
"""Get UNIX sockets used by process by parsing 'pfiles' output."""
|
||||
# TODO: rewrite this in C (...but the damn netstat source code
|
||||
# does not include this part! Argh!!)
|
||||
cmd = "pfiles %s" % pid
|
||||
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
if PY3:
|
||||
stdout, stderr = [x.decode(sys.stdout.encoding)
|
||||
for x in (stdout, stderr)]
|
||||
if p.returncode != 0:
|
||||
if 'permission denied' in stderr.lower():
|
||||
raise AccessDenied(self.pid, self._name)
|
||||
if 'no such process' in stderr.lower():
|
||||
raise NoSuchProcess(self.pid, self._name)
|
||||
raise RuntimeError("%r command error\n%s" % (cmd, stderr))
|
||||
|
||||
lines = stdout.split('\n')[2:]
|
||||
for i, line in enumerate(lines):
|
||||
line = line.lstrip()
|
||||
if line.startswith('sockname: AF_UNIX'):
|
||||
path = line.split(' ', 2)[2]
|
||||
type = lines[i - 2].strip()
|
||||
if type == 'SOCK_STREAM':
|
||||
type = socket.SOCK_STREAM
|
||||
elif type == 'SOCK_DGRAM':
|
||||
type = socket.SOCK_DGRAM
|
||||
else:
|
||||
type = -1
|
||||
yield (-1, socket.AF_UNIX, type, path, "", _common.CONN_NONE)
|
||||
|
||||
@wrap_exceptions
|
||||
def connections(self, kind='inet'):
|
||||
ret = net_connections(kind, _pid=self.pid)
|
||||
# The underlying C implementation retrieves all OS connections
|
||||
# and filters them by PID. At this point we can't tell whether
|
||||
# an empty list means there were no connections for process or
|
||||
# process is no longer active so we force NSP in case the PID
|
||||
# is no longer there.
|
||||
if not ret:
|
||||
# will raise NSP if process is gone
|
||||
os.stat('%s/%s' % (self._procfs_path, self.pid))
|
||||
|
||||
# UNIX sockets
|
||||
if kind in ('all', 'unix'):
|
||||
ret.extend([_common.pconn(*conn) for conn in
|
||||
self._get_unix_sockets(self.pid)])
|
||||
return ret
|
||||
|
||||
nt_mmap_grouped = namedtuple('mmap', 'path rss anon locked')
|
||||
nt_mmap_ext = namedtuple('mmap', 'addr perms path rss anon locked')
|
||||
|
||||
@wrap_exceptions
|
||||
def memory_maps(self):
|
||||
def toaddr(start, end):
|
||||
return '%s-%s' % (hex(start)[2:].strip('L'),
|
||||
hex(end)[2:].strip('L'))
|
||||
|
||||
procfs_path = self._procfs_path
|
||||
retlist = []
|
||||
try:
|
||||
rawlist = cext.proc_memory_maps(self.pid, procfs_path)
|
||||
except OSError as err:
|
||||
if err.errno == errno.EOVERFLOW and not IS_64_BIT:
|
||||
# We may get here if we attempt to query a 64bit process
|
||||
# with a 32bit python.
|
||||
# Error originates from read() and also tools like "cat"
|
||||
# fail in the same way (!).
|
||||
# Since there simply is no way to determine CPU times we
|
||||
# return 0.0 as a fallback. See:
|
||||
# https://github.com/giampaolo/psutil/issues/857
|
||||
return []
|
||||
else:
|
||||
raise
|
||||
hit_enoent = False
|
||||
for item in rawlist:
|
||||
addr, addrsize, perm, name, rss, anon, locked = item
|
||||
addr = toaddr(addr, addrsize)
|
||||
if not name.startswith('['):
|
||||
try:
|
||||
name = os.readlink(
|
||||
'%s/%s/path/%s' % (procfs_path, self.pid, name))
|
||||
except OSError as err:
|
||||
if err.errno == errno.ENOENT:
|
||||
# sometimes the link may not be resolved by
|
||||
# readlink() even if it exists (ls shows it).
|
||||
# If that's the case we just return the
|
||||
# unresolved link path.
|
||||
# This seems an incosistency with /proc similar
|
||||
# to: http://goo.gl/55XgO
|
||||
name = '%s/%s/path/%s' % (procfs_path, self.pid, name)
|
||||
hit_enoent = True
|
||||
else:
|
||||
raise
|
||||
retlist.append((addr, perm, name, rss, anon, locked))
|
||||
if hit_enoent:
|
||||
# raise NSP if the process disappeared on us
|
||||
os.stat('%s/%s' % (procfs_path, self.pid))
|
||||
return retlist
|
||||
|
||||
@wrap_exceptions
|
||||
def num_fds(self):
|
||||
return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid)))
|
||||
|
||||
@wrap_exceptions
|
||||
def num_ctx_switches(self):
|
||||
return _common.pctxsw(
|
||||
*cext.proc_num_ctx_switches(self.pid, self._procfs_path))
|
||||
|
||||
@wrap_exceptions
|
||||
def wait(self, timeout=None):
|
||||
try:
|
||||
return _psposix.wait_pid(self.pid, timeout)
|
||||
except _psposix.TimeoutExpired:
|
||||
raise TimeoutExpired(timeout, self.pid, self._name)
|
||||
Vendored
-1047
File diff suppressed because it is too large
Load Diff
-126
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*
|
||||
* Routines common to all platforms.
|
||||
*/
|
||||
|
||||
#ifdef PSUTIL_POSIX
|
||||
#include <sys/types.h>
|
||||
#include <signal.h>
|
||||
#endif
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
|
||||
/*
|
||||
* Set OSError(errno=ESRCH, strerror="No such process") Python exception.
|
||||
*/
|
||||
PyObject *
|
||||
NoSuchProcess(void) {
|
||||
PyObject *exc;
|
||||
char *msg = strerror(ESRCH);
|
||||
exc = PyObject_CallFunction(PyExc_OSError, "(is)", ESRCH, msg);
|
||||
PyErr_SetObject(PyExc_OSError, exc);
|
||||
Py_XDECREF(exc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Set OSError(errno=EACCES, strerror="Permission denied") Python exception.
|
||||
*/
|
||||
PyObject *
|
||||
AccessDenied(void) {
|
||||
PyObject *exc;
|
||||
char *msg = strerror(EACCES);
|
||||
exc = PyObject_CallFunction(PyExc_OSError, "(is)", EACCES, msg);
|
||||
PyErr_SetObject(PyExc_OSError, exc);
|
||||
Py_XDECREF(exc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#ifdef PSUTIL_POSIX
|
||||
/*
|
||||
* Check if PID exists. Return values:
|
||||
* 1: exists
|
||||
* 0: does not exist
|
||||
* -1: error (Python exception is set)
|
||||
*/
|
||||
int
|
||||
psutil_pid_exists(long pid) {
|
||||
int ret;
|
||||
|
||||
// No negative PID exists, plus -1 is an alias for sending signal
|
||||
// too all processes except system ones. Not what we want.
|
||||
if (pid < 0)
|
||||
return 0;
|
||||
|
||||
// As per "man 2 kill" PID 0 is an alias for sending the signal to
|
||||
// every process in the process group of the calling process.
|
||||
// Not what we want. Some platforms have PID 0, some do not.
|
||||
// We decide that at runtime.
|
||||
if (pid == 0) {
|
||||
#if defined(PSUTIL_LINUX) || defined(PSUTIL_FREEBSD)
|
||||
return 0;
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(PSUTIL_OSX)
|
||||
ret = kill((pid_t)pid , 0);
|
||||
#else
|
||||
ret = kill(pid , 0);
|
||||
#endif
|
||||
|
||||
if (ret == 0)
|
||||
return 1;
|
||||
else {
|
||||
if (errno == ESRCH) {
|
||||
// ESRCH == No such process
|
||||
return 0;
|
||||
}
|
||||
else if (errno == EPERM) {
|
||||
// EPERM clearly indicates there's a process to deny
|
||||
// access to.
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
// According to "man 2 kill" possible error values are
|
||||
// (EINVAL, EPERM, ESRCH) therefore we should never get
|
||||
// here. If we do let's be explicit in considering this
|
||||
// an error.
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Utility used for those syscalls which do not return a meaningful
|
||||
* error that we can translate into an exception which makes sense.
|
||||
* As such, we'll have to guess.
|
||||
* On UNIX, if errno is set, we return that one (OSError).
|
||||
* Else, if PID does not exist we assume the syscall failed because
|
||||
* of that so we raise NoSuchProcess.
|
||||
* If none of this is true we giveup and raise RuntimeError(msg).
|
||||
* This will always set a Python exception and return NULL.
|
||||
*/
|
||||
int
|
||||
psutil_raise_for_pid(long pid, char *msg) {
|
||||
// Set exception to AccessDenied if pid exists else NoSuchProcess.
|
||||
if (errno != 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return 0;
|
||||
}
|
||||
if (psutil_pid_exists(pid) == 0)
|
||||
NoSuchProcess();
|
||||
else
|
||||
PyErr_SetString(PyExc_RuntimeError, msg);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
PyObject* AccessDenied(void);
|
||||
PyObject* NoSuchProcess(void);
|
||||
|
||||
#ifdef PSUTIL_POSIX
|
||||
int psutil_pid_exists(long pid);
|
||||
void psutil_raise_for_pid(long pid, char *msg);
|
||||
#endif
|
||||
Vendored
-688
@@ -1,688 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*
|
||||
* Linux-specific functions.
|
||||
*/
|
||||
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE 1
|
||||
#endif
|
||||
#include <Python.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <mntent.h>
|
||||
#include <features.h>
|
||||
#include <utmp.h>
|
||||
#include <sched.h>
|
||||
#include <linux/version.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/sysinfo.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/sockios.h>
|
||||
#include <linux/if.h>
|
||||
|
||||
// see: https://github.com/giampaolo/psutil/issues/659
|
||||
#ifdef PSUTIL_ETHTOOL_MISSING_TYPES
|
||||
#include <linux/types.h>
|
||||
typedef __u64 u64;
|
||||
typedef __u32 u32;
|
||||
typedef __u16 u16;
|
||||
typedef __u8 u8;
|
||||
#endif
|
||||
/* Avoid redefinition of struct sysinfo with musl libc */
|
||||
#define _LINUX_SYSINFO_H
|
||||
#include <linux/ethtool.h>
|
||||
|
||||
/* The minimum number of CPUs allocated in a cpu_set_t */
|
||||
static const int NCPUS_START = sizeof(unsigned long) * CHAR_BIT;
|
||||
|
||||
// Linux >= 2.6.13
|
||||
#define PSUTIL_HAVE_IOPRIO defined(__NR_ioprio_get) && defined(__NR_ioprio_set)
|
||||
|
||||
// Linux >= 2.6.36 (supposedly) and glibc >= 13
|
||||
#define PSUTIL_HAVE_PRLIMIT \
|
||||
(LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)) && \
|
||||
(__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 13) && \
|
||||
defined(__NR_prlimit64)
|
||||
|
||||
#if PSUTIL_HAVE_PRLIMIT
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
#include <time.h>
|
||||
#include <sys/resource.h>
|
||||
#endif
|
||||
|
||||
|
||||
// May happen on old RedHat versions, see:
|
||||
// https://github.com/giampaolo/psutil/issues/607
|
||||
#ifndef DUPLEX_UNKNOWN
|
||||
#define DUPLEX_UNKNOWN 0xff
|
||||
#endif
|
||||
|
||||
|
||||
#if PSUTIL_HAVE_IOPRIO
|
||||
enum {
|
||||
IOPRIO_WHO_PROCESS = 1,
|
||||
};
|
||||
|
||||
static inline int
|
||||
ioprio_get(int which, int who) {
|
||||
return syscall(__NR_ioprio_get, which, who);
|
||||
}
|
||||
|
||||
static inline int
|
||||
ioprio_set(int which, int who, int ioprio) {
|
||||
return syscall(__NR_ioprio_set, which, who, ioprio);
|
||||
}
|
||||
|
||||
#define IOPRIO_CLASS_SHIFT 13
|
||||
#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1)
|
||||
|
||||
#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT)
|
||||
#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK)
|
||||
#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data)
|
||||
|
||||
|
||||
/*
|
||||
* Return a (ioclass, iodata) Python tuple representing process I/O priority.
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_proc_ioprio_get(PyObject *self, PyObject *args) {
|
||||
long pid;
|
||||
int ioprio, ioclass, iodata;
|
||||
if (! PyArg_ParseTuple(args, "l", &pid))
|
||||
return NULL;
|
||||
ioprio = ioprio_get(IOPRIO_WHO_PROCESS, pid);
|
||||
if (ioprio == -1)
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
ioclass = IOPRIO_PRIO_CLASS(ioprio);
|
||||
iodata = IOPRIO_PRIO_DATA(ioprio);
|
||||
return Py_BuildValue("ii", ioclass, iodata);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* A wrapper around ioprio_set(); sets process I/O priority.
|
||||
* ioclass can be either IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE
|
||||
* or 0. iodata goes from 0 to 7 depending on ioclass specified.
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_proc_ioprio_set(PyObject *self, PyObject *args) {
|
||||
long pid;
|
||||
int ioprio, ioclass, iodata;
|
||||
int retval;
|
||||
|
||||
if (! PyArg_ParseTuple(args, "lii", &pid, &ioclass, &iodata))
|
||||
return NULL;
|
||||
ioprio = IOPRIO_PRIO_VALUE(ioclass, iodata);
|
||||
retval = ioprio_set(IOPRIO_WHO_PROCESS, pid, ioprio);
|
||||
if (retval == -1)
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if PSUTIL_HAVE_PRLIMIT
|
||||
/*
|
||||
* A wrapper around prlimit(2); sets process resource limits.
|
||||
* This can be used for both get and set, in which case extra
|
||||
* 'soft' and 'hard' args must be provided.
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_linux_prlimit(PyObject *self, PyObject *args) {
|
||||
long pid;
|
||||
int ret, resource;
|
||||
struct rlimit old, new;
|
||||
struct rlimit *newp = NULL;
|
||||
PyObject *py_soft = NULL;
|
||||
PyObject *py_hard = NULL;
|
||||
|
||||
if (! PyArg_ParseTuple(args, "li|OO", &pid, &resource, &py_soft, &py_hard))
|
||||
return NULL;
|
||||
|
||||
// get
|
||||
if (py_soft == NULL && py_hard == NULL) {
|
||||
ret = prlimit(pid, resource, NULL, &old);
|
||||
if (ret == -1)
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
#if defined(PSUTIL_HAVE_LONG_LONG)
|
||||
if (sizeof(old.rlim_cur) > sizeof(long)) {
|
||||
return Py_BuildValue("LL",
|
||||
(PY_LONG_LONG)old.rlim_cur,
|
||||
(PY_LONG_LONG)old.rlim_max);
|
||||
}
|
||||
#endif
|
||||
return Py_BuildValue("ll", (long)old.rlim_cur, (long)old.rlim_max);
|
||||
}
|
||||
|
||||
// set
|
||||
else {
|
||||
#if defined(PSUTIL_HAVE_LARGEFILE_SUPPORT)
|
||||
new.rlim_cur = PyLong_AsLongLong(py_soft);
|
||||
if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred())
|
||||
return NULL;
|
||||
new.rlim_max = PyLong_AsLongLong(py_hard);
|
||||
if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred())
|
||||
return NULL;
|
||||
#else
|
||||
new.rlim_cur = PyLong_AsLong(py_soft);
|
||||
if (new.rlim_cur == (rlim_t) - 1 && PyErr_Occurred())
|
||||
return NULL;
|
||||
new.rlim_max = PyLong_AsLong(py_hard);
|
||||
if (new.rlim_max == (rlim_t) - 1 && PyErr_Occurred())
|
||||
return NULL;
|
||||
#endif
|
||||
newp = &new;
|
||||
ret = prlimit(pid, resource, newp, &old);
|
||||
if (ret == -1)
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Return disk mounted partitions as a list of tuples including device,
|
||||
* mount point and filesystem type
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_disk_partitions(PyObject *self, PyObject *args) {
|
||||
FILE *file = NULL;
|
||||
struct mntent *entry;
|
||||
PyObject *py_retlist = PyList_New(0);
|
||||
PyObject *py_tuple = NULL;
|
||||
|
||||
if (py_retlist == NULL)
|
||||
return NULL;
|
||||
|
||||
// MOUNTED constant comes from mntent.h and it's == '/etc/mtab'
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
file = setmntent(MOUNTED, "r");
|
||||
Py_END_ALLOW_THREADS
|
||||
if ((file == 0) || (file == NULL)) {
|
||||
PyErr_SetFromErrnoWithFilename(PyExc_OSError, MOUNTED);
|
||||
goto error;
|
||||
}
|
||||
|
||||
while ((entry = getmntent(file))) {
|
||||
if (entry == NULL) {
|
||||
PyErr_Format(PyExc_RuntimeError, "getmntent() syscall failed");
|
||||
goto error;
|
||||
}
|
||||
py_tuple = Py_BuildValue("(ssss)",
|
||||
entry->mnt_fsname, // device
|
||||
entry->mnt_dir, // mount point
|
||||
entry->mnt_type, // fs type
|
||||
entry->mnt_opts); // options
|
||||
if (! py_tuple)
|
||||
goto error;
|
||||
if (PyList_Append(py_retlist, py_tuple))
|
||||
goto error;
|
||||
Py_DECREF(py_tuple);
|
||||
}
|
||||
endmntent(file);
|
||||
return py_retlist;
|
||||
|
||||
error:
|
||||
if (file != NULL)
|
||||
endmntent(file);
|
||||
Py_XDECREF(py_tuple);
|
||||
Py_DECREF(py_retlist);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* A wrapper around sysinfo(), return system memory usage statistics.
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_linux_sysinfo(PyObject *self, PyObject *args) {
|
||||
struct sysinfo info;
|
||||
|
||||
if (sysinfo(&info) != 0)
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
// note: boot time might also be determined from here
|
||||
return Py_BuildValue(
|
||||
"(kkkkkkI)",
|
||||
info.totalram, // total
|
||||
info.freeram, // free
|
||||
info.bufferram, // buffer
|
||||
info.sharedram, // shared
|
||||
info.totalswap, // swap tot
|
||||
info.freeswap, // swap free
|
||||
info.mem_unit // multiplier
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return process CPU affinity as a Python list
|
||||
* The dual implementation exists because of:
|
||||
* https://github.com/giampaolo/psutil/issues/536
|
||||
*/
|
||||
|
||||
#ifdef CPU_ALLOC
|
||||
|
||||
static PyObject *
|
||||
psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) {
|
||||
int cpu, ncpus, count, cpucount_s;
|
||||
long pid;
|
||||
size_t setsize;
|
||||
cpu_set_t *mask = NULL;
|
||||
PyObject *py_list = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "l", &pid))
|
||||
return NULL;
|
||||
ncpus = NCPUS_START;
|
||||
while (1) {
|
||||
setsize = CPU_ALLOC_SIZE(ncpus);
|
||||
mask = CPU_ALLOC(ncpus);
|
||||
if (mask == NULL)
|
||||
return PyErr_NoMemory();
|
||||
if (sched_getaffinity(pid, setsize, mask) == 0)
|
||||
break;
|
||||
CPU_FREE(mask);
|
||||
if (errno != EINVAL)
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
if (ncpus > INT_MAX / 2) {
|
||||
PyErr_SetString(PyExc_OverflowError, "could not allocate "
|
||||
"a large enough CPU set");
|
||||
return NULL;
|
||||
}
|
||||
ncpus = ncpus * 2;
|
||||
}
|
||||
|
||||
py_list = PyList_New(0);
|
||||
if (py_list == NULL)
|
||||
goto error;
|
||||
|
||||
cpucount_s = CPU_COUNT_S(setsize, mask);
|
||||
for (cpu = 0, count = cpucount_s; count; cpu++) {
|
||||
if (CPU_ISSET_S(cpu, setsize, mask)) {
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyObject *cpu_num = PyLong_FromLong(cpu);
|
||||
#else
|
||||
PyObject *cpu_num = PyInt_FromLong(cpu);
|
||||
#endif
|
||||
if (cpu_num == NULL)
|
||||
goto error;
|
||||
if (PyList_Append(py_list, cpu_num)) {
|
||||
Py_DECREF(cpu_num);
|
||||
goto error;
|
||||
}
|
||||
Py_DECREF(cpu_num);
|
||||
--count;
|
||||
}
|
||||
}
|
||||
CPU_FREE(mask);
|
||||
return py_list;
|
||||
|
||||
error:
|
||||
if (mask)
|
||||
CPU_FREE(mask);
|
||||
Py_XDECREF(py_list);
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
|
||||
|
||||
/*
|
||||
* Alternative implementation in case CPU_ALLOC is not defined.
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) {
|
||||
cpu_set_t cpuset;
|
||||
unsigned int len = sizeof(cpu_set_t);
|
||||
long pid;
|
||||
int i;
|
||||
PyObject* py_retlist = NULL;
|
||||
PyObject *py_cpu_num = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "l", &pid))
|
||||
return NULL;
|
||||
CPU_ZERO(&cpuset);
|
||||
if (sched_getaffinity(pid, len, &cpuset) < 0)
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
|
||||
py_retlist = PyList_New(0);
|
||||
if (py_retlist == NULL)
|
||||
goto error;
|
||||
for (i = 0; i < CPU_SETSIZE; ++i) {
|
||||
if (CPU_ISSET(i, &cpuset)) {
|
||||
py_cpu_num = Py_BuildValue("i", i);
|
||||
if (py_cpu_num == NULL)
|
||||
goto error;
|
||||
if (PyList_Append(py_retlist, py_cpu_num))
|
||||
goto error;
|
||||
Py_DECREF(py_cpu_num);
|
||||
}
|
||||
}
|
||||
|
||||
return py_retlist;
|
||||
|
||||
error:
|
||||
Py_XDECREF(py_cpu_num);
|
||||
Py_XDECREF(py_retlist);
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Set process CPU affinity; expects a bitmask
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) {
|
||||
cpu_set_t cpu_set;
|
||||
size_t len;
|
||||
long pid;
|
||||
int i, seq_len;
|
||||
PyObject *py_cpu_set;
|
||||
PyObject *py_cpu_seq = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "lO", &pid, &py_cpu_set))
|
||||
return NULL;
|
||||
|
||||
if (!PySequence_Check(py_cpu_set)) {
|
||||
PyErr_Format(PyExc_TypeError, "sequence argument expected, got %s",
|
||||
Py_TYPE(py_cpu_set)->tp_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
py_cpu_seq = PySequence_Fast(py_cpu_set, "expected a sequence or integer");
|
||||
if (!py_cpu_seq)
|
||||
goto error;
|
||||
seq_len = PySequence_Fast_GET_SIZE(py_cpu_seq);
|
||||
CPU_ZERO(&cpu_set);
|
||||
for (i = 0; i < seq_len; i++) {
|
||||
PyObject *item = PySequence_Fast_GET_ITEM(py_cpu_seq, i);
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
long value = PyLong_AsLong(item);
|
||||
#else
|
||||
long value = PyInt_AsLong(item);
|
||||
#endif
|
||||
if ((value == -1) || PyErr_Occurred()) {
|
||||
if (!PyErr_Occurred())
|
||||
PyErr_SetString(PyExc_ValueError, "invalid CPU value");
|
||||
goto error;
|
||||
}
|
||||
CPU_SET(value, &cpu_set);
|
||||
}
|
||||
|
||||
|
||||
len = sizeof(cpu_set);
|
||||
if (sched_setaffinity(pid, len, &cpu_set)) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
Py_DECREF(py_cpu_seq);
|
||||
Py_RETURN_NONE;
|
||||
|
||||
error:
|
||||
if (py_cpu_seq != NULL)
|
||||
Py_DECREF(py_cpu_seq);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return currently connected users as a list of tuples.
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_users(PyObject *self, PyObject *args) {
|
||||
struct utmp *ut;
|
||||
PyObject *py_retlist = PyList_New(0);
|
||||
PyObject *py_tuple = NULL;
|
||||
PyObject *py_user_proc = NULL;
|
||||
|
||||
if (py_retlist == NULL)
|
||||
return NULL;
|
||||
setutent();
|
||||
while (NULL != (ut = getutent())) {
|
||||
py_tuple = NULL;
|
||||
py_user_proc = NULL;
|
||||
if (ut->ut_type == USER_PROCESS)
|
||||
py_user_proc = Py_True;
|
||||
else
|
||||
py_user_proc = Py_False;
|
||||
py_tuple = Py_BuildValue(
|
||||
"(sssfO)",
|
||||
ut->ut_user, // username
|
||||
ut->ut_line, // tty
|
||||
ut->ut_host, // hostname
|
||||
(float)ut->ut_tv.tv_sec, // tstamp
|
||||
py_user_proc // (bool) user process
|
||||
);
|
||||
if (! py_tuple)
|
||||
goto error;
|
||||
if (PyList_Append(py_retlist, py_tuple))
|
||||
goto error;
|
||||
Py_DECREF(py_tuple);
|
||||
}
|
||||
endutent();
|
||||
return py_retlist;
|
||||
|
||||
error:
|
||||
Py_XDECREF(py_tuple);
|
||||
Py_XDECREF(py_user_proc);
|
||||
Py_DECREF(py_retlist);
|
||||
endutent();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return stats about a particular network
|
||||
* interface. References:
|
||||
* https://github.com/dpaleino/wicd/blob/master/wicd/backends/be-ioctl.py
|
||||
* http://www.i-scream.org/libstatgrab/
|
||||
*/
|
||||
static PyObject*
|
||||
psutil_net_if_duplex_speed(PyObject* self, PyObject* args) {
|
||||
char *nic_name;
|
||||
int sock = 0;
|
||||
int ret;
|
||||
int duplex;
|
||||
int speed;
|
||||
struct ifreq ifr;
|
||||
struct ethtool_cmd ethcmd;
|
||||
PyObject *py_retlist = NULL;
|
||||
|
||||
if (! PyArg_ParseTuple(args, "s", &nic_name))
|
||||
return NULL;
|
||||
|
||||
sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sock == -1)
|
||||
goto error;
|
||||
strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
|
||||
|
||||
// duplex and speed
|
||||
memset(ðcmd, 0, sizeof ethcmd);
|
||||
ethcmd.cmd = ETHTOOL_GSET;
|
||||
ifr.ifr_data = (void *)ðcmd;
|
||||
ret = ioctl(sock, SIOCETHTOOL, &ifr);
|
||||
|
||||
if (ret != -1) {
|
||||
duplex = ethcmd.duplex;
|
||||
speed = ethcmd.speed;
|
||||
}
|
||||
else {
|
||||
if ((errno == EOPNOTSUPP) || (errno == EINVAL)) {
|
||||
// EOPNOTSUPP may occur in case of wi-fi cards.
|
||||
// For EINVAL see:
|
||||
// https://github.com/giampaolo/psutil/issues/797
|
||||
// #issuecomment-202999532
|
||||
duplex = DUPLEX_UNKNOWN;
|
||||
speed = 0;
|
||||
}
|
||||
else {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
close(sock);
|
||||
py_retlist = Py_BuildValue("[ii]", duplex, speed);
|
||||
if (!py_retlist)
|
||||
goto error;
|
||||
return py_retlist;
|
||||
|
||||
error:
|
||||
if (sock != -1)
|
||||
close(sock);
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Define the psutil C module methods and initialize the module.
|
||||
*/
|
||||
static PyMethodDef
|
||||
PsutilMethods[] = {
|
||||
|
||||
// --- per-process functions
|
||||
|
||||
#if PSUTIL_HAVE_IOPRIO
|
||||
{"proc_ioprio_get", psutil_proc_ioprio_get, METH_VARARGS,
|
||||
"Get process I/O priority"},
|
||||
{"proc_ioprio_set", psutil_proc_ioprio_set, METH_VARARGS,
|
||||
"Set process I/O priority"},
|
||||
#endif
|
||||
{"proc_cpu_affinity_get", psutil_proc_cpu_affinity_get, METH_VARARGS,
|
||||
"Return process CPU affinity as a Python long (the bitmask)."},
|
||||
{"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS,
|
||||
"Set process CPU affinity; expects a bitmask."},
|
||||
|
||||
// --- system related functions
|
||||
|
||||
{"disk_partitions", psutil_disk_partitions, METH_VARARGS,
|
||||
"Return disk mounted partitions as a list of tuples including "
|
||||
"device, mount point and filesystem type"},
|
||||
{"users", psutil_users, METH_VARARGS,
|
||||
"Return currently connected users as a list of tuples"},
|
||||
{"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS,
|
||||
"Return duplex and speed info about a NIC"},
|
||||
|
||||
// --- linux specific
|
||||
|
||||
{"linux_sysinfo", psutil_linux_sysinfo, METH_VARARGS,
|
||||
"A wrapper around sysinfo(), return system memory usage statistics"},
|
||||
#if PSUTIL_HAVE_PRLIMIT
|
||||
{"linux_prlimit", psutil_linux_prlimit, METH_VARARGS,
|
||||
"Get or set process resource limits."},
|
||||
#endif
|
||||
|
||||
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
struct module_state {
|
||||
PyObject *error;
|
||||
};
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))
|
||||
#else
|
||||
#define GETSTATE(m) (&_state)
|
||||
#endif
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
|
||||
static int
|
||||
psutil_linux_traverse(PyObject *m, visitproc visit, void *arg) {
|
||||
Py_VISIT(GETSTATE(m)->error);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
psutil_linux_clear(PyObject *m) {
|
||||
Py_CLEAR(GETSTATE(m)->error);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct PyModuleDef
|
||||
moduledef = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"psutil_linux",
|
||||
NULL,
|
||||
sizeof(struct module_state),
|
||||
PsutilMethods,
|
||||
NULL,
|
||||
psutil_linux_traverse,
|
||||
psutil_linux_clear,
|
||||
NULL
|
||||
};
|
||||
|
||||
#define INITERROR return NULL
|
||||
|
||||
PyMODINIT_FUNC PyInit__psutil_linux(void)
|
||||
|
||||
#else
|
||||
#define INITERROR return
|
||||
|
||||
void init_psutil_linux(void)
|
||||
#endif
|
||||
{
|
||||
PyObject *v;
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyObject *module = PyModule_Create(&moduledef);
|
||||
#else
|
||||
PyObject *module = Py_InitModule("_psutil_linux", PsutilMethods);
|
||||
#endif
|
||||
|
||||
PyModule_AddIntConstant(module, "version", PSUTIL_VERSION);
|
||||
#if PSUTIL_HAVE_PRLIMIT
|
||||
PyModule_AddIntConstant(module, "RLIMIT_AS", RLIMIT_AS);
|
||||
PyModule_AddIntConstant(module, "RLIMIT_CORE", RLIMIT_CORE);
|
||||
PyModule_AddIntConstant(module, "RLIMIT_CPU", RLIMIT_CPU);
|
||||
PyModule_AddIntConstant(module, "RLIMIT_DATA", RLIMIT_DATA);
|
||||
PyModule_AddIntConstant(module, "RLIMIT_FSIZE", RLIMIT_FSIZE);
|
||||
PyModule_AddIntConstant(module, "RLIMIT_LOCKS", RLIMIT_LOCKS);
|
||||
PyModule_AddIntConstant(module, "RLIMIT_MEMLOCK", RLIMIT_MEMLOCK);
|
||||
PyModule_AddIntConstant(module, "RLIMIT_NOFILE", RLIMIT_NOFILE);
|
||||
PyModule_AddIntConstant(module, "RLIMIT_NPROC", RLIMIT_NPROC);
|
||||
PyModule_AddIntConstant(module, "RLIMIT_RSS", RLIMIT_RSS);
|
||||
PyModule_AddIntConstant(module, "RLIMIT_STACK", RLIMIT_STACK);
|
||||
|
||||
#if defined(HAVE_LONG_LONG)
|
||||
if (sizeof(RLIM_INFINITY) > sizeof(long)) {
|
||||
v = PyLong_FromLongLong((PY_LONG_LONG) RLIM_INFINITY);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
v = PyLong_FromLong((long) RLIM_INFINITY);
|
||||
}
|
||||
if (v) {
|
||||
PyModule_AddObject(module, "RLIM_INFINITY", v);
|
||||
}
|
||||
|
||||
#ifdef RLIMIT_MSGQUEUE
|
||||
PyModule_AddIntConstant(module, "RLIMIT_MSGQUEUE", RLIMIT_MSGQUEUE);
|
||||
#endif
|
||||
#ifdef RLIMIT_NICE
|
||||
PyModule_AddIntConstant(module, "RLIMIT_NICE", RLIMIT_NICE);
|
||||
#endif
|
||||
#ifdef RLIMIT_RTPRIO
|
||||
PyModule_AddIntConstant(module, "RLIMIT_RTPRIO", RLIMIT_RTPRIO);
|
||||
#endif
|
||||
#ifdef RLIMIT_RTTIME
|
||||
PyModule_AddIntConstant(module, "RLIMIT_RTTIME", RLIMIT_RTTIME);
|
||||
#endif
|
||||
#ifdef RLIMIT_SIGPENDING
|
||||
PyModule_AddIntConstant(module, "RLIMIT_SIGPENDING", RLIMIT_SIGPENDING);
|
||||
#endif
|
||||
#endif
|
||||
PyModule_AddIntConstant(module, "DUPLEX_HALF", DUPLEX_HALF);
|
||||
PyModule_AddIntConstant(module, "DUPLEX_FULL", DUPLEX_FULL);
|
||||
PyModule_AddIntConstant(module, "DUPLEX_UNKNOWN", DUPLEX_UNKNOWN);
|
||||
|
||||
if (module == NULL)
|
||||
INITERROR;
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
return module;
|
||||
#endif
|
||||
}
|
||||
Vendored
-1910
File diff suppressed because it is too large
Load Diff
Vendored
-619
@@ -1,619 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*
|
||||
* Functions specific to all POSIX compliant platforms.
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#ifdef PSUTIL_SUNOS10
|
||||
#include "arch/solaris/v10/ifaddrs.h"
|
||||
#else
|
||||
#include <ifaddrs.h>
|
||||
#endif
|
||||
|
||||
#if defined(PSUTIL_LINUX)
|
||||
#include <netdb.h>
|
||||
#include <linux/if_packet.h>
|
||||
#elif defined(PSUTIL_BSD) || defined(PSUTIL_OSX)
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <sys/sockio.h>
|
||||
#include <net/if_media.h>
|
||||
#include <net/if.h>
|
||||
#elif defined(PSUTIL_SUNOS)
|
||||
#include <netdb.h>
|
||||
#include <sys/sockio.h>
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Given a PID return process priority as a Python integer.
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_posix_getpriority(PyObject *self, PyObject *args) {
|
||||
long pid;
|
||||
int priority;
|
||||
errno = 0;
|
||||
|
||||
if (! PyArg_ParseTuple(args, "l", &pid))
|
||||
return NULL;
|
||||
|
||||
#ifdef PSUTIL_OSX
|
||||
priority = getpriority(PRIO_PROCESS, (id_t)pid);
|
||||
#else
|
||||
priority = getpriority(PRIO_PROCESS, pid);
|
||||
#endif
|
||||
if (errno != 0)
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
return Py_BuildValue("i", priority);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Given a PID and a value change process priority.
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_posix_setpriority(PyObject *self, PyObject *args) {
|
||||
long pid;
|
||||
int priority;
|
||||
int retval;
|
||||
|
||||
if (! PyArg_ParseTuple(args, "li", &pid, &priority))
|
||||
return NULL;
|
||||
|
||||
#ifdef PSUTIL_OSX
|
||||
retval = setpriority(PRIO_PROCESS, (id_t)pid, priority);
|
||||
#else
|
||||
retval = setpriority(PRIO_PROCESS, pid, priority);
|
||||
#endif
|
||||
if (retval == -1)
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Translate a sockaddr struct into a Python string.
|
||||
* Return None if address family is not AF_INET* or AF_PACKET.
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_convert_ipaddr(struct sockaddr *addr, int family) {
|
||||
char buf[NI_MAXHOST];
|
||||
int err;
|
||||
int addrlen;
|
||||
size_t n;
|
||||
size_t len;
|
||||
const char *data;
|
||||
char *ptr;
|
||||
|
||||
if (addr == NULL) {
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
else if (family == AF_INET || family == AF_INET6) {
|
||||
if (family == AF_INET)
|
||||
addrlen = sizeof(struct sockaddr_in);
|
||||
else
|
||||
addrlen = sizeof(struct sockaddr_in6);
|
||||
err = getnameinfo(addr, addrlen, buf, sizeof(buf), NULL, 0,
|
||||
NI_NUMERICHOST);
|
||||
if (err != 0) {
|
||||
// XXX we get here on FreeBSD when processing 'lo' / AF_INET6
|
||||
// broadcast. Not sure what to do other than returning None.
|
||||
// ifconfig does not show anything BTW.
|
||||
//PyErr_Format(PyExc_RuntimeError, gai_strerror(err));
|
||||
//return NULL;
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
else {
|
||||
return Py_BuildValue("s", buf);
|
||||
}
|
||||
}
|
||||
#ifdef PSUTIL_LINUX
|
||||
else if (family == AF_PACKET) {
|
||||
struct sockaddr_ll *lladdr = (struct sockaddr_ll *)addr;
|
||||
len = lladdr->sll_halen;
|
||||
data = (const char *)lladdr->sll_addr;
|
||||
}
|
||||
#elif defined(PSUTIL_BSD) || defined(PSUTIL_OSX)
|
||||
else if (addr->sa_family == AF_LINK) {
|
||||
// Note: prior to Python 3.4 socket module does not expose
|
||||
// AF_LINK so we'll do.
|
||||
struct sockaddr_dl *dladdr = (struct sockaddr_dl *)addr;
|
||||
len = dladdr->sdl_alen;
|
||||
data = LLADDR(dladdr);
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
// unknown family
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
// AF_PACKET or AF_LINK
|
||||
if (len > 0) {
|
||||
ptr = buf;
|
||||
for (n = 0; n < len; ++n) {
|
||||
sprintf(ptr, "%02x:", data[n] & 0xff);
|
||||
ptr += 3;
|
||||
}
|
||||
*--ptr = '\0';
|
||||
return Py_BuildValue("s", buf);
|
||||
}
|
||||
else {
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return NICs information a-la ifconfig as a list of tuples.
|
||||
* TODO: on Solaris we won't get any MAC address.
|
||||
*/
|
||||
static PyObject*
|
||||
psutil_net_if_addrs(PyObject* self, PyObject* args) {
|
||||
struct ifaddrs *ifaddr, *ifa;
|
||||
int family;
|
||||
|
||||
PyObject *py_retlist = PyList_New(0);
|
||||
PyObject *py_tuple = NULL;
|
||||
PyObject *py_address = NULL;
|
||||
PyObject *py_netmask = NULL;
|
||||
PyObject *py_broadcast = NULL;
|
||||
PyObject *py_ptp = NULL;
|
||||
|
||||
if (py_retlist == NULL)
|
||||
return NULL;
|
||||
if (getifaddrs(&ifaddr) == -1) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
|
||||
if (!ifa->ifa_addr)
|
||||
continue;
|
||||
family = ifa->ifa_addr->sa_family;
|
||||
py_address = psutil_convert_ipaddr(ifa->ifa_addr, family);
|
||||
// If the primary address can't be determined just skip it.
|
||||
// I've never seen this happen on Linux but I did on FreeBSD.
|
||||
if (py_address == Py_None)
|
||||
continue;
|
||||
if (py_address == NULL)
|
||||
goto error;
|
||||
py_netmask = psutil_convert_ipaddr(ifa->ifa_netmask, family);
|
||||
if (py_netmask == NULL)
|
||||
goto error;
|
||||
|
||||
if (ifa->ifa_flags & IFF_BROADCAST) {
|
||||
py_broadcast = psutil_convert_ipaddr(ifa->ifa_broadaddr, family);
|
||||
Py_INCREF(Py_None);
|
||||
py_ptp = Py_None;
|
||||
}
|
||||
else if (ifa->ifa_flags & IFF_POINTOPOINT) {
|
||||
py_ptp = psutil_convert_ipaddr(ifa->ifa_dstaddr, family);
|
||||
Py_INCREF(Py_None);
|
||||
py_broadcast = Py_None;
|
||||
}
|
||||
else {
|
||||
Py_INCREF(Py_None);
|
||||
Py_INCREF(Py_None);
|
||||
py_broadcast = Py_None;
|
||||
py_ptp = Py_None;
|
||||
}
|
||||
|
||||
if ((py_broadcast == NULL) || (py_ptp == NULL))
|
||||
goto error;
|
||||
py_tuple = Py_BuildValue(
|
||||
"(siOOOO)",
|
||||
ifa->ifa_name,
|
||||
family,
|
||||
py_address,
|
||||
py_netmask,
|
||||
py_broadcast,
|
||||
py_ptp
|
||||
);
|
||||
|
||||
if (! py_tuple)
|
||||
goto error;
|
||||
if (PyList_Append(py_retlist, py_tuple))
|
||||
goto error;
|
||||
Py_DECREF(py_tuple);
|
||||
Py_DECREF(py_address);
|
||||
Py_DECREF(py_netmask);
|
||||
Py_DECREF(py_broadcast);
|
||||
Py_DECREF(py_ptp);
|
||||
}
|
||||
|
||||
freeifaddrs(ifaddr);
|
||||
return py_retlist;
|
||||
|
||||
error:
|
||||
if (ifaddr != NULL)
|
||||
freeifaddrs(ifaddr);
|
||||
Py_DECREF(py_retlist);
|
||||
Py_XDECREF(py_tuple);
|
||||
Py_XDECREF(py_address);
|
||||
Py_XDECREF(py_netmask);
|
||||
Py_XDECREF(py_broadcast);
|
||||
Py_XDECREF(py_ptp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return NIC MTU. References:
|
||||
* http://www.i-scream.org/libstatgrab/
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_net_if_mtu(PyObject *self, PyObject *args) {
|
||||
char *nic_name;
|
||||
int sock = 0;
|
||||
int ret;
|
||||
#ifdef PSUTIL_SUNOS10
|
||||
struct lifreq lifr;
|
||||
#else
|
||||
struct ifreq ifr;
|
||||
#endif
|
||||
|
||||
if (! PyArg_ParseTuple(args, "s", &nic_name))
|
||||
return NULL;
|
||||
|
||||
sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sock == -1)
|
||||
goto error;
|
||||
|
||||
#ifdef PSUTIL_SUNOS10
|
||||
strncpy(lifr.lifr_name, nic_name, sizeof(lifr.lifr_name));
|
||||
ret = ioctl(sock, SIOCGIFMTU, &lifr);
|
||||
#else
|
||||
strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
|
||||
ret = ioctl(sock, SIOCGIFMTU, &ifr);
|
||||
#endif
|
||||
if (ret == -1)
|
||||
goto error;
|
||||
close(sock);
|
||||
|
||||
#ifdef PSUTIL_SUNOS10
|
||||
return Py_BuildValue("i", lifr.lifr_mtu);
|
||||
#else
|
||||
return Py_BuildValue("i", ifr.ifr_mtu);
|
||||
#endif
|
||||
|
||||
error:
|
||||
if (sock != 0)
|
||||
close(sock);
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Inspect NIC flags, returns a bool indicating whether the NIC is
|
||||
* running. References:
|
||||
* http://www.i-scream.org/libstatgrab/
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_net_if_flags(PyObject *self, PyObject *args) {
|
||||
char *nic_name;
|
||||
int sock = 0;
|
||||
int ret;
|
||||
struct ifreq ifr;
|
||||
|
||||
if (! PyArg_ParseTuple(args, "s", &nic_name))
|
||||
return NULL;
|
||||
|
||||
sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sock == -1)
|
||||
goto error;
|
||||
|
||||
strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
|
||||
ret = ioctl(sock, SIOCGIFFLAGS, &ifr);
|
||||
if (ret == -1)
|
||||
goto error;
|
||||
|
||||
close(sock);
|
||||
if ((ifr.ifr_flags & IFF_UP) != 0)
|
||||
return Py_BuildValue("O", Py_True);
|
||||
else
|
||||
return Py_BuildValue("O", Py_False);
|
||||
|
||||
error:
|
||||
if (sock != 0)
|
||||
close(sock);
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* net_if_stats() OSX/BSD implementation.
|
||||
*/
|
||||
#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX)
|
||||
|
||||
int psutil_get_nic_speed(int ifm_active) {
|
||||
// Determine NIC speed. Taken from:
|
||||
// http://www.i-scream.org/libstatgrab/
|
||||
// Assuming only ETHER devices
|
||||
switch(IFM_TYPE(ifm_active)) {
|
||||
case IFM_ETHER:
|
||||
switch(IFM_SUBTYPE(ifm_active)) {
|
||||
#if defined(IFM_HPNA_1) && ((!defined(IFM_10G_LR)) \
|
||||
|| (IFM_10G_LR != IFM_HPNA_1))
|
||||
// HomePNA 1.0 (1Mb/s)
|
||||
case(IFM_HPNA_1):
|
||||
return 1;
|
||||
#endif
|
||||
// 10 Mbit
|
||||
case(IFM_10_T): // 10BaseT - RJ45
|
||||
case(IFM_10_2): // 10Base2 - Thinnet
|
||||
case(IFM_10_5): // 10Base5 - AUI
|
||||
case(IFM_10_STP): // 10BaseT over shielded TP
|
||||
case(IFM_10_FL): // 10baseFL - Fiber
|
||||
return 10;
|
||||
// 100 Mbit
|
||||
case(IFM_100_TX): // 100BaseTX - RJ45
|
||||
case(IFM_100_FX): // 100BaseFX - Fiber
|
||||
case(IFM_100_T4): // 100BaseT4 - 4 pair cat 3
|
||||
case(IFM_100_VG): // 100VG-AnyLAN
|
||||
case(IFM_100_T2): // 100BaseT2
|
||||
return 100;
|
||||
// 1000 Mbit
|
||||
case(IFM_1000_SX): // 1000BaseSX - multi-mode fiber
|
||||
case(IFM_1000_LX): // 1000baseLX - single-mode fiber
|
||||
case(IFM_1000_CX): // 1000baseCX - 150ohm STP
|
||||
#if defined(IFM_1000_TX) && !defined(PSUTIL_OPENBSD)
|
||||
// FreeBSD 4 and others (but NOT OpenBSD) -> #define IFM_1000_T in net/if_media.h
|
||||
case(IFM_1000_TX):
|
||||
#endif
|
||||
#ifdef IFM_1000_FX
|
||||
case(IFM_1000_FX):
|
||||
#endif
|
||||
#ifdef IFM_1000_T
|
||||
case(IFM_1000_T):
|
||||
#endif
|
||||
return 1000;
|
||||
#if defined(IFM_10G_SR) || defined(IFM_10G_LR) || defined(IFM_10G_CX4) \
|
||||
|| defined(IFM_10G_T)
|
||||
#ifdef IFM_10G_SR
|
||||
case(IFM_10G_SR):
|
||||
#endif
|
||||
#ifdef IFM_10G_LR
|
||||
case(IFM_10G_LR):
|
||||
#endif
|
||||
#ifdef IFM_10G_CX4
|
||||
case(IFM_10G_CX4):
|
||||
#endif
|
||||
#ifdef IFM_10G_TWINAX
|
||||
case(IFM_10G_TWINAX):
|
||||
#endif
|
||||
#ifdef IFM_10G_TWINAX_LONG
|
||||
case(IFM_10G_TWINAX_LONG):
|
||||
#endif
|
||||
#ifdef IFM_10G_T
|
||||
case(IFM_10G_T):
|
||||
#endif
|
||||
return 10000;
|
||||
#endif
|
||||
#if defined(IFM_2500_SX)
|
||||
#ifdef IFM_2500_SX
|
||||
case(IFM_2500_SX):
|
||||
#endif
|
||||
return 2500;
|
||||
#endif // any 2.5GBit stuff...
|
||||
// We don't know what it is
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
#ifdef IFM_TOKEN
|
||||
case IFM_TOKEN:
|
||||
switch(IFM_SUBTYPE(ifm_active)) {
|
||||
case IFM_TOK_STP4: // Shielded twisted pair 4m - DB9
|
||||
case IFM_TOK_UTP4: // Unshielded twisted pair 4m - RJ45
|
||||
return 4;
|
||||
case IFM_TOK_STP16: // Shielded twisted pair 16m - DB9
|
||||
case IFM_TOK_UTP16: // Unshielded twisted pair 16m - RJ45
|
||||
return 16;
|
||||
#if defined(IFM_TOK_STP100) || defined(IFM_TOK_UTP100)
|
||||
#ifdef IFM_TOK_STP100
|
||||
case IFM_TOK_STP100: // Shielded twisted pair 100m - DB9
|
||||
#endif
|
||||
#ifdef IFM_TOK_UTP100
|
||||
case IFM_TOK_UTP100: // Unshielded twisted pair 100m - RJ45
|
||||
#endif
|
||||
return 100;
|
||||
#endif
|
||||
// We don't know what it is
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef IFM_FDDI
|
||||
case IFM_FDDI:
|
||||
switch(IFM_SUBTYPE(ifm_active)) {
|
||||
// We don't know what it is
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case IFM_IEEE80211:
|
||||
switch(IFM_SUBTYPE(ifm_active)) {
|
||||
case IFM_IEEE80211_FH1: // Frequency Hopping 1Mbps
|
||||
case IFM_IEEE80211_DS1: // Direct Sequence 1Mbps
|
||||
return 1;
|
||||
case IFM_IEEE80211_FH2: // Frequency Hopping 2Mbps
|
||||
case IFM_IEEE80211_DS2: // Direct Sequence 2Mbps
|
||||
return 2;
|
||||
case IFM_IEEE80211_DS5: // Direct Sequence 5Mbps
|
||||
return 5;
|
||||
case IFM_IEEE80211_DS11: // Direct Sequence 11Mbps
|
||||
return 11;
|
||||
case IFM_IEEE80211_DS22: // Direct Sequence 22Mbps
|
||||
return 22;
|
||||
// We don't know what it is
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return stats about a particular network interface.
|
||||
* References:
|
||||
* http://www.i-scream.org/libstatgrab/
|
||||
*/
|
||||
static PyObject *
|
||||
psutil_net_if_duplex_speed(PyObject *self, PyObject *args) {
|
||||
char *nic_name;
|
||||
int sock = 0;
|
||||
int ret;
|
||||
int duplex;
|
||||
int speed;
|
||||
struct ifreq ifr;
|
||||
struct ifmediareq ifmed;
|
||||
|
||||
if (! PyArg_ParseTuple(args, "s", &nic_name))
|
||||
return NULL;
|
||||
|
||||
sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sock == -1)
|
||||
goto error;
|
||||
strncpy(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name));
|
||||
|
||||
// speed / duplex
|
||||
memset(&ifmed, 0, sizeof(struct ifmediareq));
|
||||
strlcpy(ifmed.ifm_name, nic_name, sizeof(ifmed.ifm_name));
|
||||
ret = ioctl(sock, SIOCGIFMEDIA, (caddr_t)&ifmed);
|
||||
if (ret == -1) {
|
||||
speed = 0;
|
||||
duplex = 0;
|
||||
}
|
||||
else {
|
||||
speed = psutil_get_nic_speed(ifmed.ifm_active);
|
||||
if ((ifmed.ifm_active | IFM_FDX) == ifmed.ifm_active)
|
||||
duplex = 2;
|
||||
else if ((ifmed.ifm_active | IFM_HDX) == ifmed.ifm_active)
|
||||
duplex = 1;
|
||||
else
|
||||
duplex = 0;
|
||||
}
|
||||
|
||||
close(sock);
|
||||
return Py_BuildValue("[ii]", duplex, speed);
|
||||
|
||||
error:
|
||||
if (sock != 0)
|
||||
close(sock);
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
}
|
||||
#endif // net_if_stats() OSX/BSD implementation
|
||||
|
||||
|
||||
/*
|
||||
* define the psutil C module methods and initialize the module.
|
||||
*/
|
||||
static PyMethodDef
|
||||
PsutilMethods[] = {
|
||||
{"getpriority", psutil_posix_getpriority, METH_VARARGS,
|
||||
"Return process priority"},
|
||||
{"setpriority", psutil_posix_setpriority, METH_VARARGS,
|
||||
"Set process priority"},
|
||||
{"net_if_addrs", psutil_net_if_addrs, METH_VARARGS,
|
||||
"Retrieve NICs information"},
|
||||
{"net_if_mtu", psutil_net_if_mtu, METH_VARARGS,
|
||||
"Retrieve NIC MTU"},
|
||||
{"net_if_flags", psutil_net_if_flags, METH_VARARGS,
|
||||
"Retrieve NIC flags"},
|
||||
#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX)
|
||||
{"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS,
|
||||
"Return NIC stats."},
|
||||
#endif
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
struct module_state {
|
||||
PyObject *error;
|
||||
};
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define GETSTATE(m) ((struct module_state*)PyModule_GetState(m))
|
||||
#else
|
||||
#define GETSTATE(m) (&_state)
|
||||
#endif
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
|
||||
static int
|
||||
psutil_posix_traverse(PyObject *m, visitproc visit, void *arg) {
|
||||
Py_VISIT(GETSTATE(m)->error);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
psutil_posix_clear(PyObject *m) {
|
||||
Py_CLEAR(GETSTATE(m)->error);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct PyModuleDef moduledef = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"psutil_posix",
|
||||
NULL,
|
||||
sizeof(struct module_state),
|
||||
PsutilMethods,
|
||||
NULL,
|
||||
psutil_posix_traverse,
|
||||
psutil_posix_clear,
|
||||
NULL
|
||||
};
|
||||
|
||||
#define INITERROR return NULL
|
||||
|
||||
PyMODINIT_FUNC PyInit__psutil_posix(void)
|
||||
|
||||
#else
|
||||
#define INITERROR return
|
||||
|
||||
void init_psutil_posix(void)
|
||||
#endif
|
||||
{
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyObject *module = PyModule_Create(&moduledef);
|
||||
#else
|
||||
PyObject *module = Py_InitModule("_psutil_posix", PsutilMethods);
|
||||
#endif
|
||||
|
||||
#if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) || defined(PSUTIL_SUNOS)
|
||||
PyModule_AddIntConstant(module, "AF_LINK", AF_LINK);
|
||||
#endif
|
||||
|
||||
if (module == NULL)
|
||||
INITERROR;
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
return module;
|
||||
#endif
|
||||
}
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
Vendored
-1539
File diff suppressed because it is too large
Load Diff
-3775
File diff suppressed because it is too large
Load Diff
Vendored
-965
@@ -1,965 +0,0 @@
|
||||
# Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
"""Windows platform implementation."""
|
||||
|
||||
import contextlib
|
||||
import errno
|
||||
import functools
|
||||
import os
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
from . import _common
|
||||
try:
|
||||
from . import _psutil_windows as cext
|
||||
except ImportError as err:
|
||||
if str(err).lower().startswith("dll load failed") and \
|
||||
sys.getwindowsversion()[0] < 6:
|
||||
# We may get here if:
|
||||
# 1) we are on an old Windows version
|
||||
# 2) psutil was installed via pip + wheel
|
||||
# See: https://github.com/giampaolo/psutil/issues/811
|
||||
# It must be noted that psutil can still (kind of) work
|
||||
# on outdated systems if compiled / installed from sources,
|
||||
# but if we get here it means this this was a wheel (or exe).
|
||||
msg = "this Windows version is too old (< Windows Vista); "
|
||||
msg += "psutil 3.4.2 is the latest version which supports Windows "
|
||||
msg += "2000, XP and 2003 server"
|
||||
raise RuntimeError(msg)
|
||||
else:
|
||||
raise
|
||||
|
||||
from ._common import conn_tmap
|
||||
from ._common import isfile_strict
|
||||
from ._common import parse_environ_block
|
||||
from ._common import sockfam_to_enum
|
||||
from ._common import socktype_to_enum
|
||||
from ._common import usage_percent
|
||||
from ._common import memoize_when_activated
|
||||
from ._compat import long
|
||||
from ._compat import lru_cache
|
||||
from ._compat import PY3
|
||||
from ._compat import xrange
|
||||
from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS
|
||||
from ._psutil_windows import BELOW_NORMAL_PRIORITY_CLASS
|
||||
from ._psutil_windows import HIGH_PRIORITY_CLASS
|
||||
from ._psutil_windows import IDLE_PRIORITY_CLASS
|
||||
from ._psutil_windows import NORMAL_PRIORITY_CLASS
|
||||
from ._psutil_windows import REALTIME_PRIORITY_CLASS
|
||||
|
||||
if sys.version_info >= (3, 4):
|
||||
import enum
|
||||
else:
|
||||
enum = None
|
||||
|
||||
# process priority constants, import from __init__.py:
|
||||
# http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx
|
||||
__extra__all__ = [
|
||||
"win_service_iter", "win_service_get",
|
||||
"ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS",
|
||||
"HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS",
|
||||
"NORMAL_PRIORITY_CLASS", "REALTIME_PRIORITY_CLASS",
|
||||
"CONN_DELETE_TCB",
|
||||
"AF_LINK",
|
||||
]
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- globals
|
||||
# =====================================================================
|
||||
|
||||
|
||||
CONN_DELETE_TCB = "DELETE_TCB"
|
||||
WAIT_TIMEOUT = 0x00000102 # 258 in decimal
|
||||
ACCESS_DENIED_SET = frozenset([errno.EPERM, errno.EACCES,
|
||||
cext.ERROR_ACCESS_DENIED])
|
||||
|
||||
if enum is None:
|
||||
AF_LINK = -1
|
||||
else:
|
||||
AddressFamily = enum.IntEnum('AddressFamily', {'AF_LINK': -1})
|
||||
AF_LINK = AddressFamily.AF_LINK
|
||||
|
||||
TCP_STATUSES = {
|
||||
cext.MIB_TCP_STATE_ESTAB: _common.CONN_ESTABLISHED,
|
||||
cext.MIB_TCP_STATE_SYN_SENT: _common.CONN_SYN_SENT,
|
||||
cext.MIB_TCP_STATE_SYN_RCVD: _common.CONN_SYN_RECV,
|
||||
cext.MIB_TCP_STATE_FIN_WAIT1: _common.CONN_FIN_WAIT1,
|
||||
cext.MIB_TCP_STATE_FIN_WAIT2: _common.CONN_FIN_WAIT2,
|
||||
cext.MIB_TCP_STATE_TIME_WAIT: _common.CONN_TIME_WAIT,
|
||||
cext.MIB_TCP_STATE_CLOSED: _common.CONN_CLOSE,
|
||||
cext.MIB_TCP_STATE_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
|
||||
cext.MIB_TCP_STATE_LAST_ACK: _common.CONN_LAST_ACK,
|
||||
cext.MIB_TCP_STATE_LISTEN: _common.CONN_LISTEN,
|
||||
cext.MIB_TCP_STATE_CLOSING: _common.CONN_CLOSING,
|
||||
cext.MIB_TCP_STATE_DELETE_TCB: CONN_DELETE_TCB,
|
||||
cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
|
||||
}
|
||||
|
||||
if enum is not None:
|
||||
class Priority(enum.IntEnum):
|
||||
ABOVE_NORMAL_PRIORITY_CLASS = ABOVE_NORMAL_PRIORITY_CLASS
|
||||
BELOW_NORMAL_PRIORITY_CLASS = BELOW_NORMAL_PRIORITY_CLASS
|
||||
HIGH_PRIORITY_CLASS = HIGH_PRIORITY_CLASS
|
||||
IDLE_PRIORITY_CLASS = IDLE_PRIORITY_CLASS
|
||||
NORMAL_PRIORITY_CLASS = NORMAL_PRIORITY_CLASS
|
||||
REALTIME_PRIORITY_CLASS = REALTIME_PRIORITY_CLASS
|
||||
|
||||
globals().update(Priority.__members__)
|
||||
|
||||
pinfo_map = dict(
|
||||
num_handles=0,
|
||||
ctx_switches=1,
|
||||
user_time=2,
|
||||
kernel_time=3,
|
||||
create_time=4,
|
||||
num_threads=5,
|
||||
io_rcount=6,
|
||||
io_wcount=7,
|
||||
io_rbytes=8,
|
||||
io_wbytes=9,
|
||||
io_count_others=10,
|
||||
io_bytes_others=11,
|
||||
num_page_faults=12,
|
||||
peak_wset=13,
|
||||
wset=14,
|
||||
peak_paged_pool=15,
|
||||
paged_pool=16,
|
||||
peak_non_paged_pool=17,
|
||||
non_paged_pool=18,
|
||||
pagefile=19,
|
||||
peak_pagefile=20,
|
||||
mem_private=21,
|
||||
)
|
||||
|
||||
# these get overwritten on "import psutil" from the __init__.py file
|
||||
NoSuchProcess = None
|
||||
AccessDenied = None
|
||||
TimeoutExpired = None
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- named tuples
|
||||
# =====================================================================
|
||||
|
||||
|
||||
# psutil.cpu_times()
|
||||
scputimes = namedtuple('scputimes',
|
||||
['user', 'system', 'idle', 'interrupt', 'dpc'])
|
||||
# psutil.virtual_memory()
|
||||
svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free'])
|
||||
# psutil.Process.memory_info()
|
||||
pmem = namedtuple(
|
||||
'pmem', ['rss', 'vms',
|
||||
'num_page_faults', 'peak_wset', 'wset', 'peak_paged_pool',
|
||||
'paged_pool', 'peak_nonpaged_pool', 'nonpaged_pool',
|
||||
'pagefile', 'peak_pagefile', 'private'])
|
||||
# psutil.Process.memory_full_info()
|
||||
pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', ))
|
||||
# psutil.Process.memory_maps(grouped=True)
|
||||
pmmap_grouped = namedtuple('pmmap_grouped', ['path', 'rss'])
|
||||
# psutil.Process.memory_maps(grouped=False)
|
||||
pmmap_ext = namedtuple(
|
||||
'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields))
|
||||
# psutil.Process.io_counters()
|
||||
pio = namedtuple('pio', ['read_count', 'write_count',
|
||||
'read_bytes', 'write_bytes',
|
||||
'other_count', 'other_bytes'])
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- utils
|
||||
# =====================================================================
|
||||
|
||||
|
||||
@lru_cache(maxsize=512)
|
||||
def convert_dos_path(s):
|
||||
"""Convert paths using native DOS format like:
|
||||
"\Device\HarddiskVolume1\Windows\systemew\file.txt"
|
||||
into:
|
||||
"C:\Windows\systemew\file.txt"
|
||||
"""
|
||||
if PY3 and not isinstance(s, str):
|
||||
s = s.decode('utf8')
|
||||
rawdrive = '\\'.join(s.split('\\')[:3])
|
||||
driveletter = cext.win32_QueryDosDevice(rawdrive)
|
||||
return os.path.join(driveletter, s[len(rawdrive):])
|
||||
|
||||
|
||||
def py2_strencode(s, encoding=sys.getfilesystemencoding()):
|
||||
"""Encode a string in the given encoding. Falls back on returning
|
||||
the string as is if it can't be encoded.
|
||||
"""
|
||||
if PY3 or isinstance(s, str):
|
||||
return s
|
||||
else:
|
||||
try:
|
||||
return s.encode(encoding)
|
||||
except UnicodeEncodeError:
|
||||
# Filesystem codec failed, return the plain unicode
|
||||
# string (this should never happen).
|
||||
return s
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- memory
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def virtual_memory():
|
||||
"""System virtual memory as a namedtuple."""
|
||||
mem = cext.virtual_mem()
|
||||
totphys, availphys, totpagef, availpagef, totvirt, freevirt = mem
|
||||
#
|
||||
total = totphys
|
||||
avail = availphys
|
||||
free = availphys
|
||||
used = total - avail
|
||||
percent = usage_percent((total - avail), total, _round=1)
|
||||
return svmem(total, avail, percent, used, free)
|
||||
|
||||
|
||||
def swap_memory():
|
||||
"""Swap system memory as a (total, used, free, sin, sout) tuple."""
|
||||
mem = cext.virtual_mem()
|
||||
total = mem[2]
|
||||
free = mem[3]
|
||||
used = total - free
|
||||
percent = usage_percent(used, total, _round=1)
|
||||
return _common.sswap(total, used, free, percent, 0, 0)
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- disk
|
||||
# =====================================================================
|
||||
|
||||
|
||||
disk_io_counters = cext.disk_io_counters
|
||||
|
||||
|
||||
def disk_usage(path):
|
||||
"""Return disk usage associated with path."""
|
||||
try:
|
||||
total, free = cext.disk_usage(path)
|
||||
except WindowsError:
|
||||
if not os.path.exists(path):
|
||||
msg = "No such file or directory: '%s'" % path
|
||||
raise OSError(errno.ENOENT, msg)
|
||||
raise
|
||||
used = total - free
|
||||
percent = usage_percent(used, total, _round=1)
|
||||
return _common.sdiskusage(total, used, free, percent)
|
||||
|
||||
|
||||
def disk_partitions(all):
|
||||
"""Return disk partitions."""
|
||||
rawlist = cext.disk_partitions(all)
|
||||
return [_common.sdiskpart(*x) for x in rawlist]
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- CPU
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def cpu_times():
|
||||
"""Return system CPU times as a named tuple."""
|
||||
user, system, idle = cext.cpu_times()
|
||||
# Internally, GetSystemTimes() is used, and it doesn't return
|
||||
# interrupt and dpc times. cext.per_cpu_times() does, so we
|
||||
# rely on it to get those only.
|
||||
percpu_summed = scputimes(*[sum(n) for n in zip(*cext.per_cpu_times())])
|
||||
return scputimes(user, system, idle,
|
||||
percpu_summed.interrupt, percpu_summed.dpc)
|
||||
|
||||
|
||||
def per_cpu_times():
|
||||
"""Return system per-CPU times as a list of named tuples."""
|
||||
ret = []
|
||||
for user, system, idle, interrupt, dpc in cext.per_cpu_times():
|
||||
item = scputimes(user, system, idle, interrupt, dpc)
|
||||
ret.append(item)
|
||||
return ret
|
||||
|
||||
|
||||
def cpu_count_logical():
|
||||
"""Return the number of logical CPUs in the system."""
|
||||
return cext.cpu_count_logical()
|
||||
|
||||
|
||||
def cpu_count_physical():
|
||||
"""Return the number of physical CPUs in the system."""
|
||||
return cext.cpu_count_phys()
|
||||
|
||||
|
||||
def cpu_stats():
|
||||
"""Return CPU statistics."""
|
||||
ctx_switches, interrupts, dpcs, syscalls = cext.cpu_stats()
|
||||
soft_interrupts = 0
|
||||
return _common.scpustats(ctx_switches, interrupts, soft_interrupts,
|
||||
syscalls)
|
||||
|
||||
|
||||
def cpu_freq():
|
||||
"""Return CPU frequency.
|
||||
On Windows per-cpu frequency is not supported.
|
||||
"""
|
||||
curr, max_ = cext.cpu_freq()
|
||||
min_ = 0.0
|
||||
return [_common.scpufreq(float(curr), min_, float(max_))]
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- network
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def net_connections(kind, _pid=-1):
|
||||
"""Return socket connections. If pid == -1 return system-wide
|
||||
connections (as opposed to connections opened by one process only).
|
||||
"""
|
||||
if kind not in conn_tmap:
|
||||
raise ValueError("invalid %r kind argument; choose between %s"
|
||||
% (kind, ', '.join([repr(x) for x in conn_tmap])))
|
||||
families, types = conn_tmap[kind]
|
||||
rawlist = cext.net_connections(_pid, families, types)
|
||||
ret = set()
|
||||
for item in rawlist:
|
||||
fd, fam, type, laddr, raddr, status, pid = item
|
||||
status = TCP_STATUSES[status]
|
||||
fam = sockfam_to_enum(fam)
|
||||
type = socktype_to_enum(type)
|
||||
if _pid == -1:
|
||||
nt = _common.sconn(fd, fam, type, laddr, raddr, status, pid)
|
||||
else:
|
||||
nt = _common.pconn(fd, fam, type, laddr, raddr, status)
|
||||
ret.add(nt)
|
||||
return list(ret)
|
||||
|
||||
|
||||
def net_if_stats():
|
||||
"""Get NIC stats (isup, duplex, speed, mtu)."""
|
||||
ret = cext.net_if_stats()
|
||||
for name, items in ret.items():
|
||||
name = py2_strencode(name)
|
||||
isup, duplex, speed, mtu = items
|
||||
if hasattr(_common, 'NicDuplex'):
|
||||
duplex = _common.NicDuplex(duplex)
|
||||
ret[name] = _common.snicstats(isup, duplex, speed, mtu)
|
||||
return ret
|
||||
|
||||
|
||||
def net_io_counters():
|
||||
"""Return network I/O statistics for every network interface
|
||||
installed on the system as a dict of raw tuples.
|
||||
"""
|
||||
ret = cext.net_io_counters()
|
||||
return dict([(py2_strencode(k), v) for k, v in ret.items()])
|
||||
|
||||
|
||||
def net_if_addrs():
|
||||
"""Return the addresses associated to each NIC."""
|
||||
ret = []
|
||||
for items in cext.net_if_addrs():
|
||||
items = list(items)
|
||||
items[0] = py2_strencode(items[0])
|
||||
ret.append(items)
|
||||
return ret
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- sensors
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def sensors_battery():
|
||||
"""Return battery information."""
|
||||
# For constants meaning see:
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/
|
||||
# aa373232(v=vs.85).aspx
|
||||
acline_status, flags, percent, secsleft = cext.sensors_battery()
|
||||
power_plugged = acline_status == 1
|
||||
no_battery = bool(flags & 128)
|
||||
charging = bool(flags & 8)
|
||||
|
||||
if no_battery:
|
||||
return None
|
||||
if power_plugged or charging:
|
||||
secsleft = _common.POWER_TIME_UNLIMITED
|
||||
elif secsleft == -1:
|
||||
secsleft = _common.POWER_TIME_UNKNOWN
|
||||
|
||||
return _common.sbattery(percent, secsleft, power_plugged)
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- other system functions
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def boot_time():
|
||||
"""The system boot time expressed in seconds since the epoch."""
|
||||
return cext.boot_time()
|
||||
|
||||
|
||||
def users():
|
||||
"""Return currently connected users as a list of namedtuples."""
|
||||
retlist = []
|
||||
rawlist = cext.users()
|
||||
for item in rawlist:
|
||||
user, hostname, tstamp = item
|
||||
user = py2_strencode(user)
|
||||
nt = _common.suser(user, None, hostname, tstamp)
|
||||
retlist.append(nt)
|
||||
return retlist
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- Windows services
|
||||
# =====================================================================
|
||||
|
||||
|
||||
def win_service_iter():
|
||||
"""Return a list of WindowsService instances."""
|
||||
for name, display_name in cext.winservice_enumerate():
|
||||
yield WindowsService(name, display_name)
|
||||
|
||||
|
||||
def win_service_get(name):
|
||||
"""Open a Windows service and return it as a WindowsService instance."""
|
||||
service = WindowsService(name, None)
|
||||
service._display_name = service._query_config()['display_name']
|
||||
return service
|
||||
|
||||
|
||||
class WindowsService(object):
|
||||
"""Represents an installed Windows service."""
|
||||
|
||||
def __init__(self, name, display_name):
|
||||
self._name = name
|
||||
self._display_name = display_name
|
||||
|
||||
def __str__(self):
|
||||
details = "(name=%r, display_name=%r)" % (
|
||||
self._name, self._display_name)
|
||||
return "%s%s" % (self.__class__.__name__, details)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s at %s>" % (self.__str__(), id(self))
|
||||
|
||||
def __eq__(self, other):
|
||||
# Test for equality with another WindosService object based
|
||||
# on name.
|
||||
if not isinstance(other, WindowsService):
|
||||
return NotImplemented
|
||||
return self._name == other._name
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def _query_config(self):
|
||||
with self._wrap_exceptions():
|
||||
display_name, binpath, username, start_type = \
|
||||
cext.winservice_query_config(self._name)
|
||||
# XXX - update _self.display_name?
|
||||
return dict(
|
||||
display_name=display_name,
|
||||
binpath=binpath,
|
||||
username=username,
|
||||
start_type=start_type)
|
||||
|
||||
def _query_status(self):
|
||||
with self._wrap_exceptions():
|
||||
status, pid = cext.winservice_query_status(self._name)
|
||||
if pid == 0:
|
||||
pid = None
|
||||
return dict(status=status, pid=pid)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _wrap_exceptions(self):
|
||||
"""Ctx manager which translates bare OSError and WindowsError
|
||||
exceptions into NoSuchProcess and AccessDenied.
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
except WindowsError as err:
|
||||
NO_SUCH_SERVICE_SET = (cext.ERROR_INVALID_NAME,
|
||||
cext.ERROR_SERVICE_DOES_NOT_EXIST)
|
||||
if err.errno in ACCESS_DENIED_SET:
|
||||
raise AccessDenied(
|
||||
pid=None, name=self._name,
|
||||
msg="service %r is not querable (not enough privileges)" %
|
||||
self._name)
|
||||
elif err.errno in NO_SUCH_SERVICE_SET or \
|
||||
err.winerror in NO_SUCH_SERVICE_SET:
|
||||
raise NoSuchProcess(
|
||||
pid=None, name=self._name,
|
||||
msg="service %r does not exist)" % self._name)
|
||||
else:
|
||||
raise
|
||||
|
||||
# config query
|
||||
|
||||
def name(self):
|
||||
"""The service name. This string is how a service is referenced
|
||||
and can be passed to win_service_get() to get a new
|
||||
WindowsService instance.
|
||||
"""
|
||||
return self._name
|
||||
|
||||
def display_name(self):
|
||||
"""The service display name. The value is cached when this class
|
||||
is instantiated.
|
||||
"""
|
||||
return self._display_name
|
||||
|
||||
def binpath(self):
|
||||
"""The fully qualified path to the service binary/exe file as
|
||||
a string, including command line arguments.
|
||||
"""
|
||||
return self._query_config()['binpath']
|
||||
|
||||
def username(self):
|
||||
"""The name of the user that owns this service."""
|
||||
return self._query_config()['username']
|
||||
|
||||
def start_type(self):
|
||||
"""A string which can either be "automatic", "manual" or
|
||||
"disabled".
|
||||
"""
|
||||
return self._query_config()['start_type']
|
||||
|
||||
# status query
|
||||
|
||||
def pid(self):
|
||||
"""The process PID, if any, else None. This can be passed
|
||||
to Process class to control the service's process.
|
||||
"""
|
||||
return self._query_status()['pid']
|
||||
|
||||
def status(self):
|
||||
"""Service status as a string."""
|
||||
return self._query_status()['status']
|
||||
|
||||
def description(self):
|
||||
"""Service long description."""
|
||||
return cext.winservice_query_descr(self.name())
|
||||
|
||||
# utils
|
||||
|
||||
def as_dict(self):
|
||||
"""Utility method retrieving all the information above as a
|
||||
dictionary.
|
||||
"""
|
||||
d = self._query_config()
|
||||
d.update(self._query_status())
|
||||
d['name'] = self.name()
|
||||
d['display_name'] = self.display_name()
|
||||
d['description'] = self.description()
|
||||
return d
|
||||
|
||||
# actions
|
||||
# XXX: the necessary C bindings for start() and stop() are
|
||||
# implemented but for now I prefer not to expose them.
|
||||
# I may change my mind in the future. Reasons:
|
||||
# - they require Administrator privileges
|
||||
# - can't implement a timeout for stop() (unless by using a thread,
|
||||
# which sucks)
|
||||
# - would require adding ServiceAlreadyStarted and
|
||||
# ServiceAlreadyStopped exceptions, adding two new APIs.
|
||||
# - we might also want to have modify(), which would basically mean
|
||||
# rewriting win32serviceutil.ChangeServiceConfig, which involves a
|
||||
# lot of stuff (and API constants which would pollute the API), see:
|
||||
# http://pyxr.sourceforge.net/PyXR/c/python24/lib/site-packages/
|
||||
# win32/lib/win32serviceutil.py.html#0175
|
||||
# - psutil is typically about "read only" monitoring stuff;
|
||||
# win_service_* APIs should only be used to retrieve a service and
|
||||
# check whether it's running
|
||||
|
||||
# def start(self, timeout=None):
|
||||
# with self._wrap_exceptions():
|
||||
# cext.winservice_start(self.name())
|
||||
# if timeout:
|
||||
# giveup_at = time.time() + timeout
|
||||
# while True:
|
||||
# if self.status() == "running":
|
||||
# return
|
||||
# else:
|
||||
# if time.time() > giveup_at:
|
||||
# raise TimeoutExpired(timeout)
|
||||
# else:
|
||||
# time.sleep(.1)
|
||||
|
||||
# def stop(self):
|
||||
# # Note: timeout is not implemented because it's just not
|
||||
# # possible, see:
|
||||
# # http://stackoverflow.com/questions/11973228/
|
||||
# with self._wrap_exceptions():
|
||||
# return cext.winservice_stop(self.name())
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# --- processes
|
||||
# =====================================================================
|
||||
|
||||
|
||||
pids = cext.pids
|
||||
pid_exists = cext.pid_exists
|
||||
ppid_map = cext.ppid_map # used internally by Process.children()
|
||||
|
||||
|
||||
def wrap_exceptions(fun):
|
||||
"""Decorator which translates bare OSError and WindowsError
|
||||
exceptions into NoSuchProcess and AccessDenied.
|
||||
"""
|
||||
@functools.wraps(fun)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
try:
|
||||
return fun(self, *args, **kwargs)
|
||||
except OSError as err:
|
||||
if err.errno in ACCESS_DENIED_SET:
|
||||
raise AccessDenied(self.pid, self._name)
|
||||
if err.errno == errno.ESRCH:
|
||||
raise NoSuchProcess(self.pid, self._name)
|
||||
raise
|
||||
return wrapper
|
||||
|
||||
|
||||
class Process(object):
|
||||
"""Wrapper class around underlying C implementation."""
|
||||
|
||||
__slots__ = ["pid", "_name", "_ppid"]
|
||||
|
||||
def __init__(self, pid):
|
||||
self.pid = pid
|
||||
self._name = None
|
||||
self._ppid = None
|
||||
|
||||
# --- oneshot() stuff
|
||||
|
||||
def oneshot_enter(self):
|
||||
self.oneshot_info.cache_activate()
|
||||
|
||||
def oneshot_exit(self):
|
||||
self.oneshot_info.cache_deactivate()
|
||||
|
||||
@memoize_when_activated
|
||||
def oneshot_info(self):
|
||||
"""Return multiple information about this process as a
|
||||
raw tuple.
|
||||
"""
|
||||
ret = cext.proc_info(self.pid)
|
||||
assert len(ret) == len(pinfo_map)
|
||||
return ret
|
||||
|
||||
@wrap_exceptions
|
||||
def name(self):
|
||||
"""Return process name, which on Windows is always the final
|
||||
part of the executable.
|
||||
"""
|
||||
# This is how PIDs 0 and 4 are always represented in taskmgr
|
||||
# and process-hacker.
|
||||
if self.pid == 0:
|
||||
return "System Idle Process"
|
||||
elif self.pid == 4:
|
||||
return "System"
|
||||
else:
|
||||
try:
|
||||
# Note: this will fail with AD for most PIDs owned
|
||||
# by another user but it's faster.
|
||||
return py2_strencode(os.path.basename(self.exe()))
|
||||
except AccessDenied:
|
||||
return py2_strencode(cext.proc_name(self.pid))
|
||||
|
||||
@wrap_exceptions
|
||||
def exe(self):
|
||||
# Note: os.path.exists(path) may return False even if the file
|
||||
# is there, see:
|
||||
# http://stackoverflow.com/questions/3112546/os-path-exists-lies
|
||||
|
||||
# see https://github.com/giampaolo/psutil/issues/414
|
||||
# see https://github.com/giampaolo/psutil/issues/528
|
||||
if self.pid in (0, 4):
|
||||
raise AccessDenied(self.pid, self._name)
|
||||
return py2_strencode(convert_dos_path(cext.proc_exe(self.pid)))
|
||||
|
||||
@wrap_exceptions
|
||||
def cmdline(self):
|
||||
ret = cext.proc_cmdline(self.pid)
|
||||
if PY3:
|
||||
return ret
|
||||
else:
|
||||
return [py2_strencode(s) for s in ret]
|
||||
|
||||
@wrap_exceptions
|
||||
def environ(self):
|
||||
return parse_environ_block(cext.proc_environ(self.pid))
|
||||
|
||||
def ppid(self):
|
||||
try:
|
||||
return ppid_map()[self.pid]
|
||||
except KeyError:
|
||||
raise NoSuchProcess(self.pid, self._name)
|
||||
|
||||
def _get_raw_meminfo(self):
|
||||
try:
|
||||
return cext.proc_memory_info(self.pid)
|
||||
except OSError as err:
|
||||
if err.errno in ACCESS_DENIED_SET:
|
||||
# TODO: the C ext can probably be refactored in order
|
||||
# to get this from cext.proc_info()
|
||||
info = self.oneshot_info()
|
||||
return (
|
||||
info[pinfo_map['num_page_faults']],
|
||||
info[pinfo_map['peak_wset']],
|
||||
info[pinfo_map['wset']],
|
||||
info[pinfo_map['peak_paged_pool']],
|
||||
info[pinfo_map['paged_pool']],
|
||||
info[pinfo_map['peak_non_paged_pool']],
|
||||
info[pinfo_map['non_paged_pool']],
|
||||
info[pinfo_map['pagefile']],
|
||||
info[pinfo_map['peak_pagefile']],
|
||||
info[pinfo_map['mem_private']],
|
||||
)
|
||||
raise
|
||||
|
||||
@wrap_exceptions
|
||||
def memory_info(self):
|
||||
# on Windows RSS == WorkingSetSize and VSM == PagefileUsage.
|
||||
# Underlying C function returns fields of PROCESS_MEMORY_COUNTERS
|
||||
# struct.
|
||||
t = self._get_raw_meminfo()
|
||||
rss = t[2] # wset
|
||||
vms = t[7] # pagefile
|
||||
return pmem(*(rss, vms, ) + t)
|
||||
|
||||
@wrap_exceptions
|
||||
def memory_full_info(self):
|
||||
basic_mem = self.memory_info()
|
||||
uss = cext.proc_memory_uss(self.pid)
|
||||
return pfullmem(*basic_mem + (uss, ))
|
||||
|
||||
def memory_maps(self):
|
||||
try:
|
||||
raw = cext.proc_memory_maps(self.pid)
|
||||
except OSError as err:
|
||||
# XXX - can't use wrap_exceptions decorator as we're
|
||||
# returning a generator; probably needs refactoring.
|
||||
if err.errno in ACCESS_DENIED_SET:
|
||||
raise AccessDenied(self.pid, self._name)
|
||||
if err.errno == errno.ESRCH:
|
||||
raise NoSuchProcess(self.pid, self._name)
|
||||
raise
|
||||
else:
|
||||
for addr, perm, path, rss in raw:
|
||||
path = convert_dos_path(path)
|
||||
addr = hex(addr)
|
||||
yield (addr, perm, path, rss)
|
||||
|
||||
@wrap_exceptions
|
||||
def kill(self):
|
||||
return cext.proc_kill(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def send_signal(self, sig):
|
||||
os.kill(self.pid, sig)
|
||||
|
||||
@wrap_exceptions
|
||||
def wait(self, timeout=None):
|
||||
if timeout is None:
|
||||
cext_timeout = cext.INFINITE
|
||||
else:
|
||||
# WaitForSingleObject() expects time in milliseconds
|
||||
cext_timeout = int(timeout * 1000)
|
||||
ret = cext.proc_wait(self.pid, cext_timeout)
|
||||
if ret == WAIT_TIMEOUT:
|
||||
raise TimeoutExpired(timeout, self.pid, self._name)
|
||||
return ret
|
||||
|
||||
@wrap_exceptions
|
||||
def username(self):
|
||||
if self.pid in (0, 4):
|
||||
return 'NT AUTHORITY\\SYSTEM'
|
||||
return cext.proc_username(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def create_time(self):
|
||||
# special case for kernel process PIDs; return system boot time
|
||||
if self.pid in (0, 4):
|
||||
return boot_time()
|
||||
try:
|
||||
return cext.proc_create_time(self.pid)
|
||||
except OSError as err:
|
||||
if err.errno in ACCESS_DENIED_SET:
|
||||
return self.oneshot_info()[pinfo_map['create_time']]
|
||||
raise
|
||||
|
||||
@wrap_exceptions
|
||||
def num_threads(self):
|
||||
return self.oneshot_info()[pinfo_map['num_threads']]
|
||||
|
||||
@wrap_exceptions
|
||||
def threads(self):
|
||||
rawlist = cext.proc_threads(self.pid)
|
||||
retlist = []
|
||||
for thread_id, utime, stime in rawlist:
|
||||
ntuple = _common.pthread(thread_id, utime, stime)
|
||||
retlist.append(ntuple)
|
||||
return retlist
|
||||
|
||||
@wrap_exceptions
|
||||
def cpu_times(self):
|
||||
try:
|
||||
user, system = cext.proc_cpu_times(self.pid)
|
||||
except OSError as err:
|
||||
if err.errno in ACCESS_DENIED_SET:
|
||||
info = self.oneshot_info()
|
||||
user = info[pinfo_map['user_time']]
|
||||
system = info[pinfo_map['kernel_time']]
|
||||
else:
|
||||
raise
|
||||
# Children user/system times are not retrievable (set to 0).
|
||||
return _common.pcputimes(user, system, 0, 0)
|
||||
|
||||
@wrap_exceptions
|
||||
def suspend(self):
|
||||
return cext.proc_suspend(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def resume(self):
|
||||
return cext.proc_resume(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def cwd(self):
|
||||
if self.pid in (0, 4):
|
||||
raise AccessDenied(self.pid, self._name)
|
||||
# return a normalized pathname since the native C function appends
|
||||
# "\\" at the and of the path
|
||||
path = cext.proc_cwd(self.pid)
|
||||
return py2_strencode(os.path.normpath(path))
|
||||
|
||||
@wrap_exceptions
|
||||
def open_files(self):
|
||||
if self.pid in (0, 4):
|
||||
return []
|
||||
ret = set()
|
||||
# Filenames come in in native format like:
|
||||
# "\Device\HarddiskVolume1\Windows\systemew\file.txt"
|
||||
# Convert the first part in the corresponding drive letter
|
||||
# (e.g. "C:\") by using Windows's QueryDosDevice()
|
||||
raw_file_names = cext.proc_open_files(self.pid)
|
||||
for _file in raw_file_names:
|
||||
_file = convert_dos_path(_file)
|
||||
if isfile_strict(_file):
|
||||
if not PY3:
|
||||
_file = py2_strencode(_file)
|
||||
ntuple = _common.popenfile(_file, -1)
|
||||
ret.add(ntuple)
|
||||
return list(ret)
|
||||
|
||||
@wrap_exceptions
|
||||
def connections(self, kind='inet'):
|
||||
return net_connections(kind, _pid=self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def nice_get(self):
|
||||
value = cext.proc_priority_get(self.pid)
|
||||
if enum is not None:
|
||||
value = Priority(value)
|
||||
return value
|
||||
|
||||
@wrap_exceptions
|
||||
def nice_set(self, value):
|
||||
return cext.proc_priority_set(self.pid, value)
|
||||
|
||||
# available on Windows >= Vista
|
||||
if hasattr(cext, "proc_io_priority_get"):
|
||||
@wrap_exceptions
|
||||
def ionice_get(self):
|
||||
return cext.proc_io_priority_get(self.pid)
|
||||
|
||||
@wrap_exceptions
|
||||
def ionice_set(self, value, _):
|
||||
if _:
|
||||
raise TypeError("set_proc_ionice() on Windows takes only "
|
||||
"1 argument (2 given)")
|
||||
if value not in (2, 1, 0):
|
||||
raise ValueError("value must be 2 (normal), 1 (low) or 0 "
|
||||
"(very low); got %r" % value)
|
||||
return cext.proc_io_priority_set(self.pid, value)
|
||||
|
||||
@wrap_exceptions
|
||||
def io_counters(self):
|
||||
try:
|
||||
ret = cext.proc_io_counters(self.pid)
|
||||
except OSError as err:
|
||||
if err.errno in ACCESS_DENIED_SET:
|
||||
info = self.oneshot_info()
|
||||
ret = (
|
||||
info[pinfo_map['io_rcount']],
|
||||
info[pinfo_map['io_wcount']],
|
||||
info[pinfo_map['io_rbytes']],
|
||||
info[pinfo_map['io_wbytes']],
|
||||
info[pinfo_map['io_count_others']],
|
||||
info[pinfo_map['io_bytes_others']],
|
||||
)
|
||||
else:
|
||||
raise
|
||||
return pio(*ret)
|
||||
|
||||
@wrap_exceptions
|
||||
def status(self):
|
||||
suspended = cext.proc_is_suspended(self.pid)
|
||||
if suspended:
|
||||
return _common.STATUS_STOPPED
|
||||
else:
|
||||
return _common.STATUS_RUNNING
|
||||
|
||||
@wrap_exceptions
|
||||
def cpu_affinity_get(self):
|
||||
def from_bitmask(x):
|
||||
return [i for i in xrange(64) if (1 << i) & x]
|
||||
bitmask = cext.proc_cpu_affinity_get(self.pid)
|
||||
return from_bitmask(bitmask)
|
||||
|
||||
@wrap_exceptions
|
||||
def cpu_affinity_set(self, value):
|
||||
def to_bitmask(l):
|
||||
if not l:
|
||||
raise ValueError("invalid argument %r" % l)
|
||||
out = 0
|
||||
for b in l:
|
||||
out |= 2 ** b
|
||||
return out
|
||||
|
||||
# SetProcessAffinityMask() states that ERROR_INVALID_PARAMETER
|
||||
# is returned for an invalid CPU but this seems not to be true,
|
||||
# therefore we check CPUs validy beforehand.
|
||||
allcpus = list(range(len(per_cpu_times())))
|
||||
for cpu in value:
|
||||
if cpu not in allcpus:
|
||||
if not isinstance(cpu, (int, long)):
|
||||
raise TypeError(
|
||||
"invalid CPU %r; an integer is required" % cpu)
|
||||
else:
|
||||
raise ValueError("invalid CPU %r" % cpu)
|
||||
|
||||
bitmask = to_bitmask(value)
|
||||
cext.proc_cpu_affinity_set(self.pid, bitmask)
|
||||
|
||||
@wrap_exceptions
|
||||
def num_handles(self):
|
||||
try:
|
||||
return cext.proc_num_handles(self.pid)
|
||||
except OSError as err:
|
||||
if err.errno in ACCESS_DENIED_SET:
|
||||
return self.oneshot_info()[pinfo_map['num_handles']]
|
||||
raise
|
||||
|
||||
@wrap_exceptions
|
||||
def num_ctx_switches(self):
|
||||
ctx_switches = self.oneshot_info()[pinfo_map['ctx_switches']]
|
||||
# only voluntary ctx switches are supported
|
||||
return _common.pctxsw(ctx_switches, 0)
|
||||
-1020
File diff suppressed because it is too large
Load Diff
-32
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
typedef struct kinfo_proc kinfo_proc;
|
||||
|
||||
int psutil_get_proc_list(struct kinfo_proc **procList, size_t *procCount);
|
||||
int psutil_kinfo_proc(const pid_t pid, struct kinfo_proc *proc);
|
||||
|
||||
//
|
||||
PyObject* psutil_cpu_count_phys(PyObject* self, PyObject* args);
|
||||
PyObject* psutil_disk_io_counters(PyObject* self, PyObject* args);
|
||||
PyObject* psutil_get_cmdline(long pid);
|
||||
PyObject* psutil_per_cpu_times(PyObject* self, PyObject* args);
|
||||
PyObject* psutil_proc_cpu_affinity_get(PyObject* self, PyObject* args);
|
||||
PyObject* psutil_proc_cpu_affinity_set(PyObject* self, PyObject* args);
|
||||
PyObject* psutil_proc_cwd(PyObject* self, PyObject* args);
|
||||
PyObject* psutil_proc_exe(PyObject* self, PyObject* args);
|
||||
PyObject* psutil_proc_memory_maps(PyObject* self, PyObject* args);
|
||||
PyObject* psutil_proc_num_fds(PyObject* self, PyObject* args);
|
||||
PyObject* psutil_proc_num_threads(PyObject* self, PyObject* args);
|
||||
PyObject* psutil_proc_threads(PyObject* self, PyObject* args);
|
||||
PyObject* psutil_swap_mem(PyObject* self, PyObject* args);
|
||||
PyObject* psutil_virtual_mem(PyObject* self, PyObject* args);
|
||||
PyObject* psutil_cpu_stats(PyObject* self, PyObject* args);
|
||||
#if defined(PSUTIL_FREEBSD)
|
||||
PyObject* psutil_sensors_battery(PyObject* self, PyObject* args);
|
||||
#endif
|
||||
-631
@@ -1,631 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Giampaolo Rodola'.
|
||||
* All rights reserved.
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/socketvar.h> // for struct xsocket
|
||||
#include <sys/un.h>
|
||||
#include <sys/unpcb.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <netinet/in.h> // for xinpcb struct
|
||||
#include <netinet/in_systm.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/in_pcb.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <netinet/tcp_timer.h>
|
||||
#include <netinet/tcp_var.h> // for struct xtcpcb
|
||||
#include <netinet/tcp_fsm.h> // for TCP connection states
|
||||
#include <arpa/inet.h> // for inet_ntop()
|
||||
#include <net/if_media.h>
|
||||
#include <libutil.h>
|
||||
|
||||
#include "../../_psutil_common.h"
|
||||
|
||||
|
||||
#define HASHSIZE 1009
|
||||
// a signaler for connections without an actual status
|
||||
static int PSUTIL_CONN_NONE = 128;
|
||||
static struct xfile *psutil_xfiles;
|
||||
static int psutil_nxfiles;
|
||||
|
||||
|
||||
// The tcplist fetching and walking is borrowed from netstat/inet.c.
|
||||
static char *
|
||||
psutil_fetch_tcplist(void) {
|
||||
char *buf;
|
||||
size_t len;
|
||||
|
||||
for (;;) {
|
||||
if (sysctlbyname("net.inet.tcp.pcblist", NULL, &len, NULL, 0) < 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
}
|
||||
buf = malloc(len);
|
||||
if (buf == NULL) {
|
||||
PyErr_NoMemory();
|
||||
return NULL;
|
||||
}
|
||||
if (sysctlbyname("net.inet.tcp.pcblist", buf, &len, NULL, 0) < 0) {
|
||||
free(buf);
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return NULL;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
psutil_sockaddr_port(int family, struct sockaddr_storage *ss) {
|
||||
struct sockaddr_in6 *sin6;
|
||||
struct sockaddr_in *sin;
|
||||
|
||||
if (family == AF_INET) {
|
||||
sin = (struct sockaddr_in *)ss;
|
||||
return (sin->sin_port);
|
||||
}
|
||||
else {
|
||||
sin6 = (struct sockaddr_in6 *)ss;
|
||||
return (sin6->sin6_port);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
psutil_sockaddr_addr(int family, struct sockaddr_storage *ss) {
|
||||
struct sockaddr_in6 *sin6;
|
||||
struct sockaddr_in *sin;
|
||||
|
||||
if (family == AF_INET) {
|
||||
sin = (struct sockaddr_in *)ss;
|
||||
return (&sin->sin_addr);
|
||||
}
|
||||
else {
|
||||
sin6 = (struct sockaddr_in6 *)ss;
|
||||
return (&sin6->sin6_addr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static socklen_t
|
||||
psutil_sockaddr_addrlen(int family) {
|
||||
if (family == AF_INET)
|
||||
return (sizeof(struct in_addr));
|
||||
else
|
||||
return (sizeof(struct in6_addr));
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
psutil_sockaddr_matches(int family, int port, void *pcb_addr,
|
||||
struct sockaddr_storage *ss) {
|
||||
if (psutil_sockaddr_port(family, ss) != port)
|
||||
return (0);
|
||||
return (memcmp(psutil_sockaddr_addr(family, ss), pcb_addr,
|
||||
psutil_sockaddr_addrlen(family)) == 0);
|
||||
}
|
||||
|
||||
|
||||
static struct tcpcb *
|
||||
psutil_search_tcplist(char *buf, struct kinfo_file *kif) {
|
||||
struct tcpcb *tp;
|
||||
struct inpcb *inp;
|
||||
struct xinpgen *xig, *oxig;
|
||||
struct xsocket *so;
|
||||
|
||||
oxig = xig = (struct xinpgen *)buf;
|
||||
for (xig = (struct xinpgen *)((char *)xig + xig->xig_len);
|
||||
xig->xig_len > sizeof(struct xinpgen);
|
||||
xig = (struct xinpgen *)((char *)xig + xig->xig_len)) {
|
||||
tp = &((struct xtcpcb *)xig)->xt_tp;
|
||||
inp = &((struct xtcpcb *)xig)->xt_inp;
|
||||
so = &((struct xtcpcb *)xig)->xt_socket;
|
||||
|
||||
if (so->so_type != kif->kf_sock_type ||
|
||||
so->xso_family != kif->kf_sock_domain ||
|
||||
so->xso_protocol != kif->kf_sock_protocol)
|
||||
continue;
|
||||
|
||||
if (kif->kf_sock_domain == AF_INET) {
|
||||
if (!psutil_sockaddr_matches(
|
||||
AF_INET, inp->inp_lport, &inp->inp_laddr,
|
||||
&kif->kf_sa_local))
|
||||
continue;
|
||||
if (!psutil_sockaddr_matches(
|
||||
AF_INET, inp->inp_fport, &inp->inp_faddr,
|
||||
&kif->kf_sa_peer))
|
||||
continue;
|
||||
} else {
|
||||
if (!psutil_sockaddr_matches(
|
||||
AF_INET6, inp->inp_lport, &inp->in6p_laddr,
|
||||
&kif->kf_sa_local))
|
||||
continue;
|
||||
if (!psutil_sockaddr_matches(
|
||||
AF_INET6, inp->inp_fport, &inp->in6p_faddr,
|
||||
&kif->kf_sa_peer))
|
||||
continue;
|
||||
}
|
||||
|
||||
return (tp);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
psutil_populate_xfiles() {
|
||||
size_t len;
|
||||
|
||||
if ((psutil_xfiles = malloc(len = sizeof *psutil_xfiles)) == NULL) {
|
||||
PyErr_NoMemory();
|
||||
return 0;
|
||||
}
|
||||
while (sysctlbyname("kern.file", psutil_xfiles, &len, 0, 0) == -1) {
|
||||
if (errno != ENOMEM) {
|
||||
PyErr_SetFromErrno(0);
|
||||
return 0;
|
||||
}
|
||||
len *= 2;
|
||||
if ((psutil_xfiles = realloc(psutil_xfiles, len)) == NULL) {
|
||||
PyErr_NoMemory();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (len > 0 && psutil_xfiles->xf_size != sizeof *psutil_xfiles) {
|
||||
PyErr_Format(PyExc_RuntimeError, "struct xfile size mismatch");
|
||||
return 0;
|
||||
}
|
||||
psutil_nxfiles = len / sizeof *psutil_xfiles;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
psutil_get_pid_from_sock(int sock_hash) {
|
||||
struct xfile *xf;
|
||||
int hash, n;
|
||||
for (xf = psutil_xfiles, n = 0; n < psutil_nxfiles; ++n, ++xf) {
|
||||
if (xf->xf_data == NULL)
|
||||
continue;
|
||||
hash = (int)((uintptr_t)xf->xf_data % HASHSIZE);
|
||||
if (sock_hash == hash)
|
||||
return xf->xf_pid;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// Reference:
|
||||
// https://gitorious.org/freebsd/freebsd/source/
|
||||
// f1d6f4778d2044502209708bc167c05f9aa48615:usr.bin/sockstat/sockstat.c
|
||||
int psutil_gather_inet(int proto, PyObject *py_retlist) {
|
||||
struct xinpgen *xig, *exig;
|
||||
struct xinpcb *xip;
|
||||
struct xtcpcb *xtp;
|
||||
struct inpcb *inp;
|
||||
struct xsocket *so;
|
||||
const char *varname = NULL;
|
||||
size_t len, bufsize;
|
||||
void *buf;
|
||||
int hash;
|
||||
int retry;
|
||||
int type;
|
||||
|
||||
PyObject *py_tuple = NULL;
|
||||
PyObject *py_laddr = NULL;
|
||||
PyObject *py_raddr = NULL;
|
||||
|
||||
switch (proto) {
|
||||
case IPPROTO_TCP:
|
||||
varname = "net.inet.tcp.pcblist";
|
||||
type = SOCK_STREAM;
|
||||
break;
|
||||
case IPPROTO_UDP:
|
||||
varname = "net.inet.udp.pcblist";
|
||||
type = SOCK_DGRAM;
|
||||
break;
|
||||
}
|
||||
|
||||
buf = NULL;
|
||||
bufsize = 8192;
|
||||
retry = 5;
|
||||
do {
|
||||
for (;;) {
|
||||
buf = realloc(buf, bufsize);
|
||||
if (buf == NULL)
|
||||
continue; // XXX
|
||||
len = bufsize;
|
||||
if (sysctlbyname(varname, buf, &len, NULL, 0) == 0)
|
||||
break;
|
||||
if (errno != ENOMEM) {
|
||||
PyErr_SetFromErrno(0);
|
||||
goto error;
|
||||
}
|
||||
bufsize *= 2;
|
||||
}
|
||||
xig = (struct xinpgen *)buf;
|
||||
exig = (struct xinpgen *)(void *)((char *)buf + len - sizeof *exig);
|
||||
if (xig->xig_len != sizeof *xig || exig->xig_len != sizeof *exig) {
|
||||
PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch");
|
||||
goto error;
|
||||
}
|
||||
} while (xig->xig_gen != exig->xig_gen && retry--);
|
||||
|
||||
for (;;) {
|
||||
int lport, rport, pid, status, family;
|
||||
|
||||
xig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len);
|
||||
if (xig >= exig)
|
||||
break;
|
||||
|
||||
switch (proto) {
|
||||
case IPPROTO_TCP:
|
||||
xtp = (struct xtcpcb *)xig;
|
||||
if (xtp->xt_len != sizeof *xtp) {
|
||||
PyErr_Format(PyExc_RuntimeError,
|
||||
"struct xtcpcb size mismatch");
|
||||
goto error;
|
||||
}
|
||||
inp = &xtp->xt_inp;
|
||||
so = &xtp->xt_socket;
|
||||
status = xtp->xt_tp.t_state;
|
||||
break;
|
||||
case IPPROTO_UDP:
|
||||
xip = (struct xinpcb *)xig;
|
||||
if (xip->xi_len != sizeof *xip) {
|
||||
PyErr_Format(PyExc_RuntimeError,
|
||||
"struct xinpcb size mismatch");
|
||||
goto error;
|
||||
}
|
||||
inp = &xip->xi_inp;
|
||||
so = &xip->xi_socket;
|
||||
status = PSUTIL_CONN_NONE;
|
||||
break;
|
||||
default:
|
||||
PyErr_Format(PyExc_RuntimeError, "invalid proto");
|
||||
goto error;
|
||||
}
|
||||
|
||||
char lip[200], rip[200];
|
||||
|
||||
hash = (int)((uintptr_t)so->xso_so % HASHSIZE);
|
||||
pid = psutil_get_pid_from_sock(hash);
|
||||
if (pid < 0)
|
||||
continue;
|
||||
lport = ntohs(inp->inp_lport);
|
||||
rport = ntohs(inp->inp_fport);
|
||||
|
||||
if (inp->inp_vflag & INP_IPV4) {
|
||||
family = AF_INET;
|
||||
inet_ntop(AF_INET, &inp->inp_laddr.s_addr, lip, sizeof(lip));
|
||||
inet_ntop(AF_INET, &inp->inp_faddr.s_addr, rip, sizeof(rip));
|
||||
}
|
||||
else if (inp->inp_vflag & INP_IPV6) {
|
||||
family = AF_INET6;
|
||||
inet_ntop(AF_INET6, &inp->in6p_laddr.s6_addr, lip, sizeof(lip));
|
||||
inet_ntop(AF_INET6, &inp->in6p_faddr.s6_addr, rip, sizeof(rip));
|
||||
}
|
||||
|
||||
// construct python tuple/list
|
||||
py_laddr = Py_BuildValue("(si)", lip, lport);
|
||||
if (!py_laddr)
|
||||
goto error;
|
||||
if (rport != 0)
|
||||
py_raddr = Py_BuildValue("(si)", rip, rport);
|
||||
else
|
||||
py_raddr = Py_BuildValue("()");
|
||||
if (!py_raddr)
|
||||
goto error;
|
||||
py_tuple = Py_BuildValue("(iiiNNii)", -1, family, type, py_laddr,
|
||||
py_raddr, status, pid);
|
||||
if (!py_tuple)
|
||||
goto error;
|
||||
if (PyList_Append(py_retlist, py_tuple))
|
||||
goto error;
|
||||
Py_DECREF(py_tuple);
|
||||
}
|
||||
|
||||
free(buf);
|
||||
return 1;
|
||||
|
||||
error:
|
||||
Py_XDECREF(py_tuple);
|
||||
Py_XDECREF(py_laddr);
|
||||
Py_XDECREF(py_raddr);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int psutil_gather_unix(int proto, PyObject *py_retlist) {
|
||||
struct xunpgen *xug, *exug;
|
||||
struct xunpcb *xup;
|
||||
const char *varname = NULL;
|
||||
const char *protoname = NULL;
|
||||
size_t len;
|
||||
size_t bufsize;
|
||||
void *buf;
|
||||
int hash;
|
||||
int retry;
|
||||
int pid;
|
||||
struct sockaddr_un *sun;
|
||||
char path[PATH_MAX];
|
||||
|
||||
PyObject *py_tuple = NULL;
|
||||
PyObject *py_laddr = NULL;
|
||||
PyObject *py_raddr = NULL;
|
||||
|
||||
switch (proto) {
|
||||
case SOCK_STREAM:
|
||||
varname = "net.local.stream.pcblist";
|
||||
protoname = "stream";
|
||||
break;
|
||||
case SOCK_DGRAM:
|
||||
varname = "net.local.dgram.pcblist";
|
||||
protoname = "dgram";
|
||||
break;
|
||||
}
|
||||
|
||||
buf = NULL;
|
||||
bufsize = 8192;
|
||||
retry = 5;
|
||||
|
||||
do {
|
||||
for (;;) {
|
||||
buf = realloc(buf, bufsize);
|
||||
if (buf == NULL) {
|
||||
PyErr_NoMemory();
|
||||
goto error;
|
||||
}
|
||||
len = bufsize;
|
||||
if (sysctlbyname(varname, buf, &len, NULL, 0) == 0)
|
||||
break;
|
||||
if (errno != ENOMEM) {
|
||||
PyErr_SetFromErrno(0);
|
||||
goto error;
|
||||
}
|
||||
bufsize *= 2;
|
||||
}
|
||||
xug = (struct xunpgen *)buf;
|
||||
exug = (struct xunpgen *)(void *)
|
||||
((char *)buf + len - sizeof *exug);
|
||||
if (xug->xug_len != sizeof *xug || exug->xug_len != sizeof *exug) {
|
||||
PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch");
|
||||
goto error;
|
||||
}
|
||||
} while (xug->xug_gen != exug->xug_gen && retry--);
|
||||
|
||||
for (;;) {
|
||||
xug = (struct xunpgen *)(void *)((char *)xug + xug->xug_len);
|
||||
if (xug >= exug)
|
||||
break;
|
||||
xup = (struct xunpcb *)xug;
|
||||
if (xup->xu_len != sizeof *xup)
|
||||
goto error;
|
||||
|
||||
hash = (int)((uintptr_t) xup->xu_socket.xso_so % HASHSIZE);
|
||||
pid = psutil_get_pid_from_sock(hash);
|
||||
if (pid < 0)
|
||||
continue;
|
||||
|
||||
sun = (struct sockaddr_un *)&xup->xu_addr;
|
||||
snprintf(path, sizeof(path), "%.*s",
|
||||
(int)(sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path))),
|
||||
sun->sun_path);
|
||||
|
||||
py_tuple = Py_BuildValue("(iiisOii)", -1, AF_UNIX, proto, path,
|
||||
Py_None, PSUTIL_CONN_NONE, pid);
|
||||
if (!py_tuple)
|
||||
goto error;
|
||||
if (PyList_Append(py_retlist, py_tuple))
|
||||
goto error;
|
||||
Py_DECREF(py_tuple);
|
||||
Py_INCREF(Py_None);
|
||||
}
|
||||
|
||||
free(buf);
|
||||
return 1;
|
||||
|
||||
error:
|
||||
Py_XDECREF(py_tuple);
|
||||
Py_XDECREF(py_laddr);
|
||||
Py_XDECREF(py_raddr);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
psutil_net_connections(PyObject* self, PyObject* args) {
|
||||
// Return system-wide open connections.
|
||||
PyObject *py_retlist = PyList_New(0);
|
||||
|
||||
if (py_retlist == NULL)
|
||||
return NULL;
|
||||
if (psutil_populate_xfiles() != 1)
|
||||
goto error;
|
||||
if (psutil_gather_inet(IPPROTO_TCP, py_retlist) == 0)
|
||||
goto error;
|
||||
if (psutil_gather_inet(IPPROTO_UDP, py_retlist) == 0)
|
||||
goto error;
|
||||
if (psutil_gather_unix(SOCK_STREAM, py_retlist) == 0)
|
||||
goto error;
|
||||
if (psutil_gather_unix(SOCK_DGRAM, py_retlist) == 0)
|
||||
goto error;
|
||||
|
||||
free(psutil_xfiles);
|
||||
return py_retlist;
|
||||
|
||||
error:
|
||||
Py_DECREF(py_retlist);
|
||||
free(psutil_xfiles);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
PyObject *
|
||||
psutil_proc_connections(PyObject *self, PyObject *args) {
|
||||
// Return connections opened by process.
|
||||
long pid;
|
||||
int i, cnt;
|
||||
struct kinfo_file *freep = NULL;
|
||||
struct kinfo_file *kif;
|
||||
char *tcplist = NULL;
|
||||
struct tcpcb *tcp;
|
||||
|
||||
PyObject *py_retlist = PyList_New(0);
|
||||
PyObject *py_tuple = NULL;
|
||||
PyObject *py_laddr = NULL;
|
||||
PyObject *py_raddr = NULL;
|
||||
PyObject *py_af_filter = NULL;
|
||||
PyObject *py_type_filter = NULL;
|
||||
PyObject *py_family = NULL;
|
||||
PyObject *py_type = NULL;
|
||||
|
||||
if (py_retlist == NULL)
|
||||
return NULL;
|
||||
if (! PyArg_ParseTuple(args, "lOO", &pid, &py_af_filter, &py_type_filter))
|
||||
goto error;
|
||||
if (!PySequence_Check(py_af_filter) || !PySequence_Check(py_type_filter)) {
|
||||
PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence");
|
||||
goto error;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
freep = kinfo_getfile(pid, &cnt);
|
||||
if (freep == NULL) {
|
||||
psutil_raise_for_pid(pid, "kinfo_getfile() failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
tcplist = psutil_fetch_tcplist();
|
||||
if (tcplist == NULL) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (i = 0; i < cnt; i++) {
|
||||
int lport, rport, state;
|
||||
char lip[200], rip[200];
|
||||
char path[PATH_MAX];
|
||||
int inseq;
|
||||
py_tuple = NULL;
|
||||
py_laddr = NULL;
|
||||
py_raddr = NULL;
|
||||
|
||||
kif = &freep[i];
|
||||
if (kif->kf_type == KF_TYPE_SOCKET) {
|
||||
// apply filters
|
||||
py_family = PyLong_FromLong((long)kif->kf_sock_domain);
|
||||
inseq = PySequence_Contains(py_af_filter, py_family);
|
||||
Py_DECREF(py_family);
|
||||
if (inseq == 0)
|
||||
continue;
|
||||
py_type = PyLong_FromLong((long)kif->kf_sock_type);
|
||||
inseq = PySequence_Contains(py_type_filter, py_type);
|
||||
Py_DECREF(py_type);
|
||||
if (inseq == 0)
|
||||
continue;
|
||||
// IPv4 / IPv6 socket
|
||||
if ((kif->kf_sock_domain == AF_INET) ||
|
||||
(kif->kf_sock_domain == AF_INET6)) {
|
||||
// fill status
|
||||
state = PSUTIL_CONN_NONE;
|
||||
if (kif->kf_sock_type == SOCK_STREAM) {
|
||||
tcp = psutil_search_tcplist(tcplist, kif);
|
||||
if (tcp != NULL)
|
||||
state = (int)tcp->t_state;
|
||||
}
|
||||
|
||||
// build addr and port
|
||||
inet_ntop(
|
||||
kif->kf_sock_domain,
|
||||
psutil_sockaddr_addr(kif->kf_sock_domain,
|
||||
&kif->kf_sa_local),
|
||||
lip,
|
||||
sizeof(lip));
|
||||
inet_ntop(
|
||||
kif->kf_sock_domain,
|
||||
psutil_sockaddr_addr(kif->kf_sock_domain,
|
||||
&kif->kf_sa_peer),
|
||||
rip,
|
||||
sizeof(rip));
|
||||
lport = htons(psutil_sockaddr_port(kif->kf_sock_domain,
|
||||
&kif->kf_sa_local));
|
||||
rport = htons(psutil_sockaddr_port(kif->kf_sock_domain,
|
||||
&kif->kf_sa_peer));
|
||||
|
||||
// construct python tuple/list
|
||||
py_laddr = Py_BuildValue("(si)", lip, lport);
|
||||
if (!py_laddr)
|
||||
goto error;
|
||||
if (rport != 0)
|
||||
py_raddr = Py_BuildValue("(si)", rip, rport);
|
||||
else
|
||||
py_raddr = Py_BuildValue("()");
|
||||
if (!py_raddr)
|
||||
goto error;
|
||||
py_tuple = Py_BuildValue(
|
||||
"(iiiNNi)",
|
||||
kif->kf_fd,
|
||||
kif->kf_sock_domain,
|
||||
kif->kf_sock_type,
|
||||
py_laddr,
|
||||
py_raddr,
|
||||
state
|
||||
);
|
||||
if (!py_tuple)
|
||||
goto error;
|
||||
if (PyList_Append(py_retlist, py_tuple))
|
||||
goto error;
|
||||
Py_DECREF(py_tuple);
|
||||
}
|
||||
// UNIX socket
|
||||
else if (kif->kf_sock_domain == AF_UNIX) {
|
||||
struct sockaddr_un *sun;
|
||||
|
||||
sun = (struct sockaddr_un *)&kif->kf_sa_local;
|
||||
snprintf(
|
||||
path, sizeof(path), "%.*s",
|
||||
(int)(sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path))),
|
||||
sun->sun_path);
|
||||
|
||||
py_tuple = Py_BuildValue(
|
||||
"(iiisOi)",
|
||||
kif->kf_fd,
|
||||
kif->kf_sock_domain,
|
||||
kif->kf_sock_type,
|
||||
path,
|
||||
Py_None,
|
||||
PSUTIL_CONN_NONE
|
||||
);
|
||||
if (!py_tuple)
|
||||
goto error;
|
||||
if (PyList_Append(py_retlist, py_tuple))
|
||||
goto error;
|
||||
Py_DECREF(py_tuple);
|
||||
Py_INCREF(Py_None);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(freep);
|
||||
free(tcplist);
|
||||
return py_retlist;
|
||||
|
||||
error:
|
||||
Py_XDECREF(py_tuple);
|
||||
Py_XDECREF(py_laddr);
|
||||
Py_XDECREF(py_raddr);
|
||||
Py_DECREF(py_retlist);
|
||||
if (freep != NULL)
|
||||
free(freep);
|
||||
if (tcplist != NULL)
|
||||
free(tcplist);
|
||||
return NULL;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user