mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 19:04:45 +00:00
Bug 1786490
- update to black 23.3.0 r=sylvestre,mach-reviewers,ahochheiden
Differential Revision: https://phabricator.services.mozilla.com/D186086
This commit is contained in:
parent
3b66ad25bb
commit
658caae5b1
@ -1,22 +0,0 @@
|
||||
click/__init__.py,sha256=FkyGDQ-cbiQxP_lxgUspyFYS48f2S_pTcfKPz-d_RMo,2463
|
||||
click/_bashcomplete.py,sha256=9J98IHQYmCAr2Jup6TDshUr5FJEen-AoQCZR0K5nKxQ,12309
|
||||
click/_compat.py,sha256=AoMaYnZ-3pwtNXuHtlb6_UXsayoG0QZiHKIRy2VFezc,24169
|
||||
click/_termui_impl.py,sha256=yNktUMAdjYOU1HMkq915jR3zgAzUNtGSQqSTSSMn3eQ,20702
|
||||
click/_textwrap.py,sha256=ajCzkzFly5tjm9foQ5N9_MOeaYJMBjAltuFa69n4iXY,1197
|
||||
click/_unicodefun.py,sha256=apLSNEBZgUsQNPMUv072zJ1swqnm0dYVT5TqcIWTt6w,4201
|
||||
click/_winconsole.py,sha256=6YDu6Rq1Wxx4w9uinBMK2LHvP83aerZM9GQurlk3QDo,10010
|
||||
click/core.py,sha256=V6DJzastGhrC6WTDwV9MSLwcJUdX2Uf1ypmgkjBdn_Y,77650
|
||||
click/decorators.py,sha256=3TvEO_BkaHl7k6Eh1G5eC7JK4LKPdpFqH9JP0QDyTlM,11215
|
||||
click/exceptions.py,sha256=3pQAyyMFzx5A3eV0Y27WtDTyGogZRbrC6_o5DjjKBbw,8118
|
||||
click/formatting.py,sha256=Wb4gqFEpWaKPgAbOvnkCl8p-bEZx5KpM5ZSByhlnJNk,9281
|
||||
click/globals.py,sha256=ht7u2kUGI08pAarB4e4yC8Lkkxy6gJfRZyzxEj8EbWQ,1501
|
||||
click/parser.py,sha256=mFK-k58JtPpqO0AC36WAr0t5UfzEw1mvgVSyn7WCe9M,15691
|
||||
click/termui.py,sha256=G7QBEKIepRIGLvNdGwBTYiEtSImRxvTO_AglVpyHH2s,23998
|
||||
click/testing.py,sha256=EUEsDUqNXFgCLhZ0ZFOROpaVDA5I_rijwnNPE6qICgA,12854
|
||||
click/types.py,sha256=wuubik4VqgqAw5dvbYFkDt-zSAx97y9TQXuXcVaRyQA,25045
|
||||
click/utils.py,sha256=4VEcJ7iEHwjnFuzEuRtkT99o5VG3zqSD7Q2CVzv13WU,15940
|
||||
click-7.1.2.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475
|
||||
click-7.1.2.dist-info/METADATA,sha256=LrRgakZKV7Yg3qJqX_plu2WhFW81MzP3EqQmZhHIO8M,2868
|
||||
click-7.1.2.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110
|
||||
click-7.1.2.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6
|
||||
click-7.1.2.dist-info/RECORD,,
|
@ -1,6 +0,0 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.34.2)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
@ -1,23 +1,27 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: click
|
||||
Version: 7.1.2
|
||||
Version: 8.1.6
|
||||
Summary: Composable command line interface toolkit
|
||||
Home-page: https://palletsprojects.com/p/click/
|
||||
Maintainer: Pallets
|
||||
Maintainer-email: contact@palletsprojects.com
|
||||
License: BSD-3-Clause
|
||||
Project-URL: Donate, https://palletsprojects.com/donate
|
||||
Project-URL: Documentation, https://click.palletsprojects.com/
|
||||
Project-URL: Code, https://github.com/pallets/click
|
||||
Project-URL: Issue tracker, https://github.com/pallets/click/issues
|
||||
Platform: UNKNOWN
|
||||
Project-URL: Changes, https://click.palletsprojects.com/changes/
|
||||
Project-URL: Source Code, https://github.com/pallets/click/
|
||||
Project-URL: Issue Tracker, https://github.com/pallets/click/issues/
|
||||
Project-URL: Chat, https://discord.gg/pallets
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
|
||||
Requires-Python: >=3.7
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE.rst
|
||||
Requires-Dist: colorama ; platform_system == "Windows"
|
||||
Requires-Dist: importlib-metadata ; python_version < "3.8"
|
||||
|
||||
\$ click\_
|
||||
==========
|
||||
@ -47,7 +51,7 @@ Install and update using `pip`_:
|
||||
|
||||
$ pip install -U click
|
||||
|
||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
||||
.. _pip: https://pip.pypa.io/en/stable/getting-started/
|
||||
|
||||
|
||||
A Simple Example
|
||||
@ -91,12 +95,9 @@ donate today`_.
|
||||
Links
|
||||
-----
|
||||
|
||||
- Website: https://palletsprojects.com/p/click/
|
||||
- Documentation: https://click.palletsprojects.com/
|
||||
- Releases: https://pypi.org/project/click/
|
||||
- Code: https://github.com/pallets/click
|
||||
- Issue tracker: https://github.com/pallets/click/issues
|
||||
- Test status: https://dev.azure.com/pallets/click/_build
|
||||
- Official chat: https://discord.gg/t6rrQZH
|
||||
|
||||
|
||||
- Changes: https://click.palletsprojects.com/changes/
|
||||
- PyPI Releases: https://pypi.org/project/click/
|
||||
- Source Code: https://github.com/pallets/click
|
||||
- Issue Tracker: https://github.com/pallets/click/issues
|
||||
- Chat: https://discord.gg/pallets
|
22
third_party/python/click/click-8.1.6.dist-info/RECORD
vendored
Normal file
22
third_party/python/click/click-8.1.6.dist-info/RECORD
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
click/__init__.py,sha256=85FPxtPII3PQQmI--C_LEOelVOl_T6w935vVDG-JG2A,3138
|
||||
click/_compat.py,sha256=IGKh_J5QdfKELitnRfTGHneejWxoCw_NX9tfMbdcg3w,18730
|
||||
click/_termui_impl.py,sha256=3dFYv4445Nw-rFvZOTBMBPYwB1bxnmNk9Du6Dm_oBSU,24069
|
||||
click/_textwrap.py,sha256=10fQ64OcBUMuK7mFvh8363_uoOxPlRItZBmKzRJDgoY,1353
|
||||
click/_winconsole.py,sha256=5ju3jQkcZD0W27WEMGqmEP4y_crUVzPCqsX_FYb7BO0,7860
|
||||
click/core.py,sha256=j6oEWtGgGna8JarD6WxhXmNnxLnfRjwXglbBc-8jr7U,114086
|
||||
click/decorators.py,sha256=-ZlbGYgV-oI8jr_oH4RpuL1PFS-5QmeuEAsLDAYgxtw,18719
|
||||
click/exceptions.py,sha256=fyROO-47HWFDjt2qupo7A3J32VlpM-ovJnfowu92K3s,9273
|
||||
click/formatting.py,sha256=Frf0-5W33-loyY_i9qrwXR8-STnW3m5gvyxLVUdyxyk,9706
|
||||
click/globals.py,sha256=TP-qM88STzc7f127h35TD_v920FgfOD2EwzqA0oE8XU,1961
|
||||
click/parser.py,sha256=LKyYQE9ZLj5KgIDXkrcTHQRXIggfoivX14_UVIn56YA,19067
|
||||
click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
click/shell_completion.py,sha256=azDxqZAVS6SRTF7alFCFoCMam3g5IyaMUO-PoOT4imY,18396
|
||||
click/termui.py,sha256=H7Q8FpmPelhJ2ovOhfCRhjMtCpNyjFXryAMLZODqsdc,28324
|
||||
click/testing.py,sha256=1Qd4kS5bucn1hsNIRryd0WtTMuCpkA93grkWxT8POsU,16084
|
||||
click/types.py,sha256=TZvz3hKvBztf-Hpa2enOmP4eznSPLzijjig5b_0XMxE,36391
|
||||
click/utils.py,sha256=1476UduUNY6UePGU4m18uzVHLt1sKM2PP3yWsQhbItM,20298
|
||||
click-8.1.6.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475
|
||||
click-8.1.6.dist-info/METADATA,sha256=W1Mybcg8Rb1eG1EDtUDutr477A_PIJDi6UOpzqohBxo,3014
|
||||
click-8.1.6.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
||||
click-8.1.6.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6
|
||||
click-8.1.6.dist-info/RECORD,,
|
@ -1,5 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.37.0)
|
||||
Generator: bdist_wheel (0.40.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
138
third_party/python/click/click/__init__.py
vendored
138
third_party/python/click/click/__init__.py
vendored
@ -4,76 +4,70 @@ 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.
|
||||
"""
|
||||
from .core import Argument
|
||||
from .core import BaseCommand
|
||||
from .core import Command
|
||||
from .core import CommandCollection
|
||||
from .core import Context
|
||||
from .core import Group
|
||||
from .core import MultiCommand
|
||||
from .core import Option
|
||||
from .core import Parameter
|
||||
from .decorators import argument
|
||||
from .decorators import command
|
||||
from .decorators import confirmation_option
|
||||
from .decorators import group
|
||||
from .decorators import help_option
|
||||
from .decorators import make_pass_decorator
|
||||
from .decorators import option
|
||||
from .decorators import pass_context
|
||||
from .decorators import pass_obj
|
||||
from .decorators import password_option
|
||||
from .decorators import version_option
|
||||
from .exceptions import Abort
|
||||
from .exceptions import BadArgumentUsage
|
||||
from .exceptions import BadOptionUsage
|
||||
from .exceptions import BadParameter
|
||||
from .exceptions import ClickException
|
||||
from .exceptions import FileError
|
||||
from .exceptions import MissingParameter
|
||||
from .exceptions import NoSuchOption
|
||||
from .exceptions import UsageError
|
||||
from .formatting import HelpFormatter
|
||||
from .formatting import wrap_text
|
||||
from .globals import get_current_context
|
||||
from .parser import OptionParser
|
||||
from .termui import clear
|
||||
from .termui import confirm
|
||||
from .termui import echo_via_pager
|
||||
from .termui import edit
|
||||
from .termui import get_terminal_size
|
||||
from .termui import getchar
|
||||
from .termui import launch
|
||||
from .termui import pause
|
||||
from .termui import progressbar
|
||||
from .termui import prompt
|
||||
from .termui import secho
|
||||
from .termui import style
|
||||
from .termui import unstyle
|
||||
from .types import BOOL
|
||||
from .types import Choice
|
||||
from .types import DateTime
|
||||
from .types import File
|
||||
from .types import FLOAT
|
||||
from .types import FloatRange
|
||||
from .types import INT
|
||||
from .types import IntRange
|
||||
from .types import ParamType
|
||||
from .types import Path
|
||||
from .types import STRING
|
||||
from .types import Tuple
|
||||
from .types import UNPROCESSED
|
||||
from .types import UUID
|
||||
from .utils import echo
|
||||
from .utils import format_filename
|
||||
from .utils import get_app_dir
|
||||
from .utils import get_binary_stream
|
||||
from .utils import get_os_args
|
||||
from .utils import get_text_stream
|
||||
from .utils import open_file
|
||||
from .core import Argument as Argument
|
||||
from .core import BaseCommand as BaseCommand
|
||||
from .core import Command as Command
|
||||
from .core import CommandCollection as CommandCollection
|
||||
from .core import Context as Context
|
||||
from .core import Group as Group
|
||||
from .core import MultiCommand as MultiCommand
|
||||
from .core import Option as Option
|
||||
from .core import Parameter as Parameter
|
||||
from .decorators import argument as argument
|
||||
from .decorators import command as command
|
||||
from .decorators import confirmation_option as confirmation_option
|
||||
from .decorators import group as group
|
||||
from .decorators import help_option as help_option
|
||||
from .decorators import make_pass_decorator as make_pass_decorator
|
||||
from .decorators import option as option
|
||||
from .decorators import pass_context as pass_context
|
||||
from .decorators import pass_obj as pass_obj
|
||||
from .decorators import password_option as password_option
|
||||
from .decorators import version_option as version_option
|
||||
from .exceptions import Abort as Abort
|
||||
from .exceptions import BadArgumentUsage as BadArgumentUsage
|
||||
from .exceptions import BadOptionUsage as BadOptionUsage
|
||||
from .exceptions import BadParameter as BadParameter
|
||||
from .exceptions import ClickException as ClickException
|
||||
from .exceptions import FileError as FileError
|
||||
from .exceptions import MissingParameter as MissingParameter
|
||||
from .exceptions import NoSuchOption as NoSuchOption
|
||||
from .exceptions import UsageError as UsageError
|
||||
from .formatting import HelpFormatter as HelpFormatter
|
||||
from .formatting import wrap_text as wrap_text
|
||||
from .globals import get_current_context as get_current_context
|
||||
from .parser import OptionParser as OptionParser
|
||||
from .termui import clear as clear
|
||||
from .termui import confirm as confirm
|
||||
from .termui import echo_via_pager as echo_via_pager
|
||||
from .termui import edit as edit
|
||||
from .termui import getchar as getchar
|
||||
from .termui import launch as launch
|
||||
from .termui import pause as pause
|
||||
from .termui import progressbar as progressbar
|
||||
from .termui import prompt as prompt
|
||||
from .termui import secho as secho
|
||||
from .termui import style as style
|
||||
from .termui import unstyle as unstyle
|
||||
from .types import BOOL as BOOL
|
||||
from .types import Choice as Choice
|
||||
from .types import DateTime as DateTime
|
||||
from .types import File as File
|
||||
from .types import FLOAT as FLOAT
|
||||
from .types import FloatRange as FloatRange
|
||||
from .types import INT as INT
|
||||
from .types import IntRange as IntRange
|
||||
from .types import ParamType as ParamType
|
||||
from .types import Path as Path
|
||||
from .types import STRING as STRING
|
||||
from .types import Tuple as Tuple
|
||||
from .types import UNPROCESSED as UNPROCESSED
|
||||
from .types import UUID as UUID
|
||||
from .utils import echo as echo
|
||||
from .utils import format_filename as format_filename
|
||||
from .utils import get_app_dir as get_app_dir
|
||||
from .utils import get_binary_stream as get_binary_stream
|
||||
from .utils import get_text_stream as get_text_stream
|
||||
from .utils import open_file as open_file
|
||||
|
||||
# Controls if click should emit the warning about the use of unicode
|
||||
# literals.
|
||||
disable_unicode_literals_warning = False
|
||||
|
||||
__version__ = "7.1.2"
|
||||
__version__ = "8.1.6"
|
||||
|
375
third_party/python/click/click/_bashcomplete.py
vendored
375
third_party/python/click/click/_bashcomplete.py
vendored
@ -1,375 +0,0 @@
|
||||
import copy
|
||||
import os
|
||||
import re
|
||||
|
||||
from .core import Argument
|
||||
from .core import MultiCommand
|
||||
from .core import Option
|
||||
from .parser import split_arg_string
|
||||
from .types import Choice
|
||||
from .utils import echo
|
||||
|
||||
try:
|
||||
from collections import abc
|
||||
except ImportError:
|
||||
import collections as abc
|
||||
|
||||
WORDBREAK = "="
|
||||
|
||||
# Note, only BASH version 4.4 and later have the nosort option.
|
||||
COMPLETION_SCRIPT_BASH = """
|
||||
%(complete_func)s() {
|
||||
local IFS=$'\n'
|
||||
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||
COMP_CWORD=$COMP_CWORD \\
|
||||
%(autocomplete_var)s=complete $1 ) )
|
||||
return 0
|
||||
}
|
||||
|
||||
%(complete_func)setup() {
|
||||
local COMPLETION_OPTIONS=""
|
||||
local BASH_VERSION_ARR=(${BASH_VERSION//./ })
|
||||
# Only BASH version 4.4 and later have the nosort option.
|
||||
if [ ${BASH_VERSION_ARR[0]} -gt 4 ] || ([ ${BASH_VERSION_ARR[0]} -eq 4 ] \
|
||||
&& [ ${BASH_VERSION_ARR[1]} -ge 4 ]); then
|
||||
COMPLETION_OPTIONS="-o nosort"
|
||||
fi
|
||||
|
||||
complete $COMPLETION_OPTIONS -F %(complete_func)s %(script_names)s
|
||||
}
|
||||
|
||||
%(complete_func)setup
|
||||
"""
|
||||
|
||||
COMPLETION_SCRIPT_ZSH = """
|
||||
#compdef %(script_names)s
|
||||
|
||||
%(complete_func)s() {
|
||||
local -a completions
|
||||
local -a completions_with_descriptions
|
||||
local -a response
|
||||
(( ! $+commands[%(script_names)s] )) && return 1
|
||||
|
||||
response=("${(@f)$( env COMP_WORDS=\"${words[*]}\" \\
|
||||
COMP_CWORD=$((CURRENT-1)) \\
|
||||
%(autocomplete_var)s=\"complete_zsh\" \\
|
||||
%(script_names)s )}")
|
||||
|
||||
for key descr in ${(kv)response}; do
|
||||
if [[ "$descr" == "_" ]]; then
|
||||
completions+=("$key")
|
||||
else
|
||||
completions_with_descriptions+=("$key":"$descr")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$completions_with_descriptions" ]; then
|
||||
_describe -V unsorted completions_with_descriptions -U
|
||||
fi
|
||||
|
||||
if [ -n "$completions" ]; then
|
||||
compadd -U -V unsorted -a completions
|
||||
fi
|
||||
compstate[insert]="automenu"
|
||||
}
|
||||
|
||||
compdef %(complete_func)s %(script_names)s
|
||||
"""
|
||||
|
||||
COMPLETION_SCRIPT_FISH = (
|
||||
"complete --no-files --command %(script_names)s --arguments"
|
||||
' "(env %(autocomplete_var)s=complete_fish'
|
||||
" COMP_WORDS=(commandline -cp) COMP_CWORD=(commandline -t)"
|
||||
' %(script_names)s)"'
|
||||
)
|
||||
|
||||
_completion_scripts = {
|
||||
"bash": COMPLETION_SCRIPT_BASH,
|
||||
"zsh": COMPLETION_SCRIPT_ZSH,
|
||||
"fish": COMPLETION_SCRIPT_FISH,
|
||||
}
|
||||
|
||||
_invalid_ident_char_re = re.compile(r"[^a-zA-Z0-9_]")
|
||||
|
||||
|
||||
def get_completion_script(prog_name, complete_var, shell):
|
||||
cf_name = _invalid_ident_char_re.sub("", prog_name.replace("-", "_"))
|
||||
script = _completion_scripts.get(shell, COMPLETION_SCRIPT_BASH)
|
||||
return (
|
||||
script
|
||||
% {
|
||||
"complete_func": "_{}_completion".format(cf_name),
|
||||
"script_names": prog_name,
|
||||
"autocomplete_var": complete_var,
|
||||
}
|
||||
).strip() + ";"
|
||||
|
||||
|
||||
def resolve_ctx(cli, prog_name, args):
|
||||
"""Parse into a hierarchy of contexts. Contexts are connected
|
||||
through the parent variable.
|
||||
|
||||
:param cli: command definition
|
||||
:param prog_name: the program that is running
|
||||
:param args: full list of args
|
||||
:return: the final context/command parsed
|
||||
"""
|
||||
ctx = cli.make_context(prog_name, args, resilient_parsing=True)
|
||||
args = ctx.protected_args + ctx.args
|
||||
while args:
|
||||
if isinstance(ctx.command, MultiCommand):
|
||||
if not ctx.command.chain:
|
||||
cmd_name, cmd, args = ctx.command.resolve_command(ctx, args)
|
||||
if cmd is None:
|
||||
return ctx
|
||||
ctx = cmd.make_context(
|
||||
cmd_name, args, parent=ctx, resilient_parsing=True
|
||||
)
|
||||
args = ctx.protected_args + ctx.args
|
||||
else:
|
||||
# Walk chained subcommand contexts saving the last one.
|
||||
while args:
|
||||
cmd_name, cmd, args = ctx.command.resolve_command(ctx, args)
|
||||
if cmd is None:
|
||||
return ctx
|
||||
sub_ctx = cmd.make_context(
|
||||
cmd_name,
|
||||
args,
|
||||
parent=ctx,
|
||||
allow_extra_args=True,
|
||||
allow_interspersed_args=False,
|
||||
resilient_parsing=True,
|
||||
)
|
||||
args = sub_ctx.args
|
||||
ctx = sub_ctx
|
||||
args = sub_ctx.protected_args + sub_ctx.args
|
||||
else:
|
||||
break
|
||||
return ctx
|
||||
|
||||
|
||||
def start_of_option(param_str):
|
||||
"""
|
||||
:param param_str: param_str to check
|
||||
:return: whether or not this is the start of an option declaration
|
||||
(i.e. starts "-" or "--")
|
||||
"""
|
||||
return param_str and param_str[:1] == "-"
|
||||
|
||||
|
||||
def is_incomplete_option(all_args, cmd_param):
|
||||
"""
|
||||
:param all_args: the full original list of args supplied
|
||||
:param cmd_param: the current command paramter
|
||||
:return: whether or not the last option declaration (i.e. starts
|
||||
"-" or "--") is incomplete and corresponds to this cmd_param. In
|
||||
other words whether this cmd_param option can still accept
|
||||
values
|
||||
"""
|
||||
if not isinstance(cmd_param, Option):
|
||||
return False
|
||||
if cmd_param.is_flag:
|
||||
return False
|
||||
last_option = None
|
||||
for index, arg_str in enumerate(
|
||||
reversed([arg for arg in all_args if arg != WORDBREAK])
|
||||
):
|
||||
if index + 1 > cmd_param.nargs:
|
||||
break
|
||||
if start_of_option(arg_str):
|
||||
last_option = arg_str
|
||||
|
||||
return True if last_option and last_option in cmd_param.opts else False
|
||||
|
||||
|
||||
def is_incomplete_argument(current_params, cmd_param):
|
||||
"""
|
||||
:param current_params: the current params and values for this
|
||||
argument as already entered
|
||||
:param cmd_param: the current command parameter
|
||||
:return: whether or not the last argument is incomplete and
|
||||
corresponds to this cmd_param. In other words whether or not the
|
||||
this cmd_param argument can still accept values
|
||||
"""
|
||||
if not isinstance(cmd_param, Argument):
|
||||
return False
|
||||
current_param_values = current_params[cmd_param.name]
|
||||
if current_param_values is None:
|
||||
return True
|
||||
if cmd_param.nargs == -1:
|
||||
return True
|
||||
if (
|
||||
isinstance(current_param_values, abc.Iterable)
|
||||
and cmd_param.nargs > 1
|
||||
and len(current_param_values) < cmd_param.nargs
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_user_autocompletions(ctx, args, incomplete, cmd_param):
|
||||
"""
|
||||
:param ctx: context associated with the parsed command
|
||||
:param args: full list of args
|
||||
:param incomplete: the incomplete text to autocomplete
|
||||
:param cmd_param: command definition
|
||||
:return: all the possible user-specified completions for the param
|
||||
"""
|
||||
results = []
|
||||
if isinstance(cmd_param.type, Choice):
|
||||
# Choices don't support descriptions.
|
||||
results = [
|
||||
(c, None) for c in cmd_param.type.choices if str(c).startswith(incomplete)
|
||||
]
|
||||
elif cmd_param.autocompletion is not None:
|
||||
dynamic_completions = cmd_param.autocompletion(
|
||||
ctx=ctx, args=args, incomplete=incomplete
|
||||
)
|
||||
results = [
|
||||
c if isinstance(c, tuple) else (c, None) for c in dynamic_completions
|
||||
]
|
||||
return results
|
||||
|
||||
|
||||
def get_visible_commands_starting_with(ctx, starts_with):
|
||||
"""
|
||||
:param ctx: context associated with the parsed command
|
||||
:starts_with: string that visible commands must start with.
|
||||
:return: all visible (not hidden) commands that start with starts_with.
|
||||
"""
|
||||
for c in ctx.command.list_commands(ctx):
|
||||
if c.startswith(starts_with):
|
||||
command = ctx.command.get_command(ctx, c)
|
||||
if not command.hidden:
|
||||
yield command
|
||||
|
||||
|
||||
def add_subcommand_completions(ctx, incomplete, completions_out):
|
||||
# Add subcommand completions.
|
||||
if isinstance(ctx.command, MultiCommand):
|
||||
completions_out.extend(
|
||||
[
|
||||
(c.name, c.get_short_help_str())
|
||||
for c in get_visible_commands_starting_with(ctx, incomplete)
|
||||
]
|
||||
)
|
||||
|
||||
# Walk up the context list and add any other completion
|
||||
# possibilities from chained commands
|
||||
while ctx.parent is not None:
|
||||
ctx = ctx.parent
|
||||
if isinstance(ctx.command, MultiCommand) and ctx.command.chain:
|
||||
remaining_commands = [
|
||||
c
|
||||
for c in get_visible_commands_starting_with(ctx, incomplete)
|
||||
if c.name not in ctx.protected_args
|
||||
]
|
||||
completions_out.extend(
|
||||
[(c.name, c.get_short_help_str()) for c in remaining_commands]
|
||||
)
|
||||
|
||||
|
||||
def get_choices(cli, prog_name, args, incomplete):
|
||||
"""
|
||||
:param cli: command definition
|
||||
:param prog_name: the program that is running
|
||||
:param args: full list of args
|
||||
:param incomplete: the incomplete text to autocomplete
|
||||
:return: all the possible completions for the incomplete
|
||||
"""
|
||||
all_args = copy.deepcopy(args)
|
||||
|
||||
ctx = resolve_ctx(cli, prog_name, args)
|
||||
if ctx is None:
|
||||
return []
|
||||
|
||||
has_double_dash = "--" in all_args
|
||||
|
||||
# In newer versions of bash long opts with '='s are partitioned, but
|
||||
# it's easier to parse without the '='
|
||||
if start_of_option(incomplete) and WORDBREAK in incomplete:
|
||||
partition_incomplete = incomplete.partition(WORDBREAK)
|
||||
all_args.append(partition_incomplete[0])
|
||||
incomplete = partition_incomplete[2]
|
||||
elif incomplete == WORDBREAK:
|
||||
incomplete = ""
|
||||
|
||||
completions = []
|
||||
if not has_double_dash and start_of_option(incomplete):
|
||||
# completions for partial options
|
||||
for param in ctx.command.params:
|
||||
if isinstance(param, Option) and not param.hidden:
|
||||
param_opts = [
|
||||
param_opt
|
||||
for param_opt in param.opts + param.secondary_opts
|
||||
if param_opt not in all_args or param.multiple
|
||||
]
|
||||
completions.extend(
|
||||
[(o, param.help) for o in param_opts if o.startswith(incomplete)]
|
||||
)
|
||||
return completions
|
||||
# completion for option values from user supplied values
|
||||
for param in ctx.command.params:
|
||||
if is_incomplete_option(all_args, param):
|
||||
return get_user_autocompletions(ctx, all_args, incomplete, param)
|
||||
# completion for argument values from user supplied values
|
||||
for param in ctx.command.params:
|
||||
if is_incomplete_argument(ctx.params, param):
|
||||
return get_user_autocompletions(ctx, all_args, incomplete, param)
|
||||
|
||||
add_subcommand_completions(ctx, incomplete, completions)
|
||||
# Sort before returning so that proper ordering can be enforced in custom types.
|
||||
return sorted(completions)
|
||||
|
||||
|
||||
def do_complete(cli, prog_name, include_descriptions):
|
||||
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[0])
|
||||
if include_descriptions:
|
||||
# ZSH has trouble dealing with empty array parameters when
|
||||
# returned from commands, use '_' to indicate no description
|
||||
# is present.
|
||||
echo(item[1] if item[1] else "_")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def do_complete_fish(cli, prog_name):
|
||||
cwords = split_arg_string(os.environ["COMP_WORDS"])
|
||||
incomplete = os.environ["COMP_CWORD"]
|
||||
args = cwords[1:]
|
||||
|
||||
for item in get_choices(cli, prog_name, args, incomplete):
|
||||
if item[1]:
|
||||
echo("{arg}\t{desc}".format(arg=item[0], desc=item[1]))
|
||||
else:
|
||||
echo(item[0])
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def bashcomplete(cli, prog_name, complete_var, complete_instr):
|
||||
if "_" in complete_instr:
|
||||
command, shell = complete_instr.split("_", 1)
|
||||
else:
|
||||
command = complete_instr
|
||||
shell = "bash"
|
||||
|
||||
if command == "source":
|
||||
echo(get_completion_script(prog_name, complete_var, shell))
|
||||
return True
|
||||
elif command == "complete":
|
||||
if shell == "fish":
|
||||
return do_complete_fish(cli, prog_name)
|
||||
elif shell in {"bash", "zsh"}:
|
||||
return do_complete(cli, prog_name, shell == "zsh")
|
||||
|
||||
return False
|
841
third_party/python/click/click/_compat.py
vendored
841
third_party/python/click/click/_compat.py
vendored
File diff suppressed because it is too large
Load Diff
430
third_party/python/click/click/_termui_impl.py
vendored
430
third_party/python/click/click/_termui_impl.py
vendored
@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This module contains implementations for the termui module. To keep the
|
||||
import time of Click down, some infrequently used functionality is
|
||||
@ -9,20 +8,24 @@ import math
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import typing as t
|
||||
from gettext import gettext as _
|
||||
from io import StringIO
|
||||
from types import TracebackType
|
||||
|
||||
from ._compat import _default_text_stdout
|
||||
from ._compat import CYGWIN
|
||||
from ._compat import get_best_encoding
|
||||
from ._compat import int_types
|
||||
from ._compat import isatty
|
||||
from ._compat import open_stream
|
||||
from ._compat import range_type
|
||||
from ._compat import strip_ansi
|
||||
from ._compat import term_len
|
||||
from ._compat import WIN
|
||||
from .exceptions import ClickException
|
||||
from .utils import echo
|
||||
|
||||
V = t.TypeVar("V")
|
||||
|
||||
if os.name == "nt":
|
||||
BEFORE_BAR = "\r"
|
||||
AFTER_BAR = "\n"
|
||||
@ -31,42 +34,25 @@ else:
|
||||
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_types) or hint < 0:
|
||||
return None
|
||||
return hint
|
||||
|
||||
|
||||
class ProgressBar(object):
|
||||
class ProgressBar(t.Generic[V]):
|
||||
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,
|
||||
):
|
||||
iterable: t.Optional[t.Iterable[V]],
|
||||
length: t.Optional[int] = None,
|
||||
fill_char: str = "#",
|
||||
empty_char: str = " ",
|
||||
bar_template: str = "%(bar)s",
|
||||
info_sep: str = " ",
|
||||
show_eta: bool = True,
|
||||
show_percent: t.Optional[bool] = None,
|
||||
show_pos: bool = False,
|
||||
item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None,
|
||||
label: t.Optional[str] = None,
|
||||
file: t.Optional[t.TextIO] = None,
|
||||
color: t.Optional[bool] = None,
|
||||
update_min_steps: int = 1,
|
||||
width: int = 30,
|
||||
) -> None:
|
||||
self.fill_char = fill_char
|
||||
self.empty_char = empty_char
|
||||
self.bar_template = bar_template
|
||||
@ -75,50 +61,69 @@ class ProgressBar(object):
|
||||
self.show_percent = show_percent
|
||||
self.show_pos = show_pos
|
||||
self.item_show_func = item_show_func
|
||||
self.label = label or ""
|
||||
self.label: str = label or ""
|
||||
|
||||
if file is None:
|
||||
file = _default_text_stdout()
|
||||
|
||||
# There are no standard streams attached to write to. For example,
|
||||
# pythonw on Windows.
|
||||
if file is None:
|
||||
file = StringIO()
|
||||
|
||||
self.file = file
|
||||
self.color = color
|
||||
self.width = width
|
||||
self.autowidth = width == 0
|
||||
self.update_min_steps = update_min_steps
|
||||
self._completed_intervals = 0
|
||||
self.width: int = width
|
||||
self.autowidth: bool = width == 0
|
||||
|
||||
if length is None:
|
||||
length = _length_hint(iterable)
|
||||
from operator import length_hint
|
||||
|
||||
length = length_hint(iterable, -1)
|
||||
|
||||
if length == -1:
|
||||
length = None
|
||||
if iterable is None:
|
||||
if length is None:
|
||||
raise TypeError("iterable or length is required")
|
||||
iterable = range_type(length)
|
||||
self.iter = iter(iterable)
|
||||
iterable = t.cast(t.Iterable[V], range(length))
|
||||
self.iter: t.Iterable[V] = iter(iterable)
|
||||
self.length = length
|
||||
self.length_known = length is not None
|
||||
self.pos = 0
|
||||
self.avg = []
|
||||
self.avg: t.List[float] = []
|
||||
self.last_eta: float
|
||||
self.start: float
|
||||
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
|
||||
self.short_limit = 0.5
|
||||
self.eta_known: bool = False
|
||||
self.finished: bool = False
|
||||
self.max_width: t.Optional[int] = None
|
||||
self.entered: bool = False
|
||||
self.current_item: t.Optional[V] = None
|
||||
self.is_hidden: bool = not isatty(self.file)
|
||||
self._last_line: t.Optional[str] = None
|
||||
|
||||
def __enter__(self):
|
||||
def __enter__(self) -> "ProgressBar[V]":
|
||||
self.entered = True
|
||||
self.render_progress()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: t.Optional[t.Type[BaseException]],
|
||||
exc_value: t.Optional[BaseException],
|
||||
tb: t.Optional[TracebackType],
|
||||
) -> None:
|
||||
self.render_finish()
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> t.Iterator[V]:
|
||||
if not self.entered:
|
||||
raise RuntimeError("You need to use progress bars in a with block.")
|
||||
self.render_progress()
|
||||
return self.generator()
|
||||
|
||||
def __next__(self):
|
||||
def __next__(self) -> V:
|
||||
# Iteration is defined in terms of a generator function,
|
||||
# returned by iter(self); use that to define next(). This works
|
||||
# because `self.iter` is an iterable consumed by that generator,
|
||||
@ -126,37 +131,31 @@ class ProgressBar(object):
|
||||
# twice works and does "what you want".
|
||||
return next(iter(self))
|
||||
|
||||
# Python 2 compat
|
||||
next = __next__
|
||||
|
||||
def is_fast(self):
|
||||
return time.time() - self.start <= self.short_limit
|
||||
|
||||
def render_finish(self):
|
||||
if self.is_hidden or self.is_fast():
|
||||
def render_finish(self) -> None:
|
||||
if self.is_hidden:
|
||||
return
|
||||
self.file.write(AFTER_BAR)
|
||||
self.file.flush()
|
||||
|
||||
@property
|
||||
def pct(self):
|
||||
def pct(self) -> float:
|
||||
if self.finished:
|
||||
return 1.0
|
||||
return min(self.pos / (float(self.length) or 1), 1.0)
|
||||
return min(self.pos / (float(self.length or 1) or 1), 1.0)
|
||||
|
||||
@property
|
||||
def time_per_iteration(self):
|
||||
def time_per_iteration(self) -> float:
|
||||
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:
|
||||
def eta(self) -> float:
|
||||
if self.length is not None and not self.finished:
|
||||
return self.time_per_iteration * (self.length - self.pos)
|
||||
return 0.0
|
||||
|
||||
def format_eta(self):
|
||||
def format_eta(self) -> str:
|
||||
if self.eta_known:
|
||||
t = int(self.eta)
|
||||
seconds = t % 60
|
||||
@ -166,44 +165,44 @@ class ProgressBar(object):
|
||||
hours = t % 24
|
||||
t //= 24
|
||||
if t > 0:
|
||||
return "{}d {:02}:{:02}:{:02}".format(t, hours, minutes, seconds)
|
||||
return f"{t}d {hours:02}:{minutes:02}:{seconds:02}"
|
||||
else:
|
||||
return "{:02}:{:02}:{:02}".format(hours, minutes, seconds)
|
||||
return f"{hours:02}:{minutes:02}:{seconds:02}"
|
||||
return ""
|
||||
|
||||
def format_pos(self):
|
||||
def format_pos(self) -> str:
|
||||
pos = str(self.pos)
|
||||
if self.length_known:
|
||||
pos += "/{}".format(self.length)
|
||||
if self.length is not None:
|
||||
pos += f"/{self.length}"
|
||||
return pos
|
||||
|
||||
def format_pct(self):
|
||||
return "{: 4}%".format(int(self.pct * 100))[1:]
|
||||
def format_pct(self) -> str:
|
||||
return f"{int(self.pct * 100): 4}%"[1:]
|
||||
|
||||
def format_bar(self):
|
||||
if self.length_known:
|
||||
def format_bar(self) -> str:
|
||||
if self.length is not None:
|
||||
bar_length = int(self.pct * self.width)
|
||||
bar = self.fill_char * bar_length
|
||||
bar += self.empty_char * (self.width - bar_length)
|
||||
elif self.finished:
|
||||
bar = self.fill_char * self.width
|
||||
else:
|
||||
bar = list(self.empty_char * (self.width or 1))
|
||||
chars = list(self.empty_char * (self.width or 1))
|
||||
if self.time_per_iteration != 0:
|
||||
bar[
|
||||
chars[
|
||||
int(
|
||||
(math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5)
|
||||
* self.width
|
||||
)
|
||||
] = self.fill_char
|
||||
bar = "".join(bar)
|
||||
bar = "".join(chars)
|
||||
return bar
|
||||
|
||||
def format_progress_line(self):
|
||||
def format_progress_line(self) -> str:
|
||||
show_percent = self.show_percent
|
||||
|
||||
info_bits = []
|
||||
if self.length_known and show_percent is None:
|
||||
if self.length is not None and show_percent is None:
|
||||
show_percent = not self.show_pos
|
||||
|
||||
if self.show_pos:
|
||||
@ -226,10 +225,16 @@ class ProgressBar(object):
|
||||
}
|
||||
).rstrip()
|
||||
|
||||
def render_progress(self):
|
||||
from .termui import get_terminal_size
|
||||
def render_progress(self) -> None:
|
||||
import shutil
|
||||
|
||||
if self.is_hidden:
|
||||
# Only output the label as it changes if the output is not a
|
||||
# TTY. Use file=stderr if you expect to be piping stdout.
|
||||
if self._last_line != self.label:
|
||||
self._last_line = self.label
|
||||
echo(self.label, file=self.file, color=self.color)
|
||||
|
||||
return
|
||||
|
||||
buf = []
|
||||
@ -238,10 +243,10 @@ class ProgressBar(object):
|
||||
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)
|
||||
new_width = max(0, shutil.get_terminal_size().columns - clutter_length)
|
||||
if new_width < old_width:
|
||||
buf.append(BEFORE_BAR)
|
||||
buf.append(" " * self.max_width)
|
||||
buf.append(" " * self.max_width) # type: ignore
|
||||
self.max_width = new_width
|
||||
self.width = new_width
|
||||
|
||||
@ -260,14 +265,14 @@ class ProgressBar(object):
|
||||
line = "".join(buf)
|
||||
# Render the line only if it changed.
|
||||
|
||||
if line != self._last_line and not self.is_fast():
|
||||
if line != self._last_line:
|
||||
self._last_line = line
|
||||
echo(line, file=self.file, color=self.color, nl=False)
|
||||
self.file.flush()
|
||||
|
||||
def make_step(self, n_steps):
|
||||
def make_step(self, n_steps: int) -> None:
|
||||
self.pos += n_steps
|
||||
if self.length_known and self.pos >= self.length:
|
||||
if self.length is not None and self.pos >= self.length:
|
||||
self.finished = True
|
||||
|
||||
if (time.time() - self.last_eta) < 1.0:
|
||||
@ -285,18 +290,40 @@ class ProgressBar(object):
|
||||
|
||||
self.avg = self.avg[-6:] + [step]
|
||||
|
||||
self.eta_known = self.length_known
|
||||
self.eta_known = self.length is not None
|
||||
|
||||
def update(self, n_steps):
|
||||
self.make_step(n_steps)
|
||||
self.render_progress()
|
||||
def update(self, n_steps: int, current_item: t.Optional[V] = None) -> None:
|
||||
"""Update the progress bar by advancing a specified number of
|
||||
steps, and optionally set the ``current_item`` for this new
|
||||
position.
|
||||
|
||||
def finish(self):
|
||||
self.eta_known = 0
|
||||
:param n_steps: Number of steps to advance.
|
||||
:param current_item: Optional item to set as ``current_item``
|
||||
for the updated position.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Added the ``current_item`` optional parameter.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Only render when the number of steps meets the
|
||||
``update_min_steps`` threshold.
|
||||
"""
|
||||
if current_item is not None:
|
||||
self.current_item = current_item
|
||||
|
||||
self._completed_intervals += n_steps
|
||||
|
||||
if self._completed_intervals >= self.update_min_steps:
|
||||
self.make_step(self._completed_intervals)
|
||||
self.render_progress()
|
||||
self._completed_intervals = 0
|
||||
|
||||
def finish(self) -> None:
|
||||
self.eta_known = False
|
||||
self.current_item = None
|
||||
self.finished = True
|
||||
|
||||
def generator(self):
|
||||
def generator(self) -> t.Iterator[V]:
|
||||
"""Return a generator which yields the items added to the bar
|
||||
during construction, and updates the progress bar *after* the
|
||||
yielded block returns.
|
||||
@ -312,20 +339,33 @@ class ProgressBar(object):
|
||||
raise RuntimeError("You need to use progress bars in a with block.")
|
||||
|
||||
if self.is_hidden:
|
||||
for rv in self.iter:
|
||||
yield rv
|
||||
yield from self.iter
|
||||
else:
|
||||
for rv in self.iter:
|
||||
self.current_item = rv
|
||||
|
||||
# This allows show_item_func to be updated before the
|
||||
# item is processed. Only trigger at the beginning of
|
||||
# the update interval.
|
||||
if self._completed_intervals == 0:
|
||||
self.render_progress()
|
||||
|
||||
yield rv
|
||||
self.update(1)
|
||||
|
||||
self.finish()
|
||||
self.render_progress()
|
||||
|
||||
|
||||
def pager(generator, color=None):
|
||||
def pager(generator: t.Iterable[str], color: t.Optional[bool] = None) -> None:
|
||||
"""Decide what method to use for paging through text."""
|
||||
stdout = _default_text_stdout()
|
||||
|
||||
# There are no standard streams attached to write to. For example,
|
||||
# pythonw on Windows.
|
||||
if stdout is None:
|
||||
stdout = StringIO()
|
||||
|
||||
if not isatty(sys.stdin) or not isatty(stdout):
|
||||
return _nullpager(stdout, generator, color)
|
||||
pager_cmd = (os.environ.get("PAGER", None) or "").strip()
|
||||
@ -345,14 +385,14 @@ def pager(generator, color=None):
|
||||
fd, filename = tempfile.mkstemp()
|
||||
os.close(fd)
|
||||
try:
|
||||
if hasattr(os, "system") and os.system('more "{}"'.format(filename)) == 0:
|
||||
if hasattr(os, "system") and os.system(f'more "{filename}"') == 0:
|
||||
return _pipepager(generator, "more", color)
|
||||
return _nullpager(stdout, generator, color)
|
||||
finally:
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def _pipepager(generator, cmd, color):
|
||||
def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> None:
|
||||
"""Page through text by feeding it to another program. Invoking a
|
||||
pager through this might support colors.
|
||||
"""
|
||||
@ -364,7 +404,7 @@ def _pipepager(generator, cmd, color):
|
||||
# condition that
|
||||
cmd_detail = cmd.rsplit("/", 1)[-1].split()
|
||||
if color is None and cmd_detail[0] == "less":
|
||||
less_flags = "{}{}".format(os.environ.get("LESS", ""), " ".join(cmd_detail[1:]))
|
||||
less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_detail[1:])}"
|
||||
if not less_flags:
|
||||
env["LESS"] = "-R"
|
||||
color = True
|
||||
@ -372,17 +412,18 @@ def _pipepager(generator, cmd, color):
|
||||
color = True
|
||||
|
||||
c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env)
|
||||
encoding = get_best_encoding(c.stdin)
|
||||
stdin = t.cast(t.BinaryIO, c.stdin)
|
||||
encoding = get_best_encoding(stdin)
|
||||
try:
|
||||
for text in generator:
|
||||
if not color:
|
||||
text = strip_ansi(text)
|
||||
|
||||
c.stdin.write(text.encode(encoding, "replace"))
|
||||
except (IOError, KeyboardInterrupt):
|
||||
stdin.write(text.encode(encoding, "replace"))
|
||||
except (OSError, KeyboardInterrupt):
|
||||
pass
|
||||
else:
|
||||
c.stdin.close()
|
||||
stdin.close()
|
||||
|
||||
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting
|
||||
# search or other commands inside less).
|
||||
@ -401,11 +442,13 @@ def _pipepager(generator, cmd, color):
|
||||
break
|
||||
|
||||
|
||||
def _tempfilepager(generator, cmd, color):
|
||||
def _tempfilepager(
|
||||
generator: t.Iterable[str], cmd: str, color: t.Optional[bool]
|
||||
) -> None:
|
||||
"""Page through text by invoking a program on a temporary file."""
|
||||
import tempfile
|
||||
|
||||
filename = tempfile.mktemp()
|
||||
fd, filename = tempfile.mkstemp()
|
||||
# TODO: This never terminates if the passed generator never terminates.
|
||||
text = "".join(generator)
|
||||
if not color:
|
||||
@ -414,12 +457,15 @@ def _tempfilepager(generator, cmd, color):
|
||||
with open_stream(filename, "wb")[0] as f:
|
||||
f.write(text.encode(encoding))
|
||||
try:
|
||||
os.system('{} "{}"'.format(cmd, filename))
|
||||
os.system(f'{cmd} "{filename}"')
|
||||
finally:
|
||||
os.close(fd)
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def _nullpager(stream, generator, color):
|
||||
def _nullpager(
|
||||
stream: t.TextIO, generator: t.Iterable[str], color: t.Optional[bool]
|
||||
) -> None:
|
||||
"""Simply print unformatted text. This is the ultimate fallback."""
|
||||
for text in generator:
|
||||
if not color:
|
||||
@ -427,14 +473,20 @@ def _nullpager(stream, generator, color):
|
||||
stream.write(text)
|
||||
|
||||
|
||||
class Editor(object):
|
||||
def __init__(self, editor=None, env=None, require_save=True, extension=".txt"):
|
||||
class Editor:
|
||||
def __init__(
|
||||
self,
|
||||
editor: t.Optional[str] = None,
|
||||
env: t.Optional[t.Mapping[str, str]] = None,
|
||||
require_save: bool = True,
|
||||
extension: str = ".txt",
|
||||
) -> None:
|
||||
self.editor = editor
|
||||
self.env = env
|
||||
self.require_save = require_save
|
||||
self.extension = extension
|
||||
|
||||
def get_editor(self):
|
||||
def get_editor(self) -> str:
|
||||
if self.editor is not None:
|
||||
return self.editor
|
||||
for key in "VISUAL", "EDITOR":
|
||||
@ -444,48 +496,62 @@ class Editor(object):
|
||||
if WIN:
|
||||
return "notepad"
|
||||
for editor in "sensible-editor", "vim", "nano":
|
||||
if os.system("which {} >/dev/null 2>&1".format(editor)) == 0:
|
||||
if os.system(f"which {editor} >/dev/null 2>&1") == 0:
|
||||
return editor
|
||||
return "vi"
|
||||
|
||||
def edit_file(self, filename):
|
||||
def edit_file(self, filename: str) -> None:
|
||||
import subprocess
|
||||
|
||||
editor = self.get_editor()
|
||||
environ: t.Optional[t.Dict[str, str]] = None
|
||||
|
||||
if self.env:
|
||||
environ = os.environ.copy()
|
||||
environ.update(self.env)
|
||||
else:
|
||||
environ = None
|
||||
|
||||
try:
|
||||
c = subprocess.Popen(
|
||||
'{} "{}"'.format(editor, filename), env=environ, shell=True,
|
||||
)
|
||||
c = subprocess.Popen(f'{editor} "{filename}"', env=environ, shell=True)
|
||||
exit_code = c.wait()
|
||||
if exit_code != 0:
|
||||
raise ClickException("{}: Editing failed!".format(editor))
|
||||
raise ClickException(
|
||||
_("{editor}: Editing failed").format(editor=editor)
|
||||
)
|
||||
except OSError as e:
|
||||
raise ClickException("{}: Editing failed: {}".format(editor, e))
|
||||
raise ClickException(
|
||||
_("{editor}: Editing failed: {e}").format(editor=editor, e=e)
|
||||
) from e
|
||||
|
||||
def edit(self, text):
|
||||
def edit(self, text: t.Optional[t.AnyStr]) -> t.Optional[t.AnyStr]:
|
||||
import tempfile
|
||||
|
||||
text = text or ""
|
||||
if text and not text.endswith("\n"):
|
||||
text += "\n"
|
||||
if not text:
|
||||
data = b""
|
||||
elif isinstance(text, (bytes, bytearray)):
|
||||
data = text
|
||||
else:
|
||||
if text and not text.endswith("\n"):
|
||||
text += "\n"
|
||||
|
||||
if WIN:
|
||||
data = text.replace("\n", "\r\n").encode("utf-8-sig")
|
||||
else:
|
||||
data = text.encode("utf-8")
|
||||
|
||||
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: t.BinaryIO
|
||||
|
||||
f = os.fdopen(fd, "wb")
|
||||
f.write(text)
|
||||
f.close()
|
||||
try:
|
||||
with os.fdopen(fd, "wb") as f:
|
||||
f.write(data)
|
||||
|
||||
# If the filesystem resolution is 1 second, like Mac OS
|
||||
# 10.12 Extended, or 2 seconds, like FAT32, and the editor
|
||||
# closes very fast, require_save can fail. Set the modified
|
||||
# time to be 2 seconds in the past to work around this.
|
||||
os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2))
|
||||
# Depending on the resolution, the exact value might not be
|
||||
# recorded, so get the new recorded value.
|
||||
timestamp = os.path.getmtime(name)
|
||||
|
||||
self.edit_file(name)
|
||||
@ -493,26 +559,26 @@ class Editor(object):
|
||||
if self.require_save and os.path.getmtime(name) == timestamp:
|
||||
return None
|
||||
|
||||
f = open(name, "rb")
|
||||
try:
|
||||
with open(name, "rb") as f:
|
||||
rv = f.read()
|
||||
finally:
|
||||
f.close()
|
||||
return rv.decode("utf-8-sig").replace("\r\n", "\n")
|
||||
|
||||
if isinstance(text, (bytes, bytearray)):
|
||||
return rv
|
||||
|
||||
return rv.decode("utf-8-sig").replace("\r\n", "\n") # type: ignore
|
||||
finally:
|
||||
os.unlink(name)
|
||||
|
||||
|
||||
def open_url(url, wait=False, locate=False):
|
||||
def open_url(url: str, wait: bool = False, locate: bool = False) -> int:
|
||||
import subprocess
|
||||
|
||||
def _unquote_file(url):
|
||||
try:
|
||||
import urllib
|
||||
except ImportError:
|
||||
import urllib
|
||||
def _unquote_file(url: str) -> str:
|
||||
from urllib.parse import unquote
|
||||
|
||||
if url.startswith("file://"):
|
||||
url = urllib.unquote(url[7:])
|
||||
url = unquote(url[7:])
|
||||
|
||||
return url
|
||||
|
||||
if sys.platform == "darwin":
|
||||
@ -529,19 +595,21 @@ def open_url(url, wait=False, locate=False):
|
||||
null.close()
|
||||
elif WIN:
|
||||
if locate:
|
||||
url = _unquote_file(url)
|
||||
args = 'explorer /select,"{}"'.format(_unquote_file(url.replace('"', "")))
|
||||
url = _unquote_file(url.replace('"', ""))
|
||||
args = f'explorer /select,"{url}"'
|
||||
else:
|
||||
args = 'start {} "" "{}"'.format(
|
||||
"/WAIT" if wait else "", url.replace('"', "")
|
||||
)
|
||||
url = url.replace('"', "")
|
||||
wait_str = "/WAIT" if wait else ""
|
||||
args = f'start {wait_str} "" "{url}"'
|
||||
return os.system(args)
|
||||
elif CYGWIN:
|
||||
if locate:
|
||||
url = _unquote_file(url)
|
||||
args = 'cygstart "{}"'.format(os.path.dirname(url).replace('"', ""))
|
||||
url = os.path.dirname(_unquote_file(url).replace('"', ""))
|
||||
args = f'cygstart "{url}"'
|
||||
else:
|
||||
args = 'cygstart {} "{}"'.format("-w" if wait else "", url.replace('"', ""))
|
||||
url = url.replace('"', "")
|
||||
wait_str = "-w" if wait else ""
|
||||
args = f'cygstart {wait_str} "{url}"'
|
||||
return os.system(args)
|
||||
|
||||
try:
|
||||
@ -562,23 +630,27 @@ def open_url(url, wait=False, locate=False):
|
||||
return 1
|
||||
|
||||
|
||||
def _translate_ch_to_exc(ch):
|
||||
if ch == u"\x03":
|
||||
def _translate_ch_to_exc(ch: str) -> t.Optional[BaseException]:
|
||||
if ch == "\x03":
|
||||
raise KeyboardInterrupt()
|
||||
if ch == u"\x04" and not WIN: # Unix-like, Ctrl+D
|
||||
|
||||
if ch == "\x04" and not WIN: # Unix-like, Ctrl+D
|
||||
raise EOFError()
|
||||
if ch == u"\x1a" and WIN: # Windows, Ctrl+Z
|
||||
|
||||
if ch == "\x1a" and WIN: # Windows, Ctrl+Z
|
||||
raise EOFError()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
if WIN:
|
||||
import msvcrt
|
||||
|
||||
@contextlib.contextmanager
|
||||
def raw_terminal():
|
||||
yield
|
||||
def raw_terminal() -> t.Iterator[int]:
|
||||
yield -1
|
||||
|
||||
def getchar(echo):
|
||||
def getchar(echo: bool) -> str:
|
||||
# The function `getch` will return a bytes object corresponding to
|
||||
# the pressed character. Since Windows 10 build 1803, it will also
|
||||
# return \x00 when called a second time after pressing a regular key.
|
||||
@ -608,50 +680,60 @@ if WIN:
|
||||
#
|
||||
# Anyway, Click doesn't claim to do this Right(tm), and using `getwch`
|
||||
# is doing the right thing in more situations than with `getch`.
|
||||
func: t.Callable[[], str]
|
||||
|
||||
if echo:
|
||||
func = msvcrt.getwche
|
||||
func = msvcrt.getwche # type: ignore
|
||||
else:
|
||||
func = msvcrt.getwch
|
||||
func = msvcrt.getwch # type: ignore
|
||||
|
||||
rv = func()
|
||||
if rv in (u"\x00", u"\xe0"):
|
||||
|
||||
if rv in ("\x00", "\xe0"):
|
||||
# \x00 and \xe0 are control characters that indicate special key,
|
||||
# see above.
|
||||
rv += func()
|
||||
|
||||
_translate_ch_to_exc(rv)
|
||||
return rv
|
||||
|
||||
|
||||
else:
|
||||
import tty
|
||||
import termios
|
||||
|
||||
@contextlib.contextmanager
|
||||
def raw_terminal():
|
||||
def raw_terminal() -> t.Iterator[int]:
|
||||
f: t.Optional[t.TextIO]
|
||||
fd: int
|
||||
|
||||
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)
|
||||
yield fd
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
||||
sys.stdout.flush()
|
||||
|
||||
if f is not None:
|
||||
f.close()
|
||||
except termios.error:
|
||||
pass
|
||||
|
||||
def getchar(echo):
|
||||
def getchar(echo: bool) -> str:
|
||||
with raw_terminal() as fd:
|
||||
ch = os.read(fd, 32)
|
||||
ch = ch.decode(get_best_encoding(sys.stdin), "replace")
|
||||
ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace")
|
||||
|
||||
if echo and isatty(sys.stdout):
|
||||
sys.stdout.write(ch)
|
||||
|
||||
_translate_ch_to_exc(ch)
|
||||
return ch
|
||||
|
20
third_party/python/click/click/_textwrap.py
vendored
20
third_party/python/click/click/_textwrap.py
vendored
@ -1,9 +1,16 @@
|
||||
import textwrap
|
||||
import typing as t
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
class TextWrapper(textwrap.TextWrapper):
|
||||
def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
|
||||
def _handle_long_word(
|
||||
self,
|
||||
reversed_chunks: t.List[str],
|
||||
cur_line: t.List[str],
|
||||
cur_len: int,
|
||||
width: int,
|
||||
) -> None:
|
||||
space_left = max(width - cur_len, 1)
|
||||
|
||||
if self.break_long_words:
|
||||
@ -16,22 +23,27 @@ class TextWrapper(textwrap.TextWrapper):
|
||||
cur_line.append(reversed_chunks.pop())
|
||||
|
||||
@contextmanager
|
||||
def extra_indent(self, indent):
|
||||
def extra_indent(self, indent: str) -> t.Iterator[None]:
|
||||
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):
|
||||
def indent_only(self, text: str) -> str:
|
||||
rv = []
|
||||
|
||||
for idx, line in enumerate(text.splitlines()):
|
||||
indent = self.initial_indent
|
||||
|
||||
if idx > 0:
|
||||
indent = self.subsequent_indent
|
||||
rv.append(indent + line)
|
||||
|
||||
rv.append(f"{indent}{line}")
|
||||
|
||||
return "\n".join(rv)
|
||||
|
131
third_party/python/click/click/_unicodefun.py
vendored
131
third_party/python/click/click/_unicodefun.py
vendored
@ -1,131 +0,0 @@
|
||||
import codecs
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ._compat import PY2
|
||||
|
||||
|
||||
def _find_unicode_literals_frame():
|
||||
import __future__
|
||||
|
||||
if not hasattr(sys, "_getframe"): # not all Python implementations have it
|
||||
return 0
|
||||
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
|
||||
|
||||
from . import disable_unicode_literals_warning
|
||||
|
||||
if not PY2 or 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"
|
||||
" https://click.palletsprojects.com/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
|
||||
|
||||
try:
|
||||
rv = subprocess.Popen(
|
||||
["locale", "-a"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
).communicate()[0]
|
||||
except OSError:
|
||||
rv = b""
|
||||
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 locales were discovered. This most likely"
|
||||
" requires resolving by reconfiguring the locale"
|
||||
" system."
|
||||
)
|
||||
elif has_c_utf8:
|
||||
extra += (
|
||||
"This system supports the C.UTF-8 locale which is"
|
||||
" recommended. You might be able to resolve your issue"
|
||||
" by exporting the 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 you can pick from. The following suitable"
|
||||
" locales were discovered: {}".format(", ".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"
|
||||
" but the locale system could not pick up from it"
|
||||
" because it does not exist. The exported locale is"
|
||||
" '{}' but it is not supported".format(bad_locale)
|
||||
)
|
||||
|
||||
raise RuntimeError(
|
||||
"Click will abort further execution because Python 3 was"
|
||||
" configured to use ASCII as encoding for the environment."
|
||||
" Consult https://click.palletsprojects.com/python3/ for"
|
||||
" mitigation steps.{}".format(extra)
|
||||
)
|
219
third_party/python/click/click/_winconsole.py
vendored
219
third_party/python/click/click/_winconsole.py
vendored
@ -1,4 +1,3 @@
|
||||
# -*- 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.
|
||||
@ -6,13 +5,11 @@
|
||||
# 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 ctypes
|
||||
# echo and prompt.
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import zlib
|
||||
import typing as t
|
||||
from ctypes import byref
|
||||
from ctypes import c_char
|
||||
from ctypes import c_char_p
|
||||
@ -22,28 +19,18 @@ from ctypes import c_ulong
|
||||
from ctypes import c_void_p
|
||||
from ctypes import POINTER
|
||||
from ctypes import py_object
|
||||
from ctypes import windll
|
||||
from ctypes import WinError
|
||||
from ctypes import WINFUNCTYPE
|
||||
from ctypes import Structure
|
||||
from ctypes.wintypes import DWORD
|
||||
from ctypes.wintypes import HANDLE
|
||||
from ctypes.wintypes import LPCWSTR
|
||||
from ctypes.wintypes import LPWSTR
|
||||
|
||||
import msvcrt
|
||||
|
||||
from ._compat import _NonClosingTextIOWrapper
|
||||
from ._compat import PY2
|
||||
from ._compat import text_type
|
||||
|
||||
try:
|
||||
from ctypes import pythonapi
|
||||
|
||||
PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
|
||||
PyBuffer_Release = pythonapi.PyBuffer_Release
|
||||
except ImportError:
|
||||
pythonapi = None
|
||||
|
||||
assert sys.platform == "win32"
|
||||
import msvcrt # noqa: E402
|
||||
from ctypes import windll # noqa: E402
|
||||
from ctypes import WINFUNCTYPE # noqa: E402
|
||||
|
||||
c_ssize_p = POINTER(c_ssize_t)
|
||||
|
||||
@ -57,16 +44,12 @@ GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32))
|
||||
CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
|
||||
("CommandLineToArgvW", windll.shell32)
|
||||
)
|
||||
LocalFree = WINFUNCTYPE(ctypes.c_void_p, ctypes.c_void_p)(
|
||||
("LocalFree", windll.kernel32)
|
||||
)
|
||||
|
||||
LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32))
|
||||
|
||||
STDIN_HANDLE = GetStdHandle(-10)
|
||||
STDOUT_HANDLE = GetStdHandle(-11)
|
||||
STDERR_HANDLE = GetStdHandle(-12)
|
||||
|
||||
|
||||
PyBUF_SIMPLE = 0
|
||||
PyBUF_WRITABLE = 1
|
||||
|
||||
@ -81,36 +64,37 @@ 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:
|
||||
try:
|
||||
from ctypes import pythonapi
|
||||
except ImportError:
|
||||
# On PyPy we cannot get buffers so our ability to operate here is
|
||||
# severely limited.
|
||||
get_buffer = None
|
||||
else:
|
||||
|
||||
class Py_buffer(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),
|
||||
]
|
||||
|
||||
PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
|
||||
PyBuffer_Release = pythonapi.PyBuffer_Release
|
||||
|
||||
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)
|
||||
@ -123,7 +107,7 @@ class _WindowsConsoleRawIOBase(io.RawIOBase):
|
||||
self.handle = handle
|
||||
|
||||
def isatty(self):
|
||||
io.RawIOBase.isatty(self)
|
||||
super().isatty()
|
||||
return True
|
||||
|
||||
|
||||
@ -155,7 +139,7 @@ class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
|
||||
# wait for KeyboardInterrupt
|
||||
time.sleep(0.1)
|
||||
if not rv:
|
||||
raise OSError("Windows error: {}".format(GetLastError()))
|
||||
raise OSError(f"Windows error: {GetLastError()}")
|
||||
|
||||
if buffer[0] == EOF:
|
||||
return 0
|
||||
@ -172,7 +156,7 @@ class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
|
||||
return "ERROR_SUCCESS"
|
||||
elif errno == ERROR_NOT_ENOUGH_MEMORY:
|
||||
return "ERROR_NOT_ENOUGH_MEMORY"
|
||||
return "Windows error {}".format(errno)
|
||||
return f"Windows error {errno}"
|
||||
|
||||
def write(self, b):
|
||||
bytes_to_be_written = len(b)
|
||||
@ -194,17 +178,17 @@ class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
|
||||
return bytes_written
|
||||
|
||||
|
||||
class ConsoleStream(object):
|
||||
def __init__(self, text_stream, byte_stream):
|
||||
class ConsoleStream:
|
||||
def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None:
|
||||
self._text_stream = text_stream
|
||||
self.buffer = byte_stream
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
return self.buffer.name
|
||||
|
||||
def write(self, x):
|
||||
if isinstance(x, text_type):
|
||||
def write(self, x: t.AnyStr) -> int:
|
||||
if isinstance(x, str):
|
||||
return self._text_stream.write(x)
|
||||
try:
|
||||
self.flush()
|
||||
@ -212,159 +196,84 @@ class ConsoleStream(object):
|
||||
pass
|
||||
return self.buffer.write(x)
|
||||
|
||||
def writelines(self, lines):
|
||||
def writelines(self, lines: t.Iterable[t.AnyStr]) -> None:
|
||||
for line in lines:
|
||||
self.write(line)
|
||||
|
||||
def __getattr__(self, name):
|
||||
def __getattr__(self, name: str) -> t.Any:
|
||||
return getattr(self._text_stream, name)
|
||||
|
||||
def isatty(self):
|
||||
def isatty(self) -> bool:
|
||||
return self.buffer.isatty()
|
||||
|
||||
def __repr__(self):
|
||||
return "<ConsoleStream name={!r} encoding={!r}>".format(
|
||||
self.name, self.encoding
|
||||
)
|
||||
return f"<ConsoleStream name={self.name!r} encoding={self.encoding!r}>"
|
||||
|
||||
|
||||
class WindowsChunkedWriter(object):
|
||||
"""
|
||||
Wraps a stream (such as stdout), acting as a transparent proxy for all
|
||||
attribute access apart from method 'write()' which we wrap to write in
|
||||
limited chunks due to a Windows limitation on binary console streams.
|
||||
"""
|
||||
|
||||
def __init__(self, wrapped):
|
||||
# double-underscore everything to prevent clashes with names of
|
||||
# attributes on the wrapped stream object.
|
||||
self.__wrapped = wrapped
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.__wrapped, name)
|
||||
|
||||
def write(self, text):
|
||||
total_to_write = len(text)
|
||||
written = 0
|
||||
|
||||
while written < total_to_write:
|
||||
to_write = min(total_to_write - written, MAX_BYTES_WRITTEN)
|
||||
self.__wrapped.write(text[written : written + to_write])
|
||||
written += to_write
|
||||
|
||||
|
||||
_wrapped_std_streams = set()
|
||||
|
||||
|
||||
def _wrap_std_stream(name):
|
||||
# Python 2 & Windows 7 and below
|
||||
if (
|
||||
PY2
|
||||
and sys.getwindowsversion()[:2] <= (6, 1)
|
||||
and name not in _wrapped_std_streams
|
||||
):
|
||||
setattr(sys, name, WindowsChunkedWriter(getattr(sys, name)))
|
||||
_wrapped_std_streams.add(name)
|
||||
|
||||
|
||||
def _get_text_stdin(buffer_stream):
|
||||
def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO:
|
||||
text_stream = _NonClosingTextIOWrapper(
|
||||
io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
|
||||
"utf-16-le",
|
||||
"strict",
|
||||
line_buffering=True,
|
||||
)
|
||||
return ConsoleStream(text_stream, buffer_stream)
|
||||
return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
|
||||
|
||||
|
||||
def _get_text_stdout(buffer_stream):
|
||||
def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO:
|
||||
text_stream = _NonClosingTextIOWrapper(
|
||||
io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)),
|
||||
"utf-16-le",
|
||||
"strict",
|
||||
line_buffering=True,
|
||||
)
|
||||
return ConsoleStream(text_stream, buffer_stream)
|
||||
return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
|
||||
|
||||
|
||||
def _get_text_stderr(buffer_stream):
|
||||
def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO:
|
||||
text_stream = _NonClosingTextIOWrapper(
|
||||
io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)),
|
||||
"utf-16-le",
|
||||
"strict",
|
||||
line_buffering=True,
|
||||
)
|
||||
return ConsoleStream(text_stream, buffer_stream)
|
||||
return t.cast(t.TextIO, 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))
|
||||
if not argv_unicode:
|
||||
raise WinError()
|
||||
try:
|
||||
argv = [argv_unicode[i] for i in range(0, argc.value)]
|
||||
finally:
|
||||
LocalFree(argv_unicode)
|
||||
del argv_unicode
|
||||
|
||||
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 = {
|
||||
_stream_factories: t.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = {
|
||||
0: _get_text_stdin,
|
||||
1: _get_text_stdout,
|
||||
2: _get_text_stderr,
|
||||
}
|
||||
|
||||
|
||||
def _is_console(f):
|
||||
def _is_console(f: t.TextIO) -> bool:
|
||||
if not hasattr(f, "fileno"):
|
||||
return False
|
||||
|
||||
try:
|
||||
fileno = f.fileno()
|
||||
except OSError:
|
||||
except (OSError, io.UnsupportedOperation):
|
||||
return False
|
||||
|
||||
handle = msvcrt.get_osfhandle(fileno)
|
||||
return bool(GetConsoleMode(handle, byref(DWORD())))
|
||||
|
||||
|
||||
def _get_windows_console_stream(f, encoding, errors):
|
||||
def _get_windows_console_stream(
|
||||
f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str]
|
||||
) -> t.Optional[t.TextIO]:
|
||||
if (
|
||||
get_buffer is not None
|
||||
and encoding in ("utf-16-le", None)
|
||||
and errors in ("strict", None)
|
||||
and encoding in {"utf-16-le", None}
|
||||
and errors in {"strict", None}
|
||||
and _is_console(f)
|
||||
):
|
||||
func = _stream_factories.get(f.fileno())
|
||||
if func is not None:
|
||||
if not PY2:
|
||||
f = getattr(f, "buffer", None)
|
||||
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)
|
||||
b = getattr(f, "buffer", None)
|
||||
|
||||
if b is None:
|
||||
return None
|
||||
|
||||
return func(b)
|
||||
|
2314
third_party/python/click/click/core.py
vendored
2314
third_party/python/click/click/core.py
vendored
File diff suppressed because it is too large
Load Diff
592
third_party/python/click/click/decorators.py
vendored
592
third_party/python/click/click/decorators.py
vendored
@ -1,41 +1,55 @@
|
||||
import inspect
|
||||
import sys
|
||||
import types
|
||||
import typing as t
|
||||
from functools import update_wrapper
|
||||
from gettext import gettext as _
|
||||
|
||||
from ._compat import iteritems
|
||||
from ._unicodefun import _check_for_unicode_literals
|
||||
from .core import Argument
|
||||
from .core import Command
|
||||
from .core import Context
|
||||
from .core import Group
|
||||
from .core import Option
|
||||
from .core import Parameter
|
||||
from .globals import get_current_context
|
||||
from .utils import echo
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
def pass_context(f):
|
||||
P = te.ParamSpec("P")
|
||||
|
||||
R = t.TypeVar("R")
|
||||
T = t.TypeVar("T")
|
||||
_AnyCallable = t.Callable[..., t.Any]
|
||||
FC = t.TypeVar("FC", bound=t.Union[_AnyCallable, Command])
|
||||
|
||||
|
||||
def pass_context(f: "t.Callable[te.Concatenate[Context, P], R]") -> "t.Callable[P, R]":
|
||||
"""Marks a callback as wanting to receive the current context
|
||||
object as first argument.
|
||||
"""
|
||||
|
||||
def new_func(*args, **kwargs):
|
||||
def new_func(*args: "P.args", **kwargs: "P.kwargs") -> "R":
|
||||
return f(get_current_context(), *args, **kwargs)
|
||||
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
|
||||
def pass_obj(f):
|
||||
def pass_obj(f: "t.Callable[te.Concatenate[t.Any, P], R]") -> "t.Callable[P, R]":
|
||||
"""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):
|
||||
def new_func(*args: "P.args", **kwargs: "P.kwargs") -> "R":
|
||||
return f(get_current_context().obj, *args, **kwargs)
|
||||
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
|
||||
def make_pass_decorator(object_type, ensure=False):
|
||||
def make_pass_decorator(
|
||||
object_type: t.Type[T], ensure: bool = False
|
||||
) -> t.Callable[["t.Callable[te.Concatenate[T, P], R]"], "t.Callable[P, R]"]:
|
||||
"""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
|
||||
@ -58,52 +72,107 @@ def make_pass_decorator(object_type, ensure=False):
|
||||
remembered on the context if it's not there yet.
|
||||
"""
|
||||
|
||||
def decorator(f):
|
||||
def new_func(*args, **kwargs):
|
||||
def decorator(f: "t.Callable[te.Concatenate[T, P], R]") -> "t.Callable[P, R]":
|
||||
def new_func(*args: "P.args", **kwargs: "P.kwargs") -> "R":
|
||||
ctx = get_current_context()
|
||||
|
||||
obj: t.Optional[T]
|
||||
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 '{}' existing".format(object_type.__name__)
|
||||
f" object of type {object_type.__name__!r}"
|
||||
" existing."
|
||||
)
|
||||
|
||||
return ctx.invoke(f, obj, *args, **kwargs)
|
||||
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
return decorator
|
||||
return decorator # type: ignore[return-value]
|
||||
|
||||
|
||||
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().replace("_", "-"),
|
||||
callback=f,
|
||||
params=params,
|
||||
**attrs
|
||||
def pass_meta_key(
|
||||
key: str, *, doc_description: t.Optional[str] = None
|
||||
) -> "t.Callable[[t.Callable[te.Concatenate[t.Any, P], R]], t.Callable[P, R]]":
|
||||
"""Create a decorator that passes a key from
|
||||
:attr:`click.Context.meta` as the first argument to the decorated
|
||||
function.
|
||||
|
||||
:param key: Key in ``Context.meta`` to pass.
|
||||
:param doc_description: Description of the object being passed,
|
||||
inserted into the decorator's docstring. Defaults to "the 'key'
|
||||
key from Context.meta".
|
||||
|
||||
.. versionadded:: 8.0
|
||||
"""
|
||||
|
||||
def decorator(f: "t.Callable[te.Concatenate[t.Any, P], R]") -> "t.Callable[P, R]":
|
||||
def new_func(*args: "P.args", **kwargs: "P.kwargs") -> R:
|
||||
ctx = get_current_context()
|
||||
obj = ctx.meta[key]
|
||||
return ctx.invoke(f, obj, *args, **kwargs)
|
||||
|
||||
return update_wrapper(new_func, f)
|
||||
|
||||
if doc_description is None:
|
||||
doc_description = f"the {key!r} key from :attr:`click.Context.meta`"
|
||||
|
||||
decorator.__doc__ = (
|
||||
f"Decorator that passes {doc_description} as the first argument"
|
||||
" to the decorated function."
|
||||
)
|
||||
return decorator # type: ignore[return-value]
|
||||
|
||||
|
||||
def command(name=None, cls=None, **attrs):
|
||||
CmdType = t.TypeVar("CmdType", bound=Command)
|
||||
|
||||
|
||||
# variant: no call, directly as decorator for a function.
|
||||
@t.overload
|
||||
def command(name: _AnyCallable) -> Command:
|
||||
...
|
||||
|
||||
|
||||
# variant: with positional name and with positional or keyword cls argument:
|
||||
# @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...)
|
||||
@t.overload
|
||||
def command(
|
||||
name: t.Optional[str],
|
||||
cls: t.Type[CmdType],
|
||||
**attrs: t.Any,
|
||||
) -> t.Callable[[_AnyCallable], CmdType]:
|
||||
...
|
||||
|
||||
|
||||
# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...)
|
||||
@t.overload
|
||||
def command(
|
||||
name: None = None,
|
||||
*,
|
||||
cls: t.Type[CmdType],
|
||||
**attrs: t.Any,
|
||||
) -> t.Callable[[_AnyCallable], CmdType]:
|
||||
...
|
||||
|
||||
|
||||
# variant: with optional string name, no cls argument provided.
|
||||
@t.overload
|
||||
def command(
|
||||
name: t.Optional[str] = ..., cls: None = None, **attrs: t.Any
|
||||
) -> t.Callable[[_AnyCallable], Command]:
|
||||
...
|
||||
|
||||
|
||||
def command(
|
||||
name: t.Union[t.Optional[str], _AnyCallable] = None,
|
||||
cls: t.Optional[t.Type[CmdType]] = None,
|
||||
**attrs: t.Any,
|
||||
) -> t.Union[Command, t.Callable[[_AnyCallable], t.Union[Command, CmdType]]]:
|
||||
r"""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.
|
||||
@ -113,6 +182,8 @@ def command(name=None, cls=None, **attrs):
|
||||
pass the intended name as the first argument.
|
||||
|
||||
All keyword arguments are forwarded to the underlying command class.
|
||||
For the ``params`` argument, any decorated params are appended to
|
||||
the end of the list.
|
||||
|
||||
Once decorated the function turns into a :class:`Command` instance
|
||||
that can be invoked as a command line utility or be attached to a
|
||||
@ -122,212 +193,369 @@ def command(name=None, cls=None, **attrs):
|
||||
name with underscores replaced by dashes.
|
||||
: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)
|
||||
.. versionchanged:: 8.1
|
||||
This decorator can be applied without parentheses.
|
||||
|
||||
.. versionchanged:: 8.1
|
||||
The ``params`` argument can be used. Decorated params are
|
||||
appended to the end of the list.
|
||||
"""
|
||||
|
||||
func: t.Optional[t.Callable[[_AnyCallable], t.Any]] = None
|
||||
|
||||
if callable(name):
|
||||
func = name
|
||||
name = None
|
||||
assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class."
|
||||
assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments."
|
||||
|
||||
if cls is None:
|
||||
cls = t.cast(t.Type[CmdType], Command)
|
||||
|
||||
def decorator(f: _AnyCallable) -> CmdType:
|
||||
if isinstance(f, Command):
|
||||
raise TypeError("Attempted to convert a callback into a command twice.")
|
||||
|
||||
attr_params = attrs.pop("params", None)
|
||||
params = attr_params if attr_params is not None else []
|
||||
|
||||
try:
|
||||
decorator_params = f.__click_params__ # type: ignore
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
del f.__click_params__ # type: ignore
|
||||
params.extend(reversed(decorator_params))
|
||||
|
||||
if attrs.get("help") is None:
|
||||
attrs["help"] = f.__doc__
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
assert cls is not None
|
||||
assert not callable(name)
|
||||
|
||||
cmd = cls(
|
||||
name=name or f.__name__.lower().replace("_", "-"),
|
||||
callback=f,
|
||||
params=params,
|
||||
**attrs,
|
||||
)
|
||||
cmd.__doc__ = f.__doc__
|
||||
return cmd
|
||||
|
||||
if func is not None:
|
||||
return decorator(func)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def group(name=None, **attrs):
|
||||
GrpType = t.TypeVar("GrpType", bound=Group)
|
||||
|
||||
|
||||
# variant: no call, directly as decorator for a function.
|
||||
@t.overload
|
||||
def group(name: _AnyCallable) -> Group:
|
||||
...
|
||||
|
||||
|
||||
# variant: with positional name and with positional or keyword cls argument:
|
||||
# @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...)
|
||||
@t.overload
|
||||
def group(
|
||||
name: t.Optional[str],
|
||||
cls: t.Type[GrpType],
|
||||
**attrs: t.Any,
|
||||
) -> t.Callable[[_AnyCallable], GrpType]:
|
||||
...
|
||||
|
||||
|
||||
# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...)
|
||||
@t.overload
|
||||
def group(
|
||||
name: None = None,
|
||||
*,
|
||||
cls: t.Type[GrpType],
|
||||
**attrs: t.Any,
|
||||
) -> t.Callable[[_AnyCallable], GrpType]:
|
||||
...
|
||||
|
||||
|
||||
# variant: with optional string name, no cls argument provided.
|
||||
@t.overload
|
||||
def group(
|
||||
name: t.Optional[str] = ..., cls: None = None, **attrs: t.Any
|
||||
) -> t.Callable[[_AnyCallable], Group]:
|
||||
...
|
||||
|
||||
|
||||
def group(
|
||||
name: t.Union[str, _AnyCallable, None] = None,
|
||||
cls: t.Optional[t.Type[GrpType]] = None,
|
||||
**attrs: t.Any,
|
||||
) -> t.Union[Group, t.Callable[[_AnyCallable], t.Union[Group, GrpType]]]:
|
||||
"""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`.
|
||||
|
||||
.. versionchanged:: 8.1
|
||||
This decorator can be applied without parentheses.
|
||||
"""
|
||||
attrs.setdefault("cls", Group)
|
||||
return command(name, **attrs)
|
||||
if cls is None:
|
||||
cls = t.cast(t.Type[GrpType], Group)
|
||||
|
||||
if callable(name):
|
||||
return command(cls=cls, **attrs)(name)
|
||||
|
||||
return command(name, cls, **attrs)
|
||||
|
||||
|
||||
def _param_memo(f, param):
|
||||
def _param_memo(f: t.Callable[..., t.Any], param: Parameter) -> None:
|
||||
if isinstance(f, Command):
|
||||
f.params.append(param)
|
||||
else:
|
||||
if not hasattr(f, "__click_params__"):
|
||||
f.__click_params__ = []
|
||||
f.__click_params__.append(param)
|
||||
f.__click_params__ = [] # type: ignore
|
||||
|
||||
f.__click_params__.append(param) # type: ignore
|
||||
|
||||
|
||||
def argument(*param_decls, **attrs):
|
||||
def argument(
|
||||
*param_decls: str, cls: t.Optional[t.Type[Argument]] = None, **attrs: t.Any
|
||||
) -> t.Callable[[FC], FC]:
|
||||
"""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.
|
||||
|
||||
For the default argument class, refer to :class:`Argument` and
|
||||
:class:`Parameter` for descriptions of parameters.
|
||||
|
||||
:param cls: the argument class to instantiate. This defaults to
|
||||
:class:`Argument`.
|
||||
:param param_decls: Passed as positional arguments to the constructor of
|
||||
``cls``.
|
||||
:param attrs: Passed as keyword arguments to the constructor of ``cls``.
|
||||
"""
|
||||
if cls is None:
|
||||
cls = Argument
|
||||
|
||||
def decorator(f):
|
||||
ArgumentClass = attrs.pop("cls", Argument)
|
||||
_param_memo(f, ArgumentClass(param_decls, **attrs))
|
||||
def decorator(f: FC) -> FC:
|
||||
_param_memo(f, cls(param_decls, **attrs))
|
||||
return f
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def option(*param_decls, **attrs):
|
||||
def option(
|
||||
*param_decls: str, cls: t.Optional[t.Type[Option]] = None, **attrs: t.Any
|
||||
) -> t.Callable[[FC], FC]:
|
||||
"""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.
|
||||
|
||||
For the default option class, refer to :class:`Option` and
|
||||
:class:`Parameter` for descriptions of parameters.
|
||||
|
||||
:param cls: the option class to instantiate. This defaults to
|
||||
:class:`Option`.
|
||||
:param param_decls: Passed as positional arguments to the constructor of
|
||||
``cls``.
|
||||
:param attrs: Passed as keyword arguments to the constructor of ``cls``.
|
||||
"""
|
||||
if cls is None:
|
||||
cls = Option
|
||||
|
||||
def decorator(f):
|
||||
# Issue 926, copy attrs, so pre-defined options can re-use the same cls=
|
||||
option_attrs = attrs.copy()
|
||||
|
||||
if "help" in option_attrs:
|
||||
option_attrs["help"] = inspect.cleandoc(option_attrs["help"])
|
||||
OptionClass = option_attrs.pop("cls", Option)
|
||||
_param_memo(f, OptionClass(param_decls, **option_attrs))
|
||||
def decorator(f: FC) -> FC:
|
||||
_param_memo(f, cls(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.
|
||||
def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
|
||||
"""Add a ``--yes`` option which shows a prompt before continuing if
|
||||
not passed. If the prompt is declined, the program will exit.
|
||||
|
||||
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
|
||||
:param param_decls: One or more option names. Defaults to the single
|
||||
value ``"--yes"``.
|
||||
:param kwargs: Extra arguments are passed to :func:`option`.
|
||||
"""
|
||||
|
||||
def decorator(f):
|
||||
def callback(ctx, param, value):
|
||||
if not value:
|
||||
ctx.abort()
|
||||
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
||||
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)
|
||||
if not param_decls:
|
||||
param_decls = ("--yes",)
|
||||
|
||||
return decorator
|
||||
kwargs.setdefault("is_flag", True)
|
||||
kwargs.setdefault("callback", callback)
|
||||
kwargs.setdefault("expose_value", False)
|
||||
kwargs.setdefault("prompt", "Do you want to continue?")
|
||||
kwargs.setdefault("help", "Confirm the action without prompting.")
|
||||
return option(*param_decls, **kwargs)
|
||||
|
||||
|
||||
def password_option(*param_decls, **attrs):
|
||||
"""Shortcut for password prompts.
|
||||
def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
|
||||
"""Add a ``--password`` option which prompts for a password, hiding
|
||||
input and asking to enter the value again for confirmation.
|
||||
|
||||
This is equivalent to decorating a function with :func:`option` with
|
||||
the following parameters::
|
||||
:param param_decls: One or more option names. Defaults to the single
|
||||
value ``"--password"``.
|
||||
:param kwargs: Extra arguments are passed to :func:`option`.
|
||||
"""
|
||||
if not param_decls:
|
||||
param_decls = ("--password",)
|
||||
|
||||
@click.command()
|
||||
@click.option('--password', prompt=True, confirmation_prompt=True,
|
||||
hide_input=True)
|
||||
def changeadmin(password):
|
||||
pass
|
||||
kwargs.setdefault("prompt", True)
|
||||
kwargs.setdefault("confirmation_prompt", True)
|
||||
kwargs.setdefault("hide_input", True)
|
||||
return option(*param_decls, **kwargs)
|
||||
|
||||
|
||||
def version_option(
|
||||
version: t.Optional[str] = None,
|
||||
*param_decls: str,
|
||||
package_name: t.Optional[str] = None,
|
||||
prog_name: t.Optional[str] = None,
|
||||
message: t.Optional[str] = None,
|
||||
**kwargs: t.Any,
|
||||
) -> t.Callable[[FC], FC]:
|
||||
"""Add a ``--version`` option which immediately prints the version
|
||||
number and exits the program.
|
||||
|
||||
If ``version`` is not provided, Click will try to detect it using
|
||||
:func:`importlib.metadata.version` to get the version for the
|
||||
``package_name``. On Python < 3.8, the ``importlib_metadata``
|
||||
backport must be installed.
|
||||
|
||||
If ``package_name`` is not provided, Click will try to detect it by
|
||||
inspecting the stack frames. This will be used to detect the
|
||||
version, so it must match the name of the installed package.
|
||||
|
||||
:param version: The version number to show. If not provided, Click
|
||||
will try to detect it.
|
||||
:param param_decls: One or more option names. Defaults to the single
|
||||
value ``"--version"``.
|
||||
:param package_name: The package name to detect the version from. If
|
||||
not provided, Click will try to detect it.
|
||||
:param prog_name: The name of the CLI to show in the message. If not
|
||||
provided, it will be detected from the command.
|
||||
:param message: The message to show. The values ``%(prog)s``,
|
||||
``%(package)s``, and ``%(version)s`` are available. Defaults to
|
||||
``"%(prog)s, version %(version)s"``.
|
||||
:param kwargs: Extra arguments are passed to :func:`option`.
|
||||
:raise RuntimeError: ``version`` could not be detected.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Add the ``package_name`` parameter, and the ``%(package)s``
|
||||
value for messages.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Use :mod:`importlib.metadata` instead of ``pkg_resources``. The
|
||||
version is detected based on the package name, not the entry
|
||||
point name. The Python package name must match the installed
|
||||
package name, or be passed with ``package_name=``.
|
||||
"""
|
||||
if message is None:
|
||||
message = _("%(prog)s, version %(version)s")
|
||||
|
||||
if version is None and package_name is None:
|
||||
frame = inspect.currentframe()
|
||||
f_back = frame.f_back if frame is not None else None
|
||||
f_globals = f_back.f_globals if f_back is not None else None
|
||||
# break reference cycle
|
||||
# https://docs.python.org/3/library/inspect.html#the-interpreter-stack
|
||||
del frame
|
||||
|
||||
if f_globals is not None:
|
||||
package_name = f_globals.get("__name__")
|
||||
|
||||
if package_name == "__main__":
|
||||
package_name = f_globals.get("__package__")
|
||||
|
||||
if package_name:
|
||||
package_name = package_name.partition(".")[0]
|
||||
|
||||
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
||||
if not value or ctx.resilient_parsing:
|
||||
return
|
||||
|
||||
nonlocal prog_name
|
||||
nonlocal version
|
||||
|
||||
if prog_name is None:
|
||||
prog_name = ctx.find_root().info_name
|
||||
|
||||
if version is None and package_name is not None:
|
||||
metadata: t.Optional[types.ModuleType]
|
||||
|
||||
try:
|
||||
from importlib import metadata # type: ignore
|
||||
except ImportError:
|
||||
# Python < 3.8
|
||||
import importlib_metadata as metadata # type: ignore
|
||||
|
||||
try:
|
||||
version = metadata.version(package_name) # type: ignore
|
||||
except metadata.PackageNotFoundError: # type: ignore
|
||||
raise RuntimeError(
|
||||
f"{package_name!r} is not installed. Try passing"
|
||||
" 'package_name' instead."
|
||||
) from None
|
||||
|
||||
if version is None:
|
||||
raise RuntimeError(
|
||||
f"Could not determine the version for {package_name!r} automatically."
|
||||
)
|
||||
|
||||
echo(
|
||||
message % {"prog": prog_name, "package": package_name, "version": version},
|
||||
color=ctx.color,
|
||||
)
|
||||
ctx.exit()
|
||||
|
||||
if not param_decls:
|
||||
param_decls = ("--version",)
|
||||
|
||||
kwargs.setdefault("is_flag", True)
|
||||
kwargs.setdefault("expose_value", False)
|
||||
kwargs.setdefault("is_eager", True)
|
||||
kwargs.setdefault("help", _("Show the version and exit."))
|
||||
kwargs["callback"] = callback
|
||||
return option(*param_decls, **kwargs)
|
||||
|
||||
|
||||
def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
|
||||
"""Add a ``--help`` option which immediately prints the help page
|
||||
and exits the program.
|
||||
|
||||
This is usually unnecessary, as the ``--help`` option is added to
|
||||
each command automatically unless ``add_help_option=False`` is
|
||||
passed.
|
||||
|
||||
:param param_decls: One or more option names. Defaults to the single
|
||||
value ``"--help"``.
|
||||
:param kwargs: Extra arguments are passed to :func:`option`.
|
||||
"""
|
||||
|
||||
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)
|
||||
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
||||
if not value or ctx.resilient_parsing:
|
||||
return
|
||||
|
||||
return decorator
|
||||
echo(ctx.get_help(), color=ctx.color)
|
||||
ctx.exit()
|
||||
|
||||
if not param_decls:
|
||||
param_decls = ("--help",)
|
||||
|
||||
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:
|
||||
if hasattr(sys, "_getframe"):
|
||||
module = sys._getframe(1).f_globals.get("__name__")
|
||||
else:
|
||||
module = ""
|
||||
|
||||
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 _, 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
|
||||
kwargs.setdefault("is_flag", True)
|
||||
kwargs.setdefault("expose_value", False)
|
||||
kwargs.setdefault("is_eager", True)
|
||||
kwargs.setdefault("help", _("Show this message and exit."))
|
||||
kwargs["callback"] = callback
|
||||
return option(*param_decls, **kwargs)
|
||||
|
205
third_party/python/click/click/exceptions.py
vendored
205
third_party/python/click/click/exceptions.py
vendored
@ -1,45 +1,47 @@
|
||||
from ._compat import filename_to_ui
|
||||
import typing as t
|
||||
from gettext import gettext as _
|
||||
from gettext import ngettext
|
||||
|
||||
from ._compat import get_text_stderr
|
||||
from ._compat import PY2
|
||||
from .utils import echo
|
||||
from .utils import format_filename
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from .core import Command
|
||||
from .core import Context
|
||||
from .core import Parameter
|
||||
|
||||
|
||||
def _join_param_hints(param_hint):
|
||||
if isinstance(param_hint, (tuple, list)):
|
||||
def _join_param_hints(
|
||||
param_hint: t.Optional[t.Union[t.Sequence[str], str]]
|
||||
) -> t.Optional[str]:
|
||||
if param_hint is not None and not isinstance(param_hint, str):
|
||||
return " / ".join(repr(x) for x in param_hint)
|
||||
|
||||
return param_hint
|
||||
|
||||
|
||||
class ClickException(Exception):
|
||||
"""An exception that Click can handle and show to the user."""
|
||||
|
||||
#: The exit code for this exception
|
||||
#: The exit code for this exception.
|
||||
exit_code = 1
|
||||
|
||||
def __init__(self, message):
|
||||
ctor_msg = message
|
||||
if PY2:
|
||||
if ctor_msg is not None:
|
||||
ctor_msg = ctor_msg.encode("utf-8")
|
||||
Exception.__init__(self, ctor_msg)
|
||||
def __init__(self, message: str) -> None:
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
|
||||
def format_message(self):
|
||||
def format_message(self) -> str:
|
||||
return self.message
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return self.message
|
||||
|
||||
if PY2:
|
||||
__unicode__ = __str__
|
||||
|
||||
def __str__(self):
|
||||
return self.message.encode("utf-8")
|
||||
|
||||
def show(self, file=None):
|
||||
def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None:
|
||||
if file is None:
|
||||
file = get_text_stderr()
|
||||
echo("Error: {}".format(self.format_message()), file=file)
|
||||
|
||||
echo(_("Error: {message}").format(message=self.format_message()), file=file)
|
||||
|
||||
|
||||
class UsageError(ClickException):
|
||||
@ -53,24 +55,32 @@ class UsageError(ClickException):
|
||||
|
||||
exit_code = 2
|
||||
|
||||
def __init__(self, message, ctx=None):
|
||||
ClickException.__init__(self, message)
|
||||
def __init__(self, message: str, ctx: t.Optional["Context"] = None) -> None:
|
||||
super().__init__(message)
|
||||
self.ctx = ctx
|
||||
self.cmd = self.ctx.command if self.ctx else None
|
||||
self.cmd: t.Optional["Command"] = self.ctx.command if self.ctx else None
|
||||
|
||||
def show(self, file=None):
|
||||
def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None:
|
||||
if file is None:
|
||||
file = get_text_stderr()
|
||||
color = None
|
||||
hint = ""
|
||||
if self.cmd is not None and self.cmd.get_help_option(self.ctx) is not None:
|
||||
hint = "Try '{} {}' for help.\n".format(
|
||||
self.ctx.command_path, self.ctx.help_option_names[0]
|
||||
if (
|
||||
self.ctx is not None
|
||||
and self.ctx.command.get_help_option(self.ctx) is not None
|
||||
):
|
||||
hint = _("Try '{command} {option}' for help.").format(
|
||||
command=self.ctx.command_path, option=self.ctx.help_option_names[0]
|
||||
)
|
||||
hint = f"{hint}\n"
|
||||
if self.ctx is not None:
|
||||
color = self.ctx.color
|
||||
echo("{}\n{}".format(self.ctx.get_usage(), hint), file=file, color=color)
|
||||
echo("Error: {}".format(self.format_message()), file=file, color=color)
|
||||
echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color)
|
||||
echo(
|
||||
_("Error: {message}").format(message=self.format_message()),
|
||||
file=file,
|
||||
color=color,
|
||||
)
|
||||
|
||||
|
||||
class BadParameter(UsageError):
|
||||
@ -91,21 +101,28 @@ class BadParameter(UsageError):
|
||||
each item is quoted and separated.
|
||||
"""
|
||||
|
||||
def __init__(self, message, ctx=None, param=None, param_hint=None):
|
||||
UsageError.__init__(self, message, ctx)
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
ctx: t.Optional["Context"] = None,
|
||||
param: t.Optional["Parameter"] = None,
|
||||
param_hint: t.Optional[str] = None,
|
||||
) -> None:
|
||||
super().__init__(message, ctx)
|
||||
self.param = param
|
||||
self.param_hint = param_hint
|
||||
|
||||
def format_message(self):
|
||||
def format_message(self) -> str:
|
||||
if self.param_hint is not None:
|
||||
param_hint = self.param_hint
|
||||
elif self.param is not None:
|
||||
param_hint = self.param.get_error_hint(self.ctx)
|
||||
param_hint = self.param.get_error_hint(self.ctx) # type: ignore
|
||||
else:
|
||||
return "Invalid value: {}".format(self.message)
|
||||
param_hint = _join_param_hints(param_hint)
|
||||
return _("Invalid value: {message}").format(message=self.message)
|
||||
|
||||
return "Invalid value for {}: {}".format(param_hint, self.message)
|
||||
return _("Invalid value for {param_hint}: {message}").format(
|
||||
param_hint=_join_param_hints(param_hint), message=self.message
|
||||
)
|
||||
|
||||
|
||||
class MissingParameter(BadParameter):
|
||||
@ -121,19 +138,26 @@ class MissingParameter(BadParameter):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, message=None, ctx=None, param=None, param_hint=None, param_type=None
|
||||
):
|
||||
BadParameter.__init__(self, message, ctx, param, param_hint)
|
||||
self,
|
||||
message: t.Optional[str] = None,
|
||||
ctx: t.Optional["Context"] = None,
|
||||
param: t.Optional["Parameter"] = None,
|
||||
param_hint: t.Optional[str] = None,
|
||||
param_type: t.Optional[str] = None,
|
||||
) -> None:
|
||||
super().__init__(message or "", ctx, param, param_hint)
|
||||
self.param_type = param_type
|
||||
|
||||
def format_message(self):
|
||||
def format_message(self) -> str:
|
||||
if self.param_hint is not None:
|
||||
param_hint = self.param_hint
|
||||
param_hint: t.Optional[str] = self.param_hint
|
||||
elif self.param is not None:
|
||||
param_hint = self.param.get_error_hint(self.ctx)
|
||||
param_hint = self.param.get_error_hint(self.ctx) # type: ignore
|
||||
else:
|
||||
param_hint = None
|
||||
|
||||
param_hint = _join_param_hints(param_hint)
|
||||
param_hint = f" {param_hint}" if param_hint else ""
|
||||
|
||||
param_type = self.param_type
|
||||
if param_type is None and self.param is not None:
|
||||
@ -144,30 +168,31 @@ class MissingParameter(BadParameter):
|
||||
msg_extra = self.param.type.get_missing_message(self.param)
|
||||
if msg_extra:
|
||||
if msg:
|
||||
msg += ". {}".format(msg_extra)
|
||||
msg += f". {msg_extra}"
|
||||
else:
|
||||
msg = msg_extra
|
||||
|
||||
return "Missing {}{}{}{}".format(
|
||||
param_type,
|
||||
" {}".format(param_hint) if param_hint else "",
|
||||
". " if msg else ".",
|
||||
msg or "",
|
||||
)
|
||||
msg = f" {msg}" if msg else ""
|
||||
|
||||
def __str__(self):
|
||||
if self.message is None:
|
||||
# Translate param_type for known types.
|
||||
if param_type == "argument":
|
||||
missing = _("Missing argument")
|
||||
elif param_type == "option":
|
||||
missing = _("Missing option")
|
||||
elif param_type == "parameter":
|
||||
missing = _("Missing parameter")
|
||||
else:
|
||||
missing = _("Missing {param_type}").format(param_type=param_type)
|
||||
|
||||
return f"{missing}{param_hint}.{msg}"
|
||||
|
||||
def __str__(self) -> str:
|
||||
if not self.message:
|
||||
param_name = self.param.name if self.param else None
|
||||
return "missing parameter: {}".format(param_name)
|
||||
return _("Missing parameter: {param_name}").format(param_name=param_name)
|
||||
else:
|
||||
return self.message
|
||||
|
||||
if PY2:
|
||||
__unicode__ = __str__
|
||||
|
||||
def __str__(self):
|
||||
return self.__unicode__().encode("utf-8")
|
||||
|
||||
|
||||
class NoSuchOption(UsageError):
|
||||
"""Raised if click attempted to handle an option that does not
|
||||
@ -176,22 +201,31 @@ class NoSuchOption(UsageError):
|
||||
.. versionadded:: 4.0
|
||||
"""
|
||||
|
||||
def __init__(self, option_name, message=None, possibilities=None, ctx=None):
|
||||
def __init__(
|
||||
self,
|
||||
option_name: str,
|
||||
message: t.Optional[str] = None,
|
||||
possibilities: t.Optional[t.Sequence[str]] = None,
|
||||
ctx: t.Optional["Context"] = None,
|
||||
) -> None:
|
||||
if message is None:
|
||||
message = "no such option: {}".format(option_name)
|
||||
UsageError.__init__(self, message, ctx)
|
||||
message = _("No such option: {name}").format(name=option_name)
|
||||
|
||||
super().__init__(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 {}?".format(self.possibilities[0]))
|
||||
else:
|
||||
possibilities = sorted(self.possibilities)
|
||||
bits.append("(Possible options: {})".format(", ".join(possibilities)))
|
||||
return " ".join(bits)
|
||||
def format_message(self) -> str:
|
||||
if not self.possibilities:
|
||||
return self.message
|
||||
|
||||
possibility_str = ", ".join(sorted(self.possibilities))
|
||||
suggest = ngettext(
|
||||
"Did you mean {possibility}?",
|
||||
"(Possible options: {possibilities})",
|
||||
len(self.possibilities),
|
||||
).format(possibility=possibility_str, possibilities=possibility_str)
|
||||
return f"{self.message} {suggest}"
|
||||
|
||||
|
||||
class BadOptionUsage(UsageError):
|
||||
@ -204,8 +238,10 @@ class BadOptionUsage(UsageError):
|
||||
:param option_name: the name of the option being used incorrectly.
|
||||
"""
|
||||
|
||||
def __init__(self, option_name, message, ctx=None):
|
||||
UsageError.__init__(self, message, ctx)
|
||||
def __init__(
|
||||
self, option_name: str, message: str, ctx: t.Optional["Context"] = None
|
||||
) -> None:
|
||||
super().__init__(message, ctx)
|
||||
self.option_name = option_name
|
||||
|
||||
|
||||
@ -217,23 +253,22 @@ class BadArgumentUsage(UsageError):
|
||||
.. 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)
|
||||
def __init__(self, filename: str, hint: t.Optional[str] = None) -> None:
|
||||
if hint is None:
|
||||
hint = "unknown error"
|
||||
ClickException.__init__(self, hint)
|
||||
self.ui_filename = ui_filename
|
||||
hint = _("unknown error")
|
||||
|
||||
super().__init__(hint)
|
||||
self.ui_filename: str = format_filename(filename)
|
||||
self.filename = filename
|
||||
|
||||
def format_message(self):
|
||||
return "Could not open file {}: {}".format(self.ui_filename, self.message)
|
||||
def format_message(self) -> str:
|
||||
return _("Could not open file {filename!r}: {message}").format(
|
||||
filename=self.ui_filename, message=self.message
|
||||
)
|
||||
|
||||
|
||||
class Abort(RuntimeError):
|
||||
@ -249,5 +284,5 @@ class Exit(RuntimeError):
|
||||
|
||||
__slots__ = ("exit_code",)
|
||||
|
||||
def __init__(self, code=0):
|
||||
self.exit_code = code
|
||||
def __init__(self, code: int = 0) -> None:
|
||||
self.exit_code: int = code
|
||||
|
110
third_party/python/click/click/formatting.py
vendored
110
third_party/python/click/click/formatting.py
vendored
@ -1,30 +1,38 @@
|
||||
import typing as t
|
||||
from contextlib import contextmanager
|
||||
from gettext import gettext as _
|
||||
|
||||
from ._compat import term_len
|
||||
from .parser import split_opt
|
||||
from .termui import get_terminal_size
|
||||
|
||||
# Can force a width. This is used by the test system
|
||||
FORCED_WIDTH = None
|
||||
FORCED_WIDTH: t.Optional[int] = None
|
||||
|
||||
|
||||
def measure_table(rows):
|
||||
widths = {}
|
||||
def measure_table(rows: t.Iterable[t.Tuple[str, str]]) -> t.Tuple[int, ...]:
|
||||
widths: t.Dict[int, int] = {}
|
||||
|
||||
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):
|
||||
def iter_rows(
|
||||
rows: t.Iterable[t.Tuple[str, str]], col_count: int
|
||||
) -> t.Iterator[t.Tuple[str, ...]]:
|
||||
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
|
||||
):
|
||||
text: str,
|
||||
width: int = 78,
|
||||
initial_indent: str = "",
|
||||
subsequent_indent: str = "",
|
||||
preserve_paragraphs: bool = False,
|
||||
) -> str:
|
||||
"""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
|
||||
@ -55,11 +63,11 @@ def wrap_text(
|
||||
if not preserve_paragraphs:
|
||||
return wrapper.fill(text)
|
||||
|
||||
p = []
|
||||
buf = []
|
||||
p: t.List[t.Tuple[int, bool, str]] = []
|
||||
buf: t.List[str] = []
|
||||
indent = None
|
||||
|
||||
def _flush_par():
|
||||
def _flush_par() -> None:
|
||||
if not buf:
|
||||
return
|
||||
if buf[0].strip() == "\b":
|
||||
@ -91,7 +99,7 @@ def wrap_text(
|
||||
return "\n\n".join(rv)
|
||||
|
||||
|
||||
class HelpFormatter(object):
|
||||
class HelpFormatter:
|
||||
"""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.
|
||||
@ -103,38 +111,51 @@ class HelpFormatter(object):
|
||||
width clamped to a maximum of 78.
|
||||
"""
|
||||
|
||||
def __init__(self, indent_increment=2, width=None, max_width=None):
|
||||
def __init__(
|
||||
self,
|
||||
indent_increment: int = 2,
|
||||
width: t.Optional[int] = None,
|
||||
max_width: t.Optional[int] = None,
|
||||
) -> None:
|
||||
import shutil
|
||||
|
||||
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)
|
||||
width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50)
|
||||
self.width = width
|
||||
self.current_indent = 0
|
||||
self.buffer = []
|
||||
self.buffer: t.List[str] = []
|
||||
|
||||
def write(self, string):
|
||||
def write(self, string: str) -> None:
|
||||
"""Writes a unicode string into the internal buffer."""
|
||||
self.buffer.append(string)
|
||||
|
||||
def indent(self):
|
||||
def indent(self) -> None:
|
||||
"""Increases the indentation."""
|
||||
self.current_indent += self.indent_increment
|
||||
|
||||
def dedent(self):
|
||||
def dedent(self) -> None:
|
||||
"""Decreases the indentation."""
|
||||
self.current_indent -= self.indent_increment
|
||||
|
||||
def write_usage(self, prog, args="", prefix="Usage: "):
|
||||
def write_usage(
|
||||
self, prog: str, args: str = "", prefix: t.Optional[str] = None
|
||||
) -> None:
|
||||
"""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.
|
||||
:param prefix: The prefix for the first line. Defaults to
|
||||
``"Usage: "``.
|
||||
"""
|
||||
usage_prefix = "{:>{w}}{} ".format(prefix, prog, w=self.current_indent)
|
||||
if prefix is None:
|
||||
prefix = f"{_('Usage:')} "
|
||||
|
||||
usage_prefix = f"{prefix:>{self.current_indent}}{prog} "
|
||||
text_width = self.width - self.current_indent
|
||||
|
||||
if text_width >= (term_len(usage_prefix) + 20):
|
||||
@ -161,25 +182,24 @@ class HelpFormatter(object):
|
||||
|
||||
self.write("\n")
|
||||
|
||||
def write_heading(self, heading):
|
||||
def write_heading(self, heading: str) -> None:
|
||||
"""Writes a heading into the buffer."""
|
||||
self.write("{:>{w}}{}:\n".format("", heading, w=self.current_indent))
|
||||
self.write(f"{'':>{self.current_indent}}{heading}:\n")
|
||||
|
||||
def write_paragraph(self):
|
||||
def write_paragraph(self) -> None:
|
||||
"""Writes a paragraph into the buffer."""
|
||||
if self.buffer:
|
||||
self.write("\n")
|
||||
|
||||
def write_text(self, text):
|
||||
def write_text(self, text: str) -> None:
|
||||
"""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,
|
||||
self.width,
|
||||
initial_indent=indent,
|
||||
subsequent_indent=indent,
|
||||
preserve_paragraphs=True,
|
||||
@ -187,7 +207,12 @@ class HelpFormatter(object):
|
||||
)
|
||||
self.write("\n")
|
||||
|
||||
def write_dl(self, rows, col_max=30, col_spacing=2):
|
||||
def write_dl(
|
||||
self,
|
||||
rows: t.Sequence[t.Tuple[str, str]],
|
||||
col_max: int = 30,
|
||||
col_spacing: int = 2,
|
||||
) -> None:
|
||||
"""Writes a definition list into the buffer. This is how options
|
||||
and commands are usually formatted.
|
||||
|
||||
@ -204,7 +229,7 @@ class HelpFormatter(object):
|
||||
first_col = min(widths[0], col_max) + col_spacing
|
||||
|
||||
for first, second in iter_rows(rows, len(widths)):
|
||||
self.write("{:>{w}}{}".format("", first, w=self.current_indent))
|
||||
self.write(f"{'':>{self.current_indent}}{first}")
|
||||
if not second:
|
||||
self.write("\n")
|
||||
continue
|
||||
@ -219,23 +244,15 @@ class HelpFormatter(object):
|
||||
lines = wrapped_text.splitlines()
|
||||
|
||||
if lines:
|
||||
self.write("{}\n".format(lines[0]))
|
||||
self.write(f"{lines[0]}\n")
|
||||
|
||||
for line in lines[1:]:
|
||||
self.write(
|
||||
"{:>{w}}{}\n".format(
|
||||
"", line, w=first_col + self.current_indent
|
||||
)
|
||||
)
|
||||
|
||||
if len(lines) > 1:
|
||||
# separate long help from next option
|
||||
self.write("\n")
|
||||
self.write(f"{'':>{first_col + self.current_indent}}{line}\n")
|
||||
else:
|
||||
self.write("\n")
|
||||
|
||||
@contextmanager
|
||||
def section(self, name):
|
||||
def section(self, name: str) -> t.Iterator[None]:
|
||||
"""Helpful context manager that writes a paragraph, a heading,
|
||||
and the indents.
|
||||
|
||||
@ -250,7 +267,7 @@ class HelpFormatter(object):
|
||||
self.dedent()
|
||||
|
||||
@contextmanager
|
||||
def indentation(self):
|
||||
def indentation(self) -> t.Iterator[None]:
|
||||
"""A context manager that increases the indentation."""
|
||||
self.indent()
|
||||
try:
|
||||
@ -258,12 +275,12 @@ class HelpFormatter(object):
|
||||
finally:
|
||||
self.dedent()
|
||||
|
||||
def getvalue(self):
|
||||
def getvalue(self) -> str:
|
||||
"""Returns the buffer contents."""
|
||||
return "".join(self.buffer)
|
||||
|
||||
|
||||
def join_options(options):
|
||||
def join_options(options: t.Sequence[str]) -> t.Tuple[str, bool]:
|
||||
"""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
|
||||
@ -271,13 +288,14 @@ def join_options(options):
|
||||
"""
|
||||
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
|
||||
return ", ".join(x[1] for x in rv), any_prefix_is_slash
|
||||
|
37
third_party/python/click/click/globals.py
vendored
37
third_party/python/click/click/globals.py
vendored
@ -1,9 +1,24 @@
|
||||
import typing as t
|
||||
from threading import local
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
from .core import Context
|
||||
|
||||
_local = local()
|
||||
|
||||
|
||||
def get_current_context(silent=False):
|
||||
@t.overload
|
||||
def get_current_context(silent: "te.Literal[False]" = False) -> "Context":
|
||||
...
|
||||
|
||||
|
||||
@t.overload
|
||||
def get_current_context(silent: bool = ...) -> t.Optional["Context"]:
|
||||
...
|
||||
|
||||
|
||||
def get_current_context(silent: bool = False) -> t.Optional["Context"]:
|
||||
"""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
|
||||
@ -19,29 +34,35 @@ def get_current_context(silent=False):
|
||||
:exc:`RuntimeError`.
|
||||
"""
|
||||
try:
|
||||
return _local.stack[-1]
|
||||
except (AttributeError, IndexError):
|
||||
return t.cast("Context", _local.stack[-1])
|
||||
except (AttributeError, IndexError) as e:
|
||||
if not silent:
|
||||
raise RuntimeError("There is no active click context.")
|
||||
raise RuntimeError("There is no active click context.") from e
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def push_context(ctx):
|
||||
def push_context(ctx: "Context") -> None:
|
||||
"""Pushes a new context to the current stack."""
|
||||
_local.__dict__.setdefault("stack", []).append(ctx)
|
||||
|
||||
|
||||
def pop_context():
|
||||
def pop_context() -> None:
|
||||
"""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
|
||||
def resolve_color_default(color: t.Optional[bool] = None) -> t.Optional[bool]:
|
||||
"""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
|
||||
|
||||
return None
|
||||
|
301
third_party/python/click/click/parser.py
vendored
301
third_party/python/click/click/parser.py
vendored
@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
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
|
||||
@ -18,16 +17,38 @@ by the Python Software Foundation. This is limited to code in parser.py.
|
||||
Copyright 2001-2006 Gregory P. Ward. All rights reserved.
|
||||
Copyright 2002-2006 Python Software Foundation. All rights reserved.
|
||||
"""
|
||||
import re
|
||||
# This code uses parts of optparse written by Gregory P. Ward and
|
||||
# maintained by the Python Software Foundation.
|
||||
# Copyright 2001-2006 Gregory P. Ward
|
||||
# Copyright 2002-2006 Python Software Foundation
|
||||
import typing as t
|
||||
from collections import deque
|
||||
from gettext import gettext as _
|
||||
from gettext import ngettext
|
||||
|
||||
from .exceptions import BadArgumentUsage
|
||||
from .exceptions import BadOptionUsage
|
||||
from .exceptions import NoSuchOption
|
||||
from .exceptions import UsageError
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
from .core import Argument as CoreArgument
|
||||
from .core import Context
|
||||
from .core import Option as CoreOption
|
||||
from .core import Parameter as CoreParameter
|
||||
|
||||
def _unpack_args(args, nargs_spec):
|
||||
V = t.TypeVar("V")
|
||||
|
||||
# Sentinel value that indicates an option was passed as a flag without a
|
||||
# value but is not a flag option. Option.consume_value uses this to
|
||||
# prompt or use the flag_value.
|
||||
_flag_needs_value = object()
|
||||
|
||||
|
||||
def _unpack_args(
|
||||
args: t.Sequence[str], nargs_spec: t.Sequence[int]
|
||||
) -> t.Tuple[t.Sequence[t.Union[str, t.Sequence[t.Optional[str]], None]], t.List[str]]:
|
||||
"""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.
|
||||
@ -39,10 +60,10 @@ def _unpack_args(args, nargs_spec):
|
||||
"""
|
||||
args = deque(args)
|
||||
nargs_spec = deque(nargs_spec)
|
||||
rv = []
|
||||
spos = None
|
||||
rv: t.List[t.Union[str, t.Tuple[t.Optional[str], ...], None]] = []
|
||||
spos: t.Optional[int] = None
|
||||
|
||||
def _fetch(c):
|
||||
def _fetch(c: "te.Deque[V]") -> t.Optional[V]:
|
||||
try:
|
||||
if spos is None:
|
||||
return c.popleft()
|
||||
@ -53,18 +74,25 @@ def _unpack_args(args, nargs_spec):
|
||||
|
||||
while nargs_spec:
|
||||
nargs = _fetch(nargs_spec)
|
||||
|
||||
if nargs is None:
|
||||
continue
|
||||
|
||||
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)
|
||||
|
||||
@ -78,13 +106,7 @@ def _unpack_args(args, nargs_spec):
|
||||
return tuple(rv), list(args)
|
||||
|
||||
|
||||
def _error_opt_args(nargs, opt):
|
||||
if nargs == 1:
|
||||
raise BadOptionUsage(opt, "{} option requires an argument".format(opt))
|
||||
raise BadOptionUsage(opt, "{} option requires {} arguments".format(opt, nargs))
|
||||
|
||||
|
||||
def split_opt(opt):
|
||||
def split_opt(opt: str) -> t.Tuple[str, str]:
|
||||
first = opt[:1]
|
||||
if first.isalnum():
|
||||
return "", opt
|
||||
@ -93,42 +115,65 @@ def split_opt(opt):
|
||||
return first, opt[1:]
|
||||
|
||||
|
||||
def normalize_opt(opt, ctx):
|
||||
def normalize_opt(opt: str, ctx: t.Optional["Context"]) -> str:
|
||||
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)
|
||||
return f"{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"('([^'\\]*(?:\\.[^'\\]*)*)'|\"([^\"\\]*(?:\\.[^\"\\]*)*)\"|\S+)\s*",
|
||||
string,
|
||||
re.S,
|
||||
def split_arg_string(string: str) -> t.List[str]:
|
||||
"""Split an argument string as with :func:`shlex.split`, but don't
|
||||
fail if the string is incomplete. Ignores a missing closing quote or
|
||||
incomplete escape sequence and uses the partial token as-is.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
split_arg_string("example 'my file")
|
||||
["example", "my file"]
|
||||
|
||||
split_arg_string("example my\\")
|
||||
["example", "my"]
|
||||
|
||||
:param string: String to split.
|
||||
"""
|
||||
import shlex
|
||||
|
||||
lex = shlex.shlex(string, posix=True)
|
||||
lex.whitespace_split = True
|
||||
lex.commenters = ""
|
||||
out = []
|
||||
|
||||
try:
|
||||
for token in lex:
|
||||
out.append(token)
|
||||
except ValueError:
|
||||
# Raised when end-of-string is reached in an invalid state. Use
|
||||
# the partial token as-is. The quote or escape character is in
|
||||
# lex.state, not lex.token.
|
||||
out.append(lex.token)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class Option:
|
||||
def __init__(
|
||||
self,
|
||||
obj: "CoreOption",
|
||||
opts: t.Sequence[str],
|
||||
dest: t.Optional[str],
|
||||
action: t.Optional[str] = None,
|
||||
nargs: int = 1,
|
||||
const: t.Optional[t.Any] = None,
|
||||
):
|
||||
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()
|
||||
self.prefixes: t.Set[str] = set()
|
||||
|
||||
for opt in opts:
|
||||
prefix, value = split_opt(opt)
|
||||
if not prefix:
|
||||
raise ValueError("Invalid start character for option ({})".format(opt))
|
||||
raise ValueError(f"Invalid start character for option ({opt})")
|
||||
self.prefixes.add(prefix[0])
|
||||
if len(prefix) == 1 and len(value) == 1:
|
||||
self._short_opts.append(opt)
|
||||
@ -146,53 +191,66 @@ class Option(object):
|
||||
self.obj = obj
|
||||
|
||||
@property
|
||||
def takes_value(self):
|
||||
def takes_value(self) -> bool:
|
||||
return self.action in ("store", "append")
|
||||
|
||||
def process(self, value, state):
|
||||
def process(self, value: t.Any, state: "ParsingState") -> None:
|
||||
if self.action == "store":
|
||||
state.opts[self.dest] = value
|
||||
state.opts[self.dest] = value # type: ignore
|
||||
elif self.action == "store_const":
|
||||
state.opts[self.dest] = self.const
|
||||
state.opts[self.dest] = self.const # type: ignore
|
||||
elif self.action == "append":
|
||||
state.opts.setdefault(self.dest, []).append(value)
|
||||
state.opts.setdefault(self.dest, []).append(value) # type: ignore
|
||||
elif self.action == "append_const":
|
||||
state.opts.setdefault(self.dest, []).append(self.const)
|
||||
state.opts.setdefault(self.dest, []).append(self.const) # type: ignore
|
||||
elif self.action == "count":
|
||||
state.opts[self.dest] = state.opts.get(self.dest, 0) + 1
|
||||
state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore
|
||||
else:
|
||||
raise ValueError("unknown action '{}'".format(self.action))
|
||||
raise ValueError(f"unknown action '{self.action}'")
|
||||
state.order.append(self.obj)
|
||||
|
||||
|
||||
class Argument(object):
|
||||
def __init__(self, dest, nargs=1, obj=None):
|
||||
class Argument:
|
||||
def __init__(self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1):
|
||||
self.dest = dest
|
||||
self.nargs = nargs
|
||||
self.obj = obj
|
||||
|
||||
def process(self, value, state):
|
||||
def process(
|
||||
self,
|
||||
value: t.Union[t.Optional[str], t.Sequence[t.Optional[str]]],
|
||||
state: "ParsingState",
|
||||
) -> None:
|
||||
if self.nargs > 1:
|
||||
assert value is not None
|
||||
holes = sum(1 for x in value if x is None)
|
||||
if holes == len(value):
|
||||
value = None
|
||||
elif holes != 0:
|
||||
raise BadArgumentUsage(
|
||||
"argument {} takes {} values".format(self.dest, self.nargs)
|
||||
_("Argument {name!r} takes {nargs} values.").format(
|
||||
name=self.dest, nargs=self.nargs
|
||||
)
|
||||
)
|
||||
state.opts[self.dest] = value
|
||||
|
||||
if self.nargs == -1 and self.obj.envvar is not None and value == ():
|
||||
# Replace empty tuple with None so that a value from the
|
||||
# environment may be tried.
|
||||
value = None
|
||||
|
||||
state.opts[self.dest] = value # type: ignore
|
||||
state.order.append(self.obj)
|
||||
|
||||
|
||||
class ParsingState(object):
|
||||
def __init__(self, rargs):
|
||||
self.opts = {}
|
||||
self.largs = []
|
||||
class ParsingState:
|
||||
def __init__(self, rargs: t.List[str]) -> None:
|
||||
self.opts: t.Dict[str, t.Any] = {}
|
||||
self.largs: t.List[str] = []
|
||||
self.rargs = rargs
|
||||
self.order = []
|
||||
self.order: t.List["CoreParameter"] = []
|
||||
|
||||
|
||||
class OptionParser(object):
|
||||
class OptionParser:
|
||||
"""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
|
||||
@ -206,7 +264,7 @@ class OptionParser(object):
|
||||
should go with.
|
||||
"""
|
||||
|
||||
def __init__(self, ctx=None):
|
||||
def __init__(self, ctx: t.Optional["Context"] = None) -> None:
|
||||
#: The :class:`~click.Context` for this parser. This might be
|
||||
#: `None` for some advanced use cases.
|
||||
self.ctx = ctx
|
||||
@ -214,50 +272,60 @@ class OptionParser(object):
|
||||
#: 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
|
||||
self.allow_interspersed_args: bool = 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
|
||||
self.ignore_unknown_options: bool = 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 = {"-", "--"}
|
||||
self._args = []
|
||||
|
||||
def add_option(self, opts, dest, action=None, nargs=1, const=None, obj=None):
|
||||
self._short_opt: t.Dict[str, Option] = {}
|
||||
self._long_opt: t.Dict[str, Option] = {}
|
||||
self._opt_prefixes = {"-", "--"}
|
||||
self._args: t.List[Argument] = []
|
||||
|
||||
def add_option(
|
||||
self,
|
||||
obj: "CoreOption",
|
||||
opts: t.Sequence[str],
|
||||
dest: t.Optional[str],
|
||||
action: t.Optional[str] = None,
|
||||
nargs: int = 1,
|
||||
const: t.Optional[t.Any] = None,
|
||||
) -> 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``.
|
||||
``append``, ``append_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)
|
||||
option = Option(obj, opts, dest, action=action, nargs=nargs, const=const)
|
||||
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):
|
||||
def add_argument(
|
||||
self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1
|
||||
) -> 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))
|
||||
self._args.append(Argument(obj, dest=dest, nargs=nargs))
|
||||
|
||||
def parse_args(self, args):
|
||||
def parse_args(
|
||||
self, args: t.List[str]
|
||||
) -> t.Tuple[t.Dict[str, t.Any], t.List[str], t.List["CoreParameter"]]:
|
||||
"""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
|
||||
@ -273,7 +341,7 @@ class OptionParser(object):
|
||||
raise
|
||||
return state.opts, state.largs, state.order
|
||||
|
||||
def _process_args_for_args(self, state):
|
||||
def _process_args_for_args(self, state: ParsingState) -> None:
|
||||
pargs, args = _unpack_args(
|
||||
state.largs + state.rargs, [x.nargs for x in self._args]
|
||||
)
|
||||
@ -284,7 +352,7 @@ class OptionParser(object):
|
||||
state.largs = args
|
||||
state.rargs = []
|
||||
|
||||
def _process_args_for_options(self, state):
|
||||
def _process_args_for_options(self, state: ParsingState) -> None:
|
||||
while state.rargs:
|
||||
arg = state.rargs.pop(0)
|
||||
arglen = len(arg)
|
||||
@ -320,9 +388,13 @@ class OptionParser(object):
|
||||
# *empty* -- still a subset of [arg0, ..., arg(i-1)], but
|
||||
# not a very interesting subset!
|
||||
|
||||
def _match_long_opt(self, opt, explicit_value, state):
|
||||
def _match_long_opt(
|
||||
self, opt: str, explicit_value: t.Optional[str], state: ParsingState
|
||||
) -> None:
|
||||
if opt not in self._long_opt:
|
||||
possibilities = [word for word in self._long_opt if word.startswith(opt)]
|
||||
from difflib import get_close_matches
|
||||
|
||||
possibilities = get_close_matches(opt, self._long_opt)
|
||||
raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx)
|
||||
|
||||
option = self._long_opt[opt]
|
||||
@ -334,31 +406,26 @@ class OptionParser(object):
|
||||
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]
|
||||
value = self._get_value_from_state(opt, option, state)
|
||||
|
||||
elif explicit_value is not None:
|
||||
raise BadOptionUsage(opt, "{} option does not take a value".format(opt))
|
||||
raise BadOptionUsage(
|
||||
opt, _("Option {name!r} does not take a value.").format(name=opt)
|
||||
)
|
||||
|
||||
else:
|
||||
value = None
|
||||
|
||||
option.process(value, state)
|
||||
|
||||
def _match_short_opt(self, arg, state):
|
||||
def _match_short_opt(self, arg: str, state: ParsingState) -> None:
|
||||
stop = False
|
||||
i = 1
|
||||
prefix = arg[0]
|
||||
unknown_options = []
|
||||
|
||||
for ch in arg[1:]:
|
||||
opt = normalize_opt(prefix + ch, self.ctx)
|
||||
opt = normalize_opt(f"{prefix}{ch}", self.ctx)
|
||||
option = self._short_opt.get(opt)
|
||||
i += 1
|
||||
|
||||
@ -374,14 +441,7 @@ class OptionParser(object):
|
||||
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]
|
||||
value = self._get_value_from_state(opt, option, state)
|
||||
|
||||
else:
|
||||
value = None
|
||||
@ -391,14 +451,52 @@ class OptionParser(object):
|
||||
if stop:
|
||||
break
|
||||
|
||||
# If we got any unknown options we re-combinate the string of the
|
||||
# If we got any unknown options we recombine 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("{}{}".format(prefix, "".join(unknown_options)))
|
||||
state.largs.append(f"{prefix}{''.join(unknown_options)}")
|
||||
|
||||
def _process_opts(self, arg, state):
|
||||
def _get_value_from_state(
|
||||
self, option_name: str, option: Option, state: ParsingState
|
||||
) -> t.Any:
|
||||
nargs = option.nargs
|
||||
|
||||
if len(state.rargs) < nargs:
|
||||
if option.obj._flag_needs_value:
|
||||
# Option allows omitting the value.
|
||||
value = _flag_needs_value
|
||||
else:
|
||||
raise BadOptionUsage(
|
||||
option_name,
|
||||
ngettext(
|
||||
"Option {name!r} requires an argument.",
|
||||
"Option {name!r} requires {nargs} arguments.",
|
||||
nargs,
|
||||
).format(name=option_name, nargs=nargs),
|
||||
)
|
||||
elif nargs == 1:
|
||||
next_rarg = state.rargs[0]
|
||||
|
||||
if (
|
||||
option.obj._flag_needs_value
|
||||
and isinstance(next_rarg, str)
|
||||
and next_rarg[:1] in self._opt_prefixes
|
||||
and len(next_rarg) > 1
|
||||
):
|
||||
# The next arg looks like the start of an option, don't
|
||||
# use it as the value if omitting the value is allowed.
|
||||
value = _flag_needs_value
|
||||
else:
|
||||
value = state.rargs.pop(0)
|
||||
else:
|
||||
value = tuple(state.rargs[:nargs])
|
||||
del state.rargs[:nargs]
|
||||
|
||||
return value
|
||||
|
||||
def _process_opts(self, arg: str, state: ParsingState) -> None:
|
||||
explicit_value = None
|
||||
# Long option handling happens in two parts. The first part is
|
||||
# supporting explicitly attached values. In any case, we will try
|
||||
@ -422,7 +520,10 @@ class OptionParser(object):
|
||||
# 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)
|
||||
self._match_short_opt(arg, state)
|
||||
return
|
||||
|
||||
if not self.ignore_unknown_options:
|
||||
raise
|
||||
|
||||
state.largs.append(arg)
|
||||
|
0
third_party/python/click/click/py.typed
vendored
Normal file
0
third_party/python/click/click/py.typed
vendored
Normal file
593
third_party/python/click/click/shell_completion.py
vendored
Normal file
593
third_party/python/click/click/shell_completion.py
vendored
Normal file
@ -0,0 +1,593 @@
|
||||
import os
|
||||
import re
|
||||
import typing as t
|
||||
from gettext import gettext as _
|
||||
|
||||
from .core import Argument
|
||||
from .core import BaseCommand
|
||||
from .core import Context
|
||||
from .core import MultiCommand
|
||||
from .core import Option
|
||||
from .core import Parameter
|
||||
from .core import ParameterSource
|
||||
from .parser import split_arg_string
|
||||
from .utils import echo
|
||||
|
||||
|
||||
def shell_complete(
|
||||
cli: BaseCommand,
|
||||
ctx_args: t.MutableMapping[str, t.Any],
|
||||
prog_name: str,
|
||||
complete_var: str,
|
||||
instruction: str,
|
||||
) -> int:
|
||||
"""Perform shell completion for the given CLI program.
|
||||
|
||||
:param cli: Command being called.
|
||||
:param ctx_args: Extra arguments to pass to
|
||||
``cli.make_context``.
|
||||
:param prog_name: Name of the executable in the shell.
|
||||
:param complete_var: Name of the environment variable that holds
|
||||
the completion instruction.
|
||||
:param instruction: Value of ``complete_var`` with the completion
|
||||
instruction and shell, in the form ``instruction_shell``.
|
||||
:return: Status code to exit with.
|
||||
"""
|
||||
shell, _, instruction = instruction.partition("_")
|
||||
comp_cls = get_completion_class(shell)
|
||||
|
||||
if comp_cls is None:
|
||||
return 1
|
||||
|
||||
comp = comp_cls(cli, ctx_args, prog_name, complete_var)
|
||||
|
||||
if instruction == "source":
|
||||
echo(comp.source())
|
||||
return 0
|
||||
|
||||
if instruction == "complete":
|
||||
echo(comp.complete())
|
||||
return 0
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
class CompletionItem:
|
||||
"""Represents a completion value and metadata about the value. The
|
||||
default metadata is ``type`` to indicate special shell handling,
|
||||
and ``help`` if a shell supports showing a help string next to the
|
||||
value.
|
||||
|
||||
Arbitrary parameters can be passed when creating the object, and
|
||||
accessed using ``item.attr``. If an attribute wasn't passed,
|
||||
accessing it returns ``None``.
|
||||
|
||||
:param value: The completion suggestion.
|
||||
:param type: Tells the shell script to provide special completion
|
||||
support for the type. Click uses ``"dir"`` and ``"file"``.
|
||||
:param help: String shown next to the value if supported.
|
||||
:param kwargs: Arbitrary metadata. The built-in implementations
|
||||
don't use this, but custom type completions paired with custom
|
||||
shell support could use it.
|
||||
"""
|
||||
|
||||
__slots__ = ("value", "type", "help", "_info")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
value: t.Any,
|
||||
type: str = "plain",
|
||||
help: t.Optional[str] = None,
|
||||
**kwargs: t.Any,
|
||||
) -> None:
|
||||
self.value: t.Any = value
|
||||
self.type: str = type
|
||||
self.help: t.Optional[str] = help
|
||||
self._info = kwargs
|
||||
|
||||
def __getattr__(self, name: str) -> t.Any:
|
||||
return self._info.get(name)
|
||||
|
||||
|
||||
# Only Bash >= 4.4 has the nosort option.
|
||||
_SOURCE_BASH = """\
|
||||
%(complete_func)s() {
|
||||
local IFS=$'\\n'
|
||||
local response
|
||||
|
||||
response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \
|
||||
%(complete_var)s=bash_complete $1)
|
||||
|
||||
for completion in $response; do
|
||||
IFS=',' read type value <<< "$completion"
|
||||
|
||||
if [[ $type == 'dir' ]]; then
|
||||
COMPREPLY=()
|
||||
compopt -o dirnames
|
||||
elif [[ $type == 'file' ]]; then
|
||||
COMPREPLY=()
|
||||
compopt -o default
|
||||
elif [[ $type == 'plain' ]]; then
|
||||
COMPREPLY+=($value)
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
%(complete_func)s_setup() {
|
||||
complete -o nosort -F %(complete_func)s %(prog_name)s
|
||||
}
|
||||
|
||||
%(complete_func)s_setup;
|
||||
"""
|
||||
|
||||
_SOURCE_ZSH = """\
|
||||
#compdef %(prog_name)s
|
||||
|
||||
%(complete_func)s() {
|
||||
local -a completions
|
||||
local -a completions_with_descriptions
|
||||
local -a response
|
||||
(( ! $+commands[%(prog_name)s] )) && return 1
|
||||
|
||||
response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \
|
||||
%(complete_var)s=zsh_complete %(prog_name)s)}")
|
||||
|
||||
for type key descr in ${response}; do
|
||||
if [[ "$type" == "plain" ]]; then
|
||||
if [[ "$descr" == "_" ]]; then
|
||||
completions+=("$key")
|
||||
else
|
||||
completions_with_descriptions+=("$key":"$descr")
|
||||
fi
|
||||
elif [[ "$type" == "dir" ]]; then
|
||||
_path_files -/
|
||||
elif [[ "$type" == "file" ]]; then
|
||||
_path_files -f
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$completions_with_descriptions" ]; then
|
||||
_describe -V unsorted completions_with_descriptions -U
|
||||
fi
|
||||
|
||||
if [ -n "$completions" ]; then
|
||||
compadd -U -V unsorted -a completions
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
|
||||
# autoload from fpath, call function directly
|
||||
%(complete_func)s "$@"
|
||||
else
|
||||
# eval/source/. command, register function for later
|
||||
compdef %(complete_func)s %(prog_name)s
|
||||
fi
|
||||
"""
|
||||
|
||||
_SOURCE_FISH = """\
|
||||
function %(complete_func)s
|
||||
set -l response (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \
|
||||
COMP_CWORD=(commandline -t) %(prog_name)s)
|
||||
|
||||
for completion in $response
|
||||
set -l metadata (string split "," $completion)
|
||||
|
||||
if test $metadata[1] = "dir"
|
||||
__fish_complete_directories $metadata[2]
|
||||
else if test $metadata[1] = "file"
|
||||
__fish_complete_path $metadata[2]
|
||||
else if test $metadata[1] = "plain"
|
||||
echo $metadata[2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
complete --no-files --command %(prog_name)s --arguments \
|
||||
"(%(complete_func)s)"
|
||||
"""
|
||||
|
||||
|
||||
class ShellComplete:
|
||||
"""Base class for providing shell completion support. A subclass for
|
||||
a given shell will override attributes and methods to implement the
|
||||
completion instructions (``source`` and ``complete``).
|
||||
|
||||
:param cli: Command being called.
|
||||
:param prog_name: Name of the executable in the shell.
|
||||
:param complete_var: Name of the environment variable that holds
|
||||
the completion instruction.
|
||||
|
||||
.. versionadded:: 8.0
|
||||
"""
|
||||
|
||||
name: t.ClassVar[str]
|
||||
"""Name to register the shell as with :func:`add_completion_class`.
|
||||
This is used in completion instructions (``{name}_source`` and
|
||||
``{name}_complete``).
|
||||
"""
|
||||
|
||||
source_template: t.ClassVar[str]
|
||||
"""Completion script template formatted by :meth:`source`. This must
|
||||
be provided by subclasses.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
cli: BaseCommand,
|
||||
ctx_args: t.MutableMapping[str, t.Any],
|
||||
prog_name: str,
|
||||
complete_var: str,
|
||||
) -> None:
|
||||
self.cli = cli
|
||||
self.ctx_args = ctx_args
|
||||
self.prog_name = prog_name
|
||||
self.complete_var = complete_var
|
||||
|
||||
@property
|
||||
def func_name(self) -> str:
|
||||
"""The name of the shell function defined by the completion
|
||||
script.
|
||||
"""
|
||||
safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), re.ASCII)
|
||||
return f"_{safe_name}_completion"
|
||||
|
||||
def source_vars(self) -> t.Dict[str, t.Any]:
|
||||
"""Vars for formatting :attr:`source_template`.
|
||||
|
||||
By default this provides ``complete_func``, ``complete_var``,
|
||||
and ``prog_name``.
|
||||
"""
|
||||
return {
|
||||
"complete_func": self.func_name,
|
||||
"complete_var": self.complete_var,
|
||||
"prog_name": self.prog_name,
|
||||
}
|
||||
|
||||
def source(self) -> str:
|
||||
"""Produce the shell script that defines the completion
|
||||
function. By default this ``%``-style formats
|
||||
:attr:`source_template` with the dict returned by
|
||||
:meth:`source_vars`.
|
||||
"""
|
||||
return self.source_template % self.source_vars()
|
||||
|
||||
def get_completion_args(self) -> t.Tuple[t.List[str], str]:
|
||||
"""Use the env vars defined by the shell script to return a
|
||||
tuple of ``args, incomplete``. This must be implemented by
|
||||
subclasses.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_completions(
|
||||
self, args: t.List[str], incomplete: str
|
||||
) -> t.List[CompletionItem]:
|
||||
"""Determine the context and last complete command or parameter
|
||||
from the complete args. Call that object's ``shell_complete``
|
||||
method to get the completions for the incomplete value.
|
||||
|
||||
:param args: List of complete args before the incomplete value.
|
||||
:param incomplete: Value being completed. May be empty.
|
||||
"""
|
||||
ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args)
|
||||
obj, incomplete = _resolve_incomplete(ctx, args, incomplete)
|
||||
return obj.shell_complete(ctx, incomplete)
|
||||
|
||||
def format_completion(self, item: CompletionItem) -> str:
|
||||
"""Format a completion item into the form recognized by the
|
||||
shell script. This must be implemented by subclasses.
|
||||
|
||||
:param item: Completion item to format.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def complete(self) -> str:
|
||||
"""Produce the completion data to send back to the shell.
|
||||
|
||||
By default this calls :meth:`get_completion_args`, gets the
|
||||
completions, then calls :meth:`format_completion` for each
|
||||
completion.
|
||||
"""
|
||||
args, incomplete = self.get_completion_args()
|
||||
completions = self.get_completions(args, incomplete)
|
||||
out = [self.format_completion(item) for item in completions]
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
class BashComplete(ShellComplete):
|
||||
"""Shell completion for Bash."""
|
||||
|
||||
name = "bash"
|
||||
source_template = _SOURCE_BASH
|
||||
|
||||
def _check_version(self) -> None:
|
||||
import subprocess
|
||||
|
||||
output = subprocess.run(
|
||||
["bash", "-c", 'echo "${BASH_VERSION}"'], stdout=subprocess.PIPE
|
||||
)
|
||||
match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode())
|
||||
|
||||
if match is not None:
|
||||
major, minor = match.groups()
|
||||
|
||||
if major < "4" or major == "4" and minor < "4":
|
||||
raise RuntimeError(
|
||||
_(
|
||||
"Shell completion is not supported for Bash"
|
||||
" versions older than 4.4."
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
_("Couldn't detect Bash version, shell completion is not supported.")
|
||||
)
|
||||
|
||||
def source(self) -> str:
|
||||
self._check_version()
|
||||
return super().source()
|
||||
|
||||
def get_completion_args(self) -> t.Tuple[t.List[str], str]:
|
||||
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 = ""
|
||||
|
||||
return args, incomplete
|
||||
|
||||
def format_completion(self, item: CompletionItem) -> str:
|
||||
return f"{item.type},{item.value}"
|
||||
|
||||
|
||||
class ZshComplete(ShellComplete):
|
||||
"""Shell completion for Zsh."""
|
||||
|
||||
name = "zsh"
|
||||
source_template = _SOURCE_ZSH
|
||||
|
||||
def get_completion_args(self) -> t.Tuple[t.List[str], str]:
|
||||
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 = ""
|
||||
|
||||
return args, incomplete
|
||||
|
||||
def format_completion(self, item: CompletionItem) -> str:
|
||||
return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}"
|
||||
|
||||
|
||||
class FishComplete(ShellComplete):
|
||||
"""Shell completion for Fish."""
|
||||
|
||||
name = "fish"
|
||||
source_template = _SOURCE_FISH
|
||||
|
||||
def get_completion_args(self) -> t.Tuple[t.List[str], str]:
|
||||
cwords = split_arg_string(os.environ["COMP_WORDS"])
|
||||
incomplete = os.environ["COMP_CWORD"]
|
||||
args = cwords[1:]
|
||||
|
||||
# Fish stores the partial word in both COMP_WORDS and
|
||||
# COMP_CWORD, remove it from complete args.
|
||||
if incomplete and args and args[-1] == incomplete:
|
||||
args.pop()
|
||||
|
||||
return args, incomplete
|
||||
|
||||
def format_completion(self, item: CompletionItem) -> str:
|
||||
if item.help:
|
||||
return f"{item.type},{item.value}\t{item.help}"
|
||||
|
||||
return f"{item.type},{item.value}"
|
||||
|
||||
|
||||
ShellCompleteType = t.TypeVar("ShellCompleteType", bound=t.Type[ShellComplete])
|
||||
|
||||
|
||||
_available_shells: t.Dict[str, t.Type[ShellComplete]] = {
|
||||
"bash": BashComplete,
|
||||
"fish": FishComplete,
|
||||
"zsh": ZshComplete,
|
||||
}
|
||||
|
||||
|
||||
def add_completion_class(
|
||||
cls: ShellCompleteType, name: t.Optional[str] = None
|
||||
) -> ShellCompleteType:
|
||||
"""Register a :class:`ShellComplete` subclass under the given name.
|
||||
The name will be provided by the completion instruction environment
|
||||
variable during completion.
|
||||
|
||||
:param cls: The completion class that will handle completion for the
|
||||
shell.
|
||||
:param name: Name to register the class under. Defaults to the
|
||||
class's ``name`` attribute.
|
||||
"""
|
||||
if name is None:
|
||||
name = cls.name
|
||||
|
||||
_available_shells[name] = cls
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
def get_completion_class(shell: str) -> t.Optional[t.Type[ShellComplete]]:
|
||||
"""Look up a registered :class:`ShellComplete` subclass by the name
|
||||
provided by the completion instruction environment variable. If the
|
||||
name isn't registered, returns ``None``.
|
||||
|
||||
:param shell: Name the class is registered under.
|
||||
"""
|
||||
return _available_shells.get(shell)
|
||||
|
||||
|
||||
def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool:
|
||||
"""Determine if the given parameter is an argument that can still
|
||||
accept values.
|
||||
|
||||
:param ctx: Invocation context for the command represented by the
|
||||
parsed complete args.
|
||||
:param param: Argument object being checked.
|
||||
"""
|
||||
if not isinstance(param, Argument):
|
||||
return False
|
||||
|
||||
assert param.name is not None
|
||||
# Will be None if expose_value is False.
|
||||
value = ctx.params.get(param.name)
|
||||
return (
|
||||
param.nargs == -1
|
||||
or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE
|
||||
or (
|
||||
param.nargs > 1
|
||||
and isinstance(value, (tuple, list))
|
||||
and len(value) < param.nargs
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _start_of_option(ctx: Context, value: str) -> bool:
|
||||
"""Check if the value looks like the start of an option."""
|
||||
if not value:
|
||||
return False
|
||||
|
||||
c = value[0]
|
||||
return c in ctx._opt_prefixes
|
||||
|
||||
|
||||
def _is_incomplete_option(ctx: Context, args: t.List[str], param: Parameter) -> bool:
|
||||
"""Determine if the given parameter is an option that needs a value.
|
||||
|
||||
:param args: List of complete args before the incomplete value.
|
||||
:param param: Option object being checked.
|
||||
"""
|
||||
if not isinstance(param, Option):
|
||||
return False
|
||||
|
||||
if param.is_flag or param.count:
|
||||
return False
|
||||
|
||||
last_option = None
|
||||
|
||||
for index, arg in enumerate(reversed(args)):
|
||||
if index + 1 > param.nargs:
|
||||
break
|
||||
|
||||
if _start_of_option(ctx, arg):
|
||||
last_option = arg
|
||||
|
||||
return last_option is not None and last_option in param.opts
|
||||
|
||||
|
||||
def _resolve_context(
|
||||
cli: BaseCommand,
|
||||
ctx_args: t.MutableMapping[str, t.Any],
|
||||
prog_name: str,
|
||||
args: t.List[str],
|
||||
) -> Context:
|
||||
"""Produce the context hierarchy starting with the command and
|
||||
traversing the complete arguments. This only follows the commands,
|
||||
it doesn't trigger input prompts or callbacks.
|
||||
|
||||
:param cli: Command being called.
|
||||
:param prog_name: Name of the executable in the shell.
|
||||
:param args: List of complete args before the incomplete value.
|
||||
"""
|
||||
ctx_args["resilient_parsing"] = True
|
||||
ctx = cli.make_context(prog_name, args.copy(), **ctx_args)
|
||||
args = ctx.protected_args + ctx.args
|
||||
|
||||
while args:
|
||||
command = ctx.command
|
||||
|
||||
if isinstance(command, MultiCommand):
|
||||
if not command.chain:
|
||||
name, cmd, args = command.resolve_command(ctx, args)
|
||||
|
||||
if cmd is None:
|
||||
return ctx
|
||||
|
||||
ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True)
|
||||
args = ctx.protected_args + ctx.args
|
||||
else:
|
||||
sub_ctx = ctx
|
||||
|
||||
while args:
|
||||
name, cmd, args = command.resolve_command(ctx, args)
|
||||
|
||||
if cmd is None:
|
||||
return ctx
|
||||
|
||||
sub_ctx = cmd.make_context(
|
||||
name,
|
||||
args,
|
||||
parent=ctx,
|
||||
allow_extra_args=True,
|
||||
allow_interspersed_args=False,
|
||||
resilient_parsing=True,
|
||||
)
|
||||
args = sub_ctx.args
|
||||
|
||||
ctx = sub_ctx
|
||||
args = [*sub_ctx.protected_args, *sub_ctx.args]
|
||||
else:
|
||||
break
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
def _resolve_incomplete(
|
||||
ctx: Context, args: t.List[str], incomplete: str
|
||||
) -> t.Tuple[t.Union[BaseCommand, Parameter], str]:
|
||||
"""Find the Click object that will handle the completion of the
|
||||
incomplete value. Return the object and the incomplete value.
|
||||
|
||||
:param ctx: Invocation context for the command represented by
|
||||
the parsed complete args.
|
||||
:param args: List of complete args before the incomplete value.
|
||||
:param incomplete: Value being completed. May be empty.
|
||||
"""
|
||||
# Different shells treat an "=" between a long option name and
|
||||
# value differently. Might keep the value joined, return the "="
|
||||
# as a separate item, or return the split name and value. Always
|
||||
# split and discard the "=" to make completion easier.
|
||||
if incomplete == "=":
|
||||
incomplete = ""
|
||||
elif "=" in incomplete and _start_of_option(ctx, incomplete):
|
||||
name, _, incomplete = incomplete.partition("=")
|
||||
args.append(name)
|
||||
|
||||
# The "--" marker tells Click to stop treating values as options
|
||||
# even if they start with the option character. If it hasn't been
|
||||
# given and the incomplete arg looks like an option, the current
|
||||
# command will provide option name completions.
|
||||
if "--" not in args and _start_of_option(ctx, incomplete):
|
||||
return ctx.command, incomplete
|
||||
|
||||
params = ctx.command.get_params(ctx)
|
||||
|
||||
# If the last complete arg is an option name with an incomplete
|
||||
# value, the option will provide value completions.
|
||||
for param in params:
|
||||
if _is_incomplete_option(ctx, args, param):
|
||||
return param, incomplete
|
||||
|
||||
# It's not an option name or value. The first argument without a
|
||||
# parsed value will provide value completions.
|
||||
for param in params:
|
||||
if _is_incomplete_argument(ctx, param):
|
||||
return param, incomplete
|
||||
|
||||
# There were no unparsed arguments, the command may be a group that
|
||||
# will provide command name completions.
|
||||
return ctx.command, incomplete
|
497
third_party/python/click/click/termui.py
vendored
497
third_party/python/click/click/termui.py
vendored
@ -1,30 +1,29 @@
|
||||
import inspect
|
||||
import io
|
||||
import itertools
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import typing as t
|
||||
from gettext import gettext as _
|
||||
|
||||
from ._compat import DEFAULT_COLUMNS
|
||||
from ._compat import get_winterm_size
|
||||
from ._compat import isatty
|
||||
from ._compat import raw_input
|
||||
from ._compat import string_types
|
||||
from ._compat import strip_ansi
|
||||
from ._compat import text_type
|
||||
from ._compat import WIN
|
||||
from .exceptions import Abort
|
||||
from .exceptions import UsageError
|
||||
from .globals import resolve_color_default
|
||||
from .types import Choice
|
||||
from .types import convert_type
|
||||
from .types import Path
|
||||
from .types import ParamType
|
||||
from .utils import echo
|
||||
from .utils import LazyFile
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from ._termui_impl import ProgressBar
|
||||
|
||||
V = t.TypeVar("V")
|
||||
|
||||
# The prompt functions to use. The doc tools currently override these
|
||||
# functions to customize how they work.
|
||||
visible_prompt_func = raw_input
|
||||
visible_prompt_func: t.Callable[[str], str] = input
|
||||
|
||||
_ansi_colors = {
|
||||
"black": 30,
|
||||
@ -48,24 +47,29 @@ _ansi_colors = {
|
||||
_ansi_reset_all = "\033[0m"
|
||||
|
||||
|
||||
def hidden_prompt_func(prompt):
|
||||
def hidden_prompt_func(prompt: str) -> str:
|
||||
import getpass
|
||||
|
||||
return getpass.getpass(prompt)
|
||||
|
||||
|
||||
def _build_prompt(
|
||||
text, suffix, show_default=False, default=None, show_choices=True, type=None
|
||||
):
|
||||
text: str,
|
||||
suffix: str,
|
||||
show_default: bool = False,
|
||||
default: t.Optional[t.Any] = None,
|
||||
show_choices: bool = True,
|
||||
type: t.Optional[ParamType] = None,
|
||||
) -> str:
|
||||
prompt = text
|
||||
if type is not None and show_choices and isinstance(type, Choice):
|
||||
prompt += " ({})".format(", ".join(map(str, type.choices)))
|
||||
prompt += f" ({', '.join(map(str, type.choices))})"
|
||||
if default is not None and show_default:
|
||||
prompt = "{} [{}]".format(prompt, _format_default(default))
|
||||
return prompt + suffix
|
||||
prompt = f"{prompt} [{_format_default(default)}]"
|
||||
return f"{prompt}{suffix}"
|
||||
|
||||
|
||||
def _format_default(default):
|
||||
def _format_default(default: t.Any) -> t.Any:
|
||||
if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"):
|
||||
return default.name
|
||||
|
||||
@ -73,38 +77,31 @@ def _format_default(default):
|
||||
|
||||
|
||||
def prompt(
|
||||
text,
|
||||
default=None,
|
||||
hide_input=False,
|
||||
confirmation_prompt=False,
|
||||
type=None,
|
||||
value_proc=None,
|
||||
prompt_suffix=": ",
|
||||
show_default=True,
|
||||
err=False,
|
||||
show_choices=True,
|
||||
):
|
||||
text: str,
|
||||
default: t.Optional[t.Any] = None,
|
||||
hide_input: bool = False,
|
||||
confirmation_prompt: t.Union[bool, str] = False,
|
||||
type: t.Optional[t.Union[ParamType, t.Any]] = None,
|
||||
value_proc: t.Optional[t.Callable[[str], t.Any]] = None,
|
||||
prompt_suffix: str = ": ",
|
||||
show_default: bool = True,
|
||||
err: bool = False,
|
||||
show_choices: bool = True,
|
||||
) -> t.Any:
|
||||
"""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
|
||||
If the user aborts the input by sending an interrupt signal, this
|
||||
function will catch it and raise a :exc:`Abort` exception.
|
||||
|
||||
.. versionadded:: 7.0
|
||||
Added the show_choices parameter.
|
||||
|
||||
.. 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 confirmation_prompt: Prompt a second time to confirm the
|
||||
value. Can be set to a string instead of ``True`` to customize
|
||||
the message.
|
||||
: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
|
||||
@ -117,23 +114,37 @@ def prompt(
|
||||
For example if type is a Choice of either day or week,
|
||||
show_choices is true and text is "Group by" then the
|
||||
prompt will be "Group by (day, week): ".
|
||||
"""
|
||||
result = None
|
||||
|
||||
def prompt_func(text):
|
||||
.. versionadded:: 8.0
|
||||
``confirmation_prompt`` can be a custom string.
|
||||
|
||||
.. versionadded:: 7.0
|
||||
Added the ``show_choices`` parameter.
|
||||
|
||||
.. versionadded:: 6.0
|
||||
Added unicode support for cmd.exe on Windows.
|
||||
|
||||
.. versionadded:: 4.0
|
||||
Added the `err` parameter.
|
||||
|
||||
"""
|
||||
|
||||
def prompt_func(text: str) -> str:
|
||||
f = hidden_prompt_func if hide_input else 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("")
|
||||
echo(text.rstrip(" "), nl=False, err=err)
|
||||
# Echo a space to stdout to work around an issue where
|
||||
# readline causes backspace to clear the whole line.
|
||||
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()
|
||||
raise Abort() from None
|
||||
|
||||
if value_proc is None:
|
||||
value_proc = convert_type(type, default)
|
||||
@ -142,72 +153,94 @@ def prompt(
|
||||
text, prompt_suffix, show_default, default, show_choices, type
|
||||
)
|
||||
|
||||
while 1:
|
||||
while 1:
|
||||
if confirmation_prompt:
|
||||
if confirmation_prompt is True:
|
||||
confirmation_prompt = _("Repeat for confirmation")
|
||||
|
||||
confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix)
|
||||
|
||||
while True:
|
||||
while True:
|
||||
value = prompt_func(prompt)
|
||||
if value:
|
||||
break
|
||||
elif default is not None:
|
||||
if isinstance(value_proc, Path):
|
||||
# validate Path default value(exists, dir_okay etc.)
|
||||
value = default
|
||||
break
|
||||
return default
|
||||
value = default
|
||||
break
|
||||
try:
|
||||
result = value_proc(value)
|
||||
except UsageError as e:
|
||||
echo("Error: {}".format(e.message), err=err) # noqa: B306
|
||||
if hide_input:
|
||||
echo(_("Error: The value you entered was invalid."), err=err)
|
||||
else:
|
||||
echo(_("Error: {e.message}").format(e=e), err=err) # noqa: B306
|
||||
continue
|
||||
if not confirmation_prompt:
|
||||
return result
|
||||
while 1:
|
||||
value2 = prompt_func("Repeat for confirmation: ")
|
||||
if value2:
|
||||
while True:
|
||||
value2 = prompt_func(confirmation_prompt)
|
||||
is_empty = not value and not value2
|
||||
if value2 or is_empty:
|
||||
break
|
||||
if value == value2:
|
||||
return result
|
||||
echo("Error: the two entered values do not match", err=err)
|
||||
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
|
||||
):
|
||||
text: str,
|
||||
default: t.Optional[bool] = False,
|
||||
abort: bool = False,
|
||||
prompt_suffix: str = ": ",
|
||||
show_default: bool = True,
|
||||
err: bool = False,
|
||||
) -> bool:
|
||||
"""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 default: The default value to use when no input is given. If
|
||||
``None``, repeat until input is given.
|
||||
: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.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Repeat until input is given if ``default`` is ``None``.
|
||||
|
||||
.. versionadded:: 4.0
|
||||
Added the ``err`` parameter.
|
||||
"""
|
||||
prompt = _build_prompt(
|
||||
text, prompt_suffix, show_default, "Y/n" if default else "y/N"
|
||||
text,
|
||||
prompt_suffix,
|
||||
show_default,
|
||||
"y/n" if default is None else ("Y/n" if default else "y/N"),
|
||||
)
|
||||
while 1:
|
||||
|
||||
while True:
|
||||
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()
|
||||
echo(prompt.rstrip(" "), nl=False, err=err)
|
||||
# Echo a space to stdout to work around an issue where
|
||||
# readline causes backspace to clear the whole line.
|
||||
value = visible_prompt_func(" ").lower().strip()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
raise Abort()
|
||||
raise Abort() from None
|
||||
if value in ("y", "yes"):
|
||||
rv = True
|
||||
elif value in ("n", "no"):
|
||||
rv = False
|
||||
elif value == "":
|
||||
elif default is not None and value == "":
|
||||
rv = default
|
||||
else:
|
||||
echo("Error: invalid input", err=err)
|
||||
echo(_("Error: invalid input"), err=err)
|
||||
continue
|
||||
break
|
||||
if abort and not rv:
|
||||
@ -215,54 +248,10 @@ def confirm(
|
||||
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
|
||||
|
||||
# We provide a sensible default for get_winterm_size() when being invoked
|
||||
# inside a subprocess. Without this, it would not provide a useful input.
|
||||
if get_winterm_size is not None:
|
||||
size = get_winterm_size()
|
||||
if size == (0, 0):
|
||||
return (79, 24)
|
||||
else:
|
||||
return 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_or_generator, color=None):
|
||||
def echo_via_pager(
|
||||
text_or_generator: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str],
|
||||
color: t.Optional[bool] = None,
|
||||
) -> None:
|
||||
"""This function takes a text and shows it via an environment specific
|
||||
pager on stdout.
|
||||
|
||||
@ -277,14 +266,14 @@ def echo_via_pager(text_or_generator, color=None):
|
||||
color = resolve_color_default(color)
|
||||
|
||||
if inspect.isgeneratorfunction(text_or_generator):
|
||||
i = text_or_generator()
|
||||
elif isinstance(text_or_generator, string_types):
|
||||
i = t.cast(t.Callable[[], t.Iterable[str]], text_or_generator)()
|
||||
elif isinstance(text_or_generator, str):
|
||||
i = [text_or_generator]
|
||||
else:
|
||||
i = iter(text_or_generator)
|
||||
i = iter(t.cast(t.Iterable[str], text_or_generator))
|
||||
|
||||
# convert every element of i to a text type if necessary
|
||||
text_generator = (el if isinstance(el, string_types) else text_type(el) for el in i)
|
||||
text_generator = (el if isinstance(el, str) else str(el) for el in i)
|
||||
|
||||
from ._termui_impl import pager
|
||||
|
||||
@ -292,21 +281,22 @@ def echo_via_pager(text_or_generator, color=None):
|
||||
|
||||
|
||||
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,
|
||||
):
|
||||
iterable: t.Optional[t.Iterable[V]] = None,
|
||||
length: t.Optional[int] = None,
|
||||
label: t.Optional[str] = None,
|
||||
show_eta: bool = True,
|
||||
show_percent: t.Optional[bool] = None,
|
||||
show_pos: bool = False,
|
||||
item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None,
|
||||
fill_char: str = "#",
|
||||
empty_char: str = "-",
|
||||
bar_template: str = "%(label)s [%(bar)s] %(info)s",
|
||||
info_sep: str = " ",
|
||||
width: int = 36,
|
||||
file: t.Optional[t.TextIO] = None,
|
||||
color: t.Optional[bool] = None,
|
||||
update_min_steps: int = 1,
|
||||
) -> "ProgressBar[V]":
|
||||
"""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
|
||||
@ -346,11 +336,19 @@ def progressbar(
|
||||
process_chunk(chunk)
|
||||
bar.update(chunks.bytes)
|
||||
|
||||
.. versionadded:: 2.0
|
||||
The ``update()`` method also takes an optional value specifying the
|
||||
``current_item`` at the new position. This is useful when used
|
||||
together with ``item_show_func`` to customize the output for each
|
||||
manual step::
|
||||
|
||||
.. versionadded:: 4.0
|
||||
Added the `color` parameter. Added a `update` method to the
|
||||
progressbar object.
|
||||
with click.progressbar(
|
||||
length=total_size,
|
||||
label='Unzipping archive',
|
||||
item_show_func=lambda a: a.filename
|
||||
) as bar:
|
||||
for archive in zip_file:
|
||||
archive.extract()
|
||||
bar.update(archive.size, archive)
|
||||
|
||||
:param iterable: an iterable to iterate over. If not provided the length
|
||||
is required.
|
||||
@ -369,10 +367,10 @@ def progressbar(
|
||||
`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 item_show_func: A function called with the current item which
|
||||
can return a string to show next to the progress bar. If the
|
||||
function returns ``None`` nothing is shown. The current item can
|
||||
be ``None``, such as when entering and exiting the bar.
|
||||
: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
|
||||
@ -384,12 +382,33 @@ def progressbar(
|
||||
: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 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.
|
||||
:param update_min_steps: Render only when this many updates have
|
||||
completed. This allows tuning for very fast iterators.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Output is shown even if execution time is less than 0.5 seconds.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
``item_show_func`` shows the current item, not the previous one.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Labels are echoed if the output is not a TTY. Reverts a change
|
||||
in 7.0 that removed all output.
|
||||
|
||||
.. versionadded:: 8.0
|
||||
Added the ``update_min_steps`` parameter.
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
Added the ``color`` parameter. Added the ``update`` method to
|
||||
the object.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
from ._termui_impl import ProgressBar
|
||||
|
||||
@ -409,10 +428,11 @@ def progressbar(
|
||||
label=label,
|
||||
width=width,
|
||||
color=color,
|
||||
update_min_steps=update_min_steps,
|
||||
)
|
||||
|
||||
|
||||
def clear():
|
||||
def clear() -> None:
|
||||
"""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.
|
||||
@ -421,26 +441,38 @@ def clear():
|
||||
"""
|
||||
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")
|
||||
|
||||
# ANSI escape \033[2J clears the screen, \033[1;1H moves the cursor
|
||||
echo("\033[2J\033[1;1H", nl=False)
|
||||
|
||||
|
||||
def _interpret_color(
|
||||
color: t.Union[int, t.Tuple[int, int, int], str], offset: int = 0
|
||||
) -> str:
|
||||
if isinstance(color, int):
|
||||
return f"{38 + offset};5;{color:d}"
|
||||
|
||||
if isinstance(color, (tuple, list)):
|
||||
r, g, b = color
|
||||
return f"{38 + offset};2;{r:d};{g:d};{b:d}"
|
||||
|
||||
return str(_ansi_colors[color] + offset)
|
||||
|
||||
|
||||
def style(
|
||||
text,
|
||||
fg=None,
|
||||
bg=None,
|
||||
bold=None,
|
||||
dim=None,
|
||||
underline=None,
|
||||
blink=None,
|
||||
reverse=None,
|
||||
reset=True,
|
||||
):
|
||||
text: t.Any,
|
||||
fg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None,
|
||||
bg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None,
|
||||
bold: t.Optional[bool] = None,
|
||||
dim: t.Optional[bool] = None,
|
||||
underline: t.Optional[bool] = None,
|
||||
overline: t.Optional[bool] = None,
|
||||
italic: t.Optional[bool] = None,
|
||||
blink: t.Optional[bool] = None,
|
||||
reverse: t.Optional[bool] = None,
|
||||
strikethrough: t.Optional[bool] = None,
|
||||
reset: bool = True,
|
||||
) -> str:
|
||||
"""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
|
||||
@ -451,6 +483,7 @@ def style(
|
||||
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'))
|
||||
click.echo(click.style('More colors', fg=(255, 12, 128), bg=117))
|
||||
|
||||
Supported color names:
|
||||
|
||||
@ -472,10 +505,15 @@ def style(
|
||||
* ``bright_white``
|
||||
* ``reset`` (reset the color code only)
|
||||
|
||||
.. versionadded:: 2.0
|
||||
If the terminal supports it, color may also be specified as:
|
||||
|
||||
.. versionadded:: 7.0
|
||||
Added support for bright colors.
|
||||
- An integer in the interval [0, 255]. The terminal must support
|
||||
8-bit/256-color mode.
|
||||
- An RGB tuple of three integers in [0, 255]. The terminal must
|
||||
support 24-bit/true-color mode.
|
||||
|
||||
See https://en.wikipedia.org/wiki/ANSI_color and
|
||||
https://gist.github.com/XVilka/8346728 for more information.
|
||||
|
||||
:param text: the string to style with ansi codes.
|
||||
:param fg: if provided this will become the foreground color.
|
||||
@ -484,42 +522,73 @@ def style(
|
||||
: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 overline: if provided this will enable or disable overline.
|
||||
:param italic: if provided this will enable or disable italic.
|
||||
: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 strikethrough: if provided this will enable or disable
|
||||
striking through text.
|
||||
: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.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
A non-string ``message`` is converted to a string.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Added support for 256 and RGB color codes.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Added the ``strikethrough``, ``italic``, and ``overline``
|
||||
parameters.
|
||||
|
||||
.. versionchanged:: 7.0
|
||||
Added support for bright colors.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
if not isinstance(text, str):
|
||||
text = str(text)
|
||||
|
||||
bits = []
|
||||
|
||||
if fg:
|
||||
try:
|
||||
bits.append("\033[{}m".format(_ansi_colors[fg]))
|
||||
bits.append(f"\033[{_interpret_color(fg)}m")
|
||||
except KeyError:
|
||||
raise TypeError("Unknown color '{}'".format(fg))
|
||||
raise TypeError(f"Unknown color {fg!r}") from None
|
||||
|
||||
if bg:
|
||||
try:
|
||||
bits.append("\033[{}m".format(_ansi_colors[bg] + 10))
|
||||
bits.append(f"\033[{_interpret_color(bg, 10)}m")
|
||||
except KeyError:
|
||||
raise TypeError("Unknown color '{}'".format(bg))
|
||||
raise TypeError(f"Unknown color {bg!r}") from None
|
||||
|
||||
if bold is not None:
|
||||
bits.append("\033[{}m".format(1 if bold else 22))
|
||||
bits.append(f"\033[{1 if bold else 22}m")
|
||||
if dim is not None:
|
||||
bits.append("\033[{}m".format(2 if dim else 22))
|
||||
bits.append(f"\033[{2 if dim else 22}m")
|
||||
if underline is not None:
|
||||
bits.append("\033[{}m".format(4 if underline else 24))
|
||||
bits.append(f"\033[{4 if underline else 24}m")
|
||||
if overline is not None:
|
||||
bits.append(f"\033[{53 if overline else 55}m")
|
||||
if italic is not None:
|
||||
bits.append(f"\033[{3 if italic else 23}m")
|
||||
if blink is not None:
|
||||
bits.append("\033[{}m".format(5 if blink else 25))
|
||||
bits.append(f"\033[{5 if blink else 25}m")
|
||||
if reverse is not None:
|
||||
bits.append("\033[{}m".format(7 if reverse else 27))
|
||||
bits.append(f"\033[{7 if reverse else 27}m")
|
||||
if strikethrough is not None:
|
||||
bits.append(f"\033[{9 if strikethrough else 29}m")
|
||||
bits.append(text)
|
||||
if reset:
|
||||
bits.append(_ansi_reset_all)
|
||||
return "".join(bits)
|
||||
|
||||
|
||||
def unstyle(text):
|
||||
def unstyle(text: str) -> str:
|
||||
"""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.
|
||||
@ -531,7 +600,14 @@ def unstyle(text):
|
||||
return strip_ansi(text)
|
||||
|
||||
|
||||
def secho(message=None, file=None, nl=True, err=False, color=None, **styles):
|
||||
def secho(
|
||||
message: t.Optional[t.Any] = None,
|
||||
file: t.Optional[t.IO[t.AnyStr]] = None,
|
||||
nl: bool = True,
|
||||
err: bool = False,
|
||||
color: t.Optional[bool] = None,
|
||||
**styles: t.Any,
|
||||
) -> None:
|
||||
"""This function combines :func:`echo` and :func:`style` into one
|
||||
call. As such the following two calls are the same::
|
||||
|
||||
@ -541,16 +617,31 @@ def secho(message=None, file=None, nl=True, err=False, color=None, **styles):
|
||||
All keyword arguments are forwarded to the underlying functions
|
||||
depending on which one they go with.
|
||||
|
||||
Non-string types will be converted to :class:`str`. However,
|
||||
:class:`bytes` are passed directly to :meth:`echo` without applying
|
||||
style. If you want to style bytes that represent text, call
|
||||
:meth:`bytes.decode` first.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
A non-string ``message`` is converted to a string. Bytes are
|
||||
passed through without style applied.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
if message is not None:
|
||||
if message is not None and not isinstance(message, (bytes, bytearray)):
|
||||
message = style(message, **styles)
|
||||
|
||||
return echo(message, file=file, nl=nl, err=err, color=color)
|
||||
|
||||
|
||||
def edit(
|
||||
text=None, editor=None, env=None, require_save=True, extension=".txt", filename=None
|
||||
):
|
||||
text: t.Optional[t.AnyStr] = None,
|
||||
editor: t.Optional[str] = None,
|
||||
env: t.Optional[t.Mapping[str, str]] = None,
|
||||
require_save: bool = True,
|
||||
extension: str = ".txt",
|
||||
filename: t.Optional[str] = None,
|
||||
) -> t.Optional[t.AnyStr]:
|
||||
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
|
||||
@ -580,15 +671,16 @@ def edit(
|
||||
"""
|
||||
from ._termui_impl import Editor
|
||||
|
||||
editor = Editor(
|
||||
editor=editor, env=env, require_save=require_save, extension=extension
|
||||
)
|
||||
ed = Editor(editor=editor, env=env, require_save=require_save, extension=extension)
|
||||
|
||||
if filename is None:
|
||||
return editor.edit(text)
|
||||
editor.edit_file(filename)
|
||||
return ed.edit(text)
|
||||
|
||||
ed.edit_file(filename)
|
||||
return None
|
||||
|
||||
|
||||
def launch(url, wait=False, locate=False):
|
||||
def launch(url: str, wait: bool = False, locate: bool = False) -> int:
|
||||
"""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
|
||||
@ -603,7 +695,9 @@ def launch(url, wait=False, locate=False):
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:param url: URL or filename of the thing to launch.
|
||||
:param wait: waits for the program to stop.
|
||||
:param wait: Wait for the program to exit before returning. This
|
||||
only works if the launched program blocks. In particular,
|
||||
``xdg-open`` on Linux does not block.
|
||||
: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
|
||||
@ -617,10 +711,10 @@ def launch(url, wait=False, locate=False):
|
||||
|
||||
# If this is provided, getchar() calls into this instead. This is used
|
||||
# for unittesting purposes.
|
||||
_getchar = None
|
||||
_getchar: t.Optional[t.Callable[[bool], str]] = None
|
||||
|
||||
|
||||
def getchar(echo=False):
|
||||
def getchar(echo: bool = False) -> str:
|
||||
"""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
|
||||
@ -640,19 +734,23 @@ def getchar(echo=False):
|
||||
: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:
|
||||
global _getchar
|
||||
|
||||
if _getchar is None:
|
||||
from ._termui_impl import getchar as f
|
||||
return f(echo)
|
||||
|
||||
_getchar = f
|
||||
|
||||
return _getchar(echo)
|
||||
|
||||
|
||||
def raw_terminal():
|
||||
def raw_terminal() -> t.ContextManager[int]:
|
||||
from ._termui_impl import raw_terminal as f
|
||||
|
||||
return f()
|
||||
|
||||
|
||||
def pause(info="Press any key to continue ...", err=False):
|
||||
def pause(info: t.Optional[str] = None, err: bool = False) -> None:
|
||||
"""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
|
||||
@ -663,12 +761,17 @@ def pause(info="Press any key to continue ...", err=False):
|
||||
.. versionadded:: 4.0
|
||||
Added the `err` parameter.
|
||||
|
||||
:param info: the info string to print before pausing.
|
||||
:param info: The message to print before pausing. Defaults to
|
||||
``"Press any key to continue..."``.
|
||||
: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
|
||||
|
||||
if info is None:
|
||||
info = _("Press any key to continue...")
|
||||
|
||||
try:
|
||||
if info:
|
||||
echo(info, nl=False, err=err)
|
||||
|
347
third_party/python/click/click/testing.py
vendored
347
third_party/python/click/click/testing.py
vendored
@ -1,77 +1,117 @@
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import typing as t
|
||||
from types import TracebackType
|
||||
|
||||
from . import formatting
|
||||
from . import termui
|
||||
from . import utils
|
||||
from ._compat import iteritems
|
||||
from ._compat import PY2
|
||||
from ._compat import string_types
|
||||
from ._compat import _find_binary_reader
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from .core import BaseCommand
|
||||
|
||||
|
||||
if PY2:
|
||||
from cStringIO import StringIO
|
||||
else:
|
||||
import io
|
||||
from ._compat import _find_binary_reader
|
||||
|
||||
|
||||
class EchoingStdin(object):
|
||||
def __init__(self, input, output):
|
||||
class EchoingStdin:
|
||||
def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None:
|
||||
self._input = input
|
||||
self._output = output
|
||||
self._paused = False
|
||||
|
||||
def __getattr__(self, x):
|
||||
def __getattr__(self, x: str) -> t.Any:
|
||||
return getattr(self._input, x)
|
||||
|
||||
def _echo(self, rv):
|
||||
self._output.write(rv)
|
||||
def _echo(self, rv: bytes) -> bytes:
|
||||
if not self._paused:
|
||||
self._output.write(rv)
|
||||
|
||||
return rv
|
||||
|
||||
def read(self, n=-1):
|
||||
def read(self, n: int = -1) -> bytes:
|
||||
return self._echo(self._input.read(n))
|
||||
|
||||
def readline(self, n=-1):
|
||||
def read1(self, n: int = -1) -> bytes:
|
||||
return self._echo(self._input.read1(n)) # type: ignore
|
||||
|
||||
def readline(self, n: int = -1) -> bytes:
|
||||
return self._echo(self._input.readline(n))
|
||||
|
||||
def readlines(self):
|
||||
def readlines(self) -> t.List[bytes]:
|
||||
return [self._echo(x) for x in self._input.readlines()]
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> t.Iterator[bytes]:
|
||||
return iter(self._echo(x) for x in self._input)
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return repr(self._input)
|
||||
|
||||
|
||||
def make_input_stream(input, charset):
|
||||
@contextlib.contextmanager
|
||||
def _pause_echo(stream: t.Optional[EchoingStdin]) -> t.Iterator[None]:
|
||||
if stream is None:
|
||||
yield
|
||||
else:
|
||||
stream._paused = True
|
||||
yield
|
||||
stream._paused = False
|
||||
|
||||
|
||||
class _NamedTextIOWrapper(io.TextIOWrapper):
|
||||
def __init__(
|
||||
self, buffer: t.BinaryIO, name: str, mode: str, **kwargs: t.Any
|
||||
) -> None:
|
||||
super().__init__(buffer, **kwargs)
|
||||
self._name = name
|
||||
self._mode = mode
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def mode(self) -> str:
|
||||
return self._mode
|
||||
|
||||
|
||||
def make_input_stream(
|
||||
input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]], charset: str
|
||||
) -> t.BinaryIO:
|
||||
# Is already an input stream.
|
||||
if hasattr(input, "read"):
|
||||
if PY2:
|
||||
return input
|
||||
rv = _find_binary_reader(input)
|
||||
rv = _find_binary_reader(t.cast(t.IO[t.Any], 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):
|
||||
elif isinstance(input, str):
|
||||
input = input.encode(charset)
|
||||
if PY2:
|
||||
return StringIO(input)
|
||||
|
||||
return io.BytesIO(input)
|
||||
|
||||
|
||||
class Result(object):
|
||||
class Result:
|
||||
"""Holds the captured result of an invoked CLI script."""
|
||||
|
||||
def __init__(
|
||||
self, runner, stdout_bytes, stderr_bytes, exit_code, exception, exc_info=None
|
||||
self,
|
||||
runner: "CliRunner",
|
||||
stdout_bytes: bytes,
|
||||
stderr_bytes: t.Optional[bytes],
|
||||
return_value: t.Any,
|
||||
exit_code: int,
|
||||
exception: t.Optional[BaseException],
|
||||
exc_info: t.Optional[
|
||||
t.Tuple[t.Type[BaseException], BaseException, TracebackType]
|
||||
] = None,
|
||||
):
|
||||
#: The runner that created the result
|
||||
self.runner = runner
|
||||
@ -79,6 +119,10 @@ class Result(object):
|
||||
self.stdout_bytes = stdout_bytes
|
||||
#: The standard error as bytes, or None if not available
|
||||
self.stderr_bytes = stderr_bytes
|
||||
#: The value returned from the invoked command.
|
||||
#:
|
||||
#: .. versionadded:: 8.0
|
||||
self.return_value = return_value
|
||||
#: The exit code as integer.
|
||||
self.exit_code = exit_code
|
||||
#: The exception that happened if one did.
|
||||
@ -87,19 +131,19 @@ class Result(object):
|
||||
self.exc_info = exc_info
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
def output(self) -> str:
|
||||
"""The (standard) output as unicode string."""
|
||||
return self.stdout
|
||||
|
||||
@property
|
||||
def stdout(self):
|
||||
def stdout(self) -> str:
|
||||
"""The standard output as unicode string."""
|
||||
return self.stdout_bytes.decode(self.runner.charset, "replace").replace(
|
||||
"\r\n", "\n"
|
||||
)
|
||||
|
||||
@property
|
||||
def stderr(self):
|
||||
def stderr(self) -> str:
|
||||
"""The standard error as unicode string."""
|
||||
if self.stderr_bytes is None:
|
||||
raise ValueError("stderr not separately captured")
|
||||
@ -107,21 +151,18 @@ class Result(object):
|
||||
"\r\n", "\n"
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "<{} {}>".format(
|
||||
type(self).__name__, repr(self.exception) if self.exception else "okay"
|
||||
)
|
||||
def __repr__(self) -> str:
|
||||
exc_str = repr(self.exception) if self.exception else "okay"
|
||||
return f"<{type(self).__name__} {exc_str}>"
|
||||
|
||||
|
||||
class CliRunner(object):
|
||||
class CliRunner:
|
||||
"""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 charset: the character set for the input and output data.
|
||||
: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
|
||||
@ -134,22 +175,28 @@ class CliRunner(object):
|
||||
independently
|
||||
"""
|
||||
|
||||
def __init__(self, charset=None, env=None, echo_stdin=False, mix_stderr=True):
|
||||
if charset is None:
|
||||
charset = "utf-8"
|
||||
def __init__(
|
||||
self,
|
||||
charset: str = "utf-8",
|
||||
env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
|
||||
echo_stdin: bool = False,
|
||||
mix_stderr: bool = True,
|
||||
) -> None:
|
||||
self.charset = charset
|
||||
self.env = env or {}
|
||||
self.env: t.Mapping[str, t.Optional[str]] = env or {}
|
||||
self.echo_stdin = echo_stdin
|
||||
self.mix_stderr = mix_stderr
|
||||
|
||||
def get_default_prog_name(self, cli):
|
||||
def get_default_prog_name(self, cli: "BaseCommand") -> str:
|
||||
"""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):
|
||||
def make_env(
|
||||
self, overrides: t.Optional[t.Mapping[str, t.Optional[str]]] = None
|
||||
) -> t.Mapping[str, t.Optional[str]]:
|
||||
"""Returns the environment overrides for invoking a script."""
|
||||
rv = dict(self.env)
|
||||
if overrides:
|
||||
@ -157,7 +204,12 @@ class CliRunner(object):
|
||||
return rv
|
||||
|
||||
@contextlib.contextmanager
|
||||
def isolation(self, input=None, env=None, color=False):
|
||||
def isolation(
|
||||
self,
|
||||
input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]] = None,
|
||||
env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
|
||||
color: bool = False,
|
||||
) -> t.Iterator[t.Tuple[io.BytesIO, t.Optional[io.BytesIO]]]:
|
||||
"""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.
|
||||
@ -166,15 +218,20 @@ class CliRunner(object):
|
||||
|
||||
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.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
``stderr`` is opened with ``errors="backslashreplace"``
|
||||
instead of the default ``"strict"``.
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
Added the ``color`` parameter.
|
||||
"""
|
||||
input = make_input_stream(input, self.charset)
|
||||
bytes_input = make_input_stream(input, self.charset)
|
||||
echo_input = None
|
||||
|
||||
old_stdin = sys.stdin
|
||||
old_stdout = sys.stdout
|
||||
@ -184,51 +241,68 @@ class CliRunner(object):
|
||||
|
||||
env = self.make_env(env)
|
||||
|
||||
if PY2:
|
||||
bytes_output = StringIO()
|
||||
if self.echo_stdin:
|
||||
input = EchoingStdin(input, bytes_output)
|
||||
sys.stdout = bytes_output
|
||||
if not self.mix_stderr:
|
||||
bytes_error = StringIO()
|
||||
sys.stderr = bytes_error
|
||||
else:
|
||||
bytes_output = io.BytesIO()
|
||||
if self.echo_stdin:
|
||||
input = EchoingStdin(input, bytes_output)
|
||||
input = io.TextIOWrapper(input, encoding=self.charset)
|
||||
sys.stdout = io.TextIOWrapper(bytes_output, encoding=self.charset)
|
||||
if not self.mix_stderr:
|
||||
bytes_error = io.BytesIO()
|
||||
sys.stderr = io.TextIOWrapper(bytes_error, encoding=self.charset)
|
||||
bytes_output = io.BytesIO()
|
||||
|
||||
if self.echo_stdin:
|
||||
bytes_input = echo_input = t.cast(
|
||||
t.BinaryIO, EchoingStdin(bytes_input, bytes_output)
|
||||
)
|
||||
|
||||
sys.stdin = text_input = _NamedTextIOWrapper(
|
||||
bytes_input, encoding=self.charset, name="<stdin>", mode="r"
|
||||
)
|
||||
|
||||
if self.echo_stdin:
|
||||
# Force unbuffered reads, otherwise TextIOWrapper reads a
|
||||
# large chunk which is echoed early.
|
||||
text_input._CHUNK_SIZE = 1 # type: ignore
|
||||
|
||||
sys.stdout = _NamedTextIOWrapper(
|
||||
bytes_output, encoding=self.charset, name="<stdout>", mode="w"
|
||||
)
|
||||
|
||||
bytes_error = None
|
||||
if self.mix_stderr:
|
||||
sys.stderr = sys.stdout
|
||||
else:
|
||||
bytes_error = io.BytesIO()
|
||||
sys.stderr = _NamedTextIOWrapper(
|
||||
bytes_error,
|
||||
encoding=self.charset,
|
||||
name="<stderr>",
|
||||
mode="w",
|
||||
errors="backslashreplace",
|
||||
)
|
||||
|
||||
sys.stdin = input
|
||||
|
||||
def visible_input(prompt=None):
|
||||
@_pause_echo(echo_input) # type: ignore
|
||||
def visible_input(prompt: t.Optional[str] = None) -> str:
|
||||
sys.stdout.write(prompt or "")
|
||||
val = input.readline().rstrip("\r\n")
|
||||
sys.stdout.write("{}\n".format(val))
|
||||
val = text_input.readline().rstrip("\r\n")
|
||||
sys.stdout.write(f"{val}\n")
|
||||
sys.stdout.flush()
|
||||
return val
|
||||
|
||||
def hidden_input(prompt=None):
|
||||
sys.stdout.write("{}\n".format(prompt or ""))
|
||||
@_pause_echo(echo_input) # type: ignore
|
||||
def hidden_input(prompt: t.Optional[str] = None) -> str:
|
||||
sys.stdout.write(f"{prompt or ''}\n")
|
||||
sys.stdout.flush()
|
||||
return input.readline().rstrip("\r\n")
|
||||
return text_input.readline().rstrip("\r\n")
|
||||
|
||||
def _getchar(echo):
|
||||
@_pause_echo(echo_input) # type: ignore
|
||||
def _getchar(echo: bool) -> str:
|
||||
char = sys.stdin.read(1)
|
||||
|
||||
if echo:
|
||||
sys.stdout.write(char)
|
||||
sys.stdout.flush()
|
||||
|
||||
sys.stdout.flush()
|
||||
return char
|
||||
|
||||
default_color = color
|
||||
|
||||
def should_strip_ansi(stream=None, color=None):
|
||||
def should_strip_ansi(
|
||||
stream: t.Optional[t.IO[t.Any]] = None, color: t.Optional[bool] = None
|
||||
) -> bool:
|
||||
if color is None:
|
||||
return not default_color
|
||||
return not color
|
||||
@ -236,15 +310,15 @@ class CliRunner(object):
|
||||
old_visible_prompt_func = termui.visible_prompt_func
|
||||
old_hidden_prompt_func = termui.hidden_prompt_func
|
||||
old__getchar_func = termui._getchar
|
||||
old_should_strip_ansi = utils.should_strip_ansi
|
||||
old_should_strip_ansi = utils.should_strip_ansi # type: ignore
|
||||
termui.visible_prompt_func = visible_input
|
||||
termui.hidden_prompt_func = hidden_input
|
||||
termui._getchar = _getchar
|
||||
utils.should_strip_ansi = should_strip_ansi
|
||||
utils.should_strip_ansi = should_strip_ansi # type: ignore
|
||||
|
||||
old_env = {}
|
||||
try:
|
||||
for key, value in iteritems(env):
|
||||
for key, value in env.items():
|
||||
old_env[key] = os.environ.get(key)
|
||||
if value is None:
|
||||
try:
|
||||
@ -253,9 +327,9 @@ class CliRunner(object):
|
||||
pass
|
||||
else:
|
||||
os.environ[key] = value
|
||||
yield (bytes_output, not self.mix_stderr and bytes_error)
|
||||
yield (bytes_output, bytes_error)
|
||||
finally:
|
||||
for key, value in iteritems(old_env):
|
||||
for key, value in old_env.items():
|
||||
if value is None:
|
||||
try:
|
||||
del os.environ[key]
|
||||
@ -269,19 +343,19 @@ class CliRunner(object):
|
||||
termui.visible_prompt_func = old_visible_prompt_func
|
||||
termui.hidden_prompt_func = old_hidden_prompt_func
|
||||
termui._getchar = old__getchar_func
|
||||
utils.should_strip_ansi = old_should_strip_ansi
|
||||
utils.should_strip_ansi = old_should_strip_ansi # type: ignore
|
||||
formatting.FORCED_WIDTH = old_forced_width
|
||||
|
||||
def invoke(
|
||||
self,
|
||||
cli,
|
||||
args=None,
|
||||
input=None,
|
||||
env=None,
|
||||
catch_exceptions=True,
|
||||
color=False,
|
||||
**extra
|
||||
):
|
||||
cli: "BaseCommand",
|
||||
args: t.Optional[t.Union[str, t.Sequence[str]]] = None,
|
||||
input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]] = None,
|
||||
env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
|
||||
catch_exceptions: bool = True,
|
||||
color: bool = False,
|
||||
**extra: t.Any,
|
||||
) -> Result:
|
||||
"""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
|
||||
@ -289,16 +363,6 @@ class CliRunner(object):
|
||||
|
||||
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. It may be given as an iterable
|
||||
or a string. When given as string it will be interpreted
|
||||
@ -311,13 +375,28 @@ class CliRunner(object):
|
||||
: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.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
The result object has the ``return_value`` attribute with
|
||||
the value returned from the invoked command.
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
Added the ``color`` parameter.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Added the ``catch_exceptions`` parameter.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The result object has the ``exc_info`` attribute with the
|
||||
traceback if available.
|
||||
"""
|
||||
exc_info = None
|
||||
with self.isolation(input=input, env=env, color=color) as outstreams:
|
||||
exception = None
|
||||
return_value = None
|
||||
exception: t.Optional[BaseException] = None
|
||||
exit_code = 0
|
||||
|
||||
if isinstance(args, string_types):
|
||||
if isinstance(args, str):
|
||||
args = shlex.split(args)
|
||||
|
||||
try:
|
||||
@ -326,20 +405,23 @@ class CliRunner(object):
|
||||
prog_name = self.get_default_prog_name(cli)
|
||||
|
||||
try:
|
||||
cli.main(args=args or (), prog_name=prog_name, **extra)
|
||||
return_value = cli.main(args=args or (), prog_name=prog_name, **extra)
|
||||
except SystemExit as e:
|
||||
exc_info = sys.exc_info()
|
||||
exit_code = e.code
|
||||
if exit_code is None:
|
||||
exit_code = 0
|
||||
e_code = t.cast(t.Optional[t.Union[int, t.Any]], e.code)
|
||||
|
||||
if exit_code != 0:
|
||||
if e_code is None:
|
||||
e_code = 0
|
||||
|
||||
if e_code != 0:
|
||||
exception = e
|
||||
|
||||
if not isinstance(exit_code, int):
|
||||
sys.stdout.write(str(exit_code))
|
||||
if not isinstance(e_code, int):
|
||||
sys.stdout.write(str(e_code))
|
||||
sys.stdout.write("\n")
|
||||
exit_code = 1
|
||||
e_code = 1
|
||||
|
||||
exit_code = e_code
|
||||
|
||||
except Exception as e:
|
||||
if not catch_exceptions:
|
||||
@ -353,30 +435,45 @@ class CliRunner(object):
|
||||
if self.mix_stderr:
|
||||
stderr = None
|
||||
else:
|
||||
stderr = outstreams[1].getvalue()
|
||||
stderr = outstreams[1].getvalue() # type: ignore
|
||||
|
||||
return Result(
|
||||
runner=self,
|
||||
stdout_bytes=stdout,
|
||||
stderr_bytes=stderr,
|
||||
return_value=return_value,
|
||||
exit_code=exit_code,
|
||||
exception=exception,
|
||||
exc_info=exc_info,
|
||||
exc_info=exc_info, # type: ignore
|
||||
)
|
||||
|
||||
@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.
|
||||
def isolated_filesystem(
|
||||
self, temp_dir: t.Optional[t.Union[str, "os.PathLike[str]"]] = None
|
||||
) -> t.Iterator[str]:
|
||||
"""A context manager that creates a temporary directory and
|
||||
changes the current working directory to it. This isolates tests
|
||||
that affect the contents of the CWD to prevent them from
|
||||
interfering with each other.
|
||||
|
||||
:param temp_dir: Create the temporary directory under this
|
||||
directory. If given, the created directory is not removed
|
||||
when exiting.
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
Added the ``temp_dir`` parameter.
|
||||
"""
|
||||
cwd = os.getcwd()
|
||||
t = tempfile.mkdtemp()
|
||||
os.chdir(t)
|
||||
dt = tempfile.mkdtemp(dir=temp_dir)
|
||||
os.chdir(dt)
|
||||
|
||||
try:
|
||||
yield t
|
||||
yield dt
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
try:
|
||||
shutil.rmtree(t)
|
||||
except (OSError, IOError): # noqa: B014
|
||||
pass
|
||||
|
||||
if temp_dir is None:
|
||||
try:
|
||||
shutil.rmtree(dt)
|
||||
except OSError: # noqa: B014
|
||||
pass
|
||||
|
953
third_party/python/click/click/types.py
vendored
953
third_party/python/click/click/types.py
vendored
File diff suppressed because it is too large
Load Diff
557
third_party/python/click/click/utils.py
vendored
557
third_party/python/click/click/utils.py
vendored
@ -1,86 +1,109 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import typing as t
|
||||
from functools import update_wrapper
|
||||
from types import ModuleType
|
||||
from types import TracebackType
|
||||
|
||||
from ._compat import _default_text_stderr
|
||||
from ._compat import _default_text_stdout
|
||||
from ._compat import _find_binary_writer
|
||||
from ._compat import auto_wrap_for_ansi
|
||||
from ._compat import binary_streams
|
||||
from ._compat import filename_to_ui
|
||||
from ._compat import get_filesystem_encoding
|
||||
from ._compat import get_streerror
|
||||
from ._compat import is_bytes
|
||||
from ._compat import open_stream
|
||||
from ._compat import PY2
|
||||
from ._compat import should_strip_ansi
|
||||
from ._compat import string_types
|
||||
from ._compat import strip_ansi
|
||||
from ._compat import text_streams
|
||||
from ._compat import text_type
|
||||
from ._compat import WIN
|
||||
from .globals import resolve_color_default
|
||||
|
||||
if not PY2:
|
||||
from ._compat import _find_binary_writer
|
||||
elif WIN:
|
||||
from ._winconsole import _get_windows_argv
|
||||
from ._winconsole import _hash_py_argv
|
||||
from ._winconsole import _initial_argv_hash
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
|
||||
echo_native_types = string_types + (bytes, bytearray)
|
||||
P = te.ParamSpec("P")
|
||||
|
||||
R = t.TypeVar("R")
|
||||
|
||||
|
||||
def _posixify(name):
|
||||
def _posixify(name: str) -> str:
|
||||
return "-".join(name.split()).lower()
|
||||
|
||||
|
||||
def safecall(func):
|
||||
def safecall(func: "t.Callable[P, R]") -> "t.Callable[P, t.Optional[R]]":
|
||||
"""Wraps a function so that it swallows exceptions."""
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
def wrapper(*args: "P.args", **kwargs: "P.kwargs") -> t.Optional[R]:
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
return wrapper
|
||||
return update_wrapper(wrapper, func)
|
||||
|
||||
|
||||
def make_str(value):
|
||||
def make_str(value: t.Any) -> str:
|
||||
"""Converts a value into a valid string."""
|
||||
if isinstance(value, bytes):
|
||||
try:
|
||||
return value.decode(get_filesystem_encoding())
|
||||
return value.decode(sys.getfilesystemencoding())
|
||||
except UnicodeError:
|
||||
return value.decode("utf-8", "replace")
|
||||
return text_type(value)
|
||||
return str(value)
|
||||
|
||||
|
||||
def make_default_short_help(help, max_length=45):
|
||||
"""Return a condensed version of help string."""
|
||||
def make_default_short_help(help: str, max_length: int = 45) -> str:
|
||||
"""Returns a condensed version of help string."""
|
||||
# Consider only the first paragraph.
|
||||
paragraph_end = help.find("\n\n")
|
||||
|
||||
if paragraph_end != -1:
|
||||
help = help[:paragraph_end]
|
||||
|
||||
# Collapse newlines, tabs, and spaces.
|
||||
words = help.split()
|
||||
|
||||
if not words:
|
||||
return ""
|
||||
|
||||
# The first paragraph started with a "no rewrap" marker, ignore it.
|
||||
if words[0] == "\b":
|
||||
words = words[1:]
|
||||
|
||||
total_length = 0
|
||||
result = []
|
||||
done = False
|
||||
last_index = len(words) - 1
|
||||
|
||||
for word in words:
|
||||
if word[-1:] == ".":
|
||||
done = True
|
||||
new_length = 1 + len(word) if result else len(word)
|
||||
if total_length + new_length > max_length:
|
||||
result.append("...")
|
||||
done = True
|
||||
else:
|
||||
if result:
|
||||
result.append(" ")
|
||||
result.append(word)
|
||||
if done:
|
||||
for i, word in enumerate(words):
|
||||
total_length += len(word) + (i > 0)
|
||||
|
||||
if total_length > max_length: # too long, truncate
|
||||
break
|
||||
total_length += new_length
|
||||
|
||||
return "".join(result)
|
||||
if word[-1] == ".": # sentence end, truncate without "..."
|
||||
return " ".join(words[: i + 1])
|
||||
|
||||
if total_length == max_length and i != last_index:
|
||||
break # not at sentence end, truncate with "..."
|
||||
else:
|
||||
return " ".join(words) # no truncation needed
|
||||
|
||||
# Account for the length of the suffix.
|
||||
total_length += len("...")
|
||||
|
||||
# remove words until the length is short enough
|
||||
while i > 0:
|
||||
total_length -= len(words[i]) + (i > 0)
|
||||
|
||||
if total_length <= max_length:
|
||||
break
|
||||
|
||||
i -= 1
|
||||
|
||||
return " ".join(words[:i]) + "..."
|
||||
|
||||
|
||||
class LazyFile(object):
|
||||
class LazyFile:
|
||||
"""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
|
||||
@ -88,15 +111,22 @@ class LazyFile(object):
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, filename, mode="r", encoding=None, errors="strict", atomic=False
|
||||
self,
|
||||
filename: t.Union[str, "os.PathLike[str]"],
|
||||
mode: str = "r",
|
||||
encoding: t.Optional[str] = None,
|
||||
errors: t.Optional[str] = "strict",
|
||||
atomic: bool = False,
|
||||
):
|
||||
self.name = filename
|
||||
self.name: str = os.fspath(filename)
|
||||
self.mode = mode
|
||||
self.encoding = encoding
|
||||
self.errors = errors
|
||||
self.atomic = atomic
|
||||
self._f: t.Optional[t.IO[t.Any]]
|
||||
self.should_close: bool
|
||||
|
||||
if filename == "-":
|
||||
if self.name == "-":
|
||||
self._f, self.should_close = open_stream(filename, mode, encoding, errors)
|
||||
else:
|
||||
if "r" in mode:
|
||||
@ -107,15 +137,15 @@ class LazyFile(object):
|
||||
self._f = None
|
||||
self.should_close = True
|
||||
|
||||
def __getattr__(self, name):
|
||||
def __getattr__(self, name: str) -> t.Any:
|
||||
return getattr(self.open(), name)
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
if self._f is not None:
|
||||
return repr(self._f)
|
||||
return "<unopened file '{}' {}>".format(self.name, self.mode)
|
||||
return f"<unopened file '{format_filename(self.name)}' {self.mode}>"
|
||||
|
||||
def open(self):
|
||||
def open(self) -> t.IO[t.Any]:
|
||||
"""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.
|
||||
@ -126,102 +156,110 @@ class LazyFile(object):
|
||||
rv, self.should_close = open_stream(
|
||||
self.name, self.mode, self.encoding, self.errors, atomic=self.atomic
|
||||
)
|
||||
except (IOError, OSError) as e: # noqa: E402
|
||||
except OSError as e: # noqa: E402
|
||||
from .exceptions import FileError
|
||||
|
||||
raise FileError(self.name, hint=get_streerror(e))
|
||||
raise FileError(self.name, hint=e.strerror) from e
|
||||
self._f = rv
|
||||
return rv
|
||||
|
||||
def close(self):
|
||||
def close(self) -> None:
|
||||
"""Closes the underlying file, no matter what."""
|
||||
if self._f is not None:
|
||||
self._f.close()
|
||||
|
||||
def close_intelligently(self):
|
||||
def close_intelligently(self) -> None:
|
||||
"""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):
|
||||
def __enter__(self) -> "LazyFile":
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: t.Optional[t.Type[BaseException]],
|
||||
exc_value: t.Optional[BaseException],
|
||||
tb: t.Optional[TracebackType],
|
||||
) -> None:
|
||||
self.close_intelligently()
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> t.Iterator[t.AnyStr]:
|
||||
self.open()
|
||||
return iter(self._f)
|
||||
return iter(self._f) # type: ignore
|
||||
|
||||
|
||||
class KeepOpenFile(object):
|
||||
def __init__(self, file):
|
||||
self._file = file
|
||||
class KeepOpenFile:
|
||||
def __init__(self, file: t.IO[t.Any]) -> None:
|
||||
self._file: t.IO[t.Any] = file
|
||||
|
||||
def __getattr__(self, name):
|
||||
def __getattr__(self, name: str) -> t.Any:
|
||||
return getattr(self._file, name)
|
||||
|
||||
def __enter__(self):
|
||||
def __enter__(self) -> "KeepOpenFile":
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: t.Optional[t.Type[BaseException]],
|
||||
exc_value: t.Optional[BaseException],
|
||||
tb: t.Optional[TracebackType],
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return repr(self._file)
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> t.Iterator[t.AnyStr]:
|
||||
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.
|
||||
def echo(
|
||||
message: t.Optional[t.Any] = None,
|
||||
file: t.Optional[t.IO[t.Any]] = None,
|
||||
nl: bool = True,
|
||||
err: bool = False,
|
||||
color: t.Optional[bool] = None,
|
||||
) -> None:
|
||||
"""Print a message and newline to stdout or a file. This should be
|
||||
used instead of :func:`print` because it provides better support
|
||||
for different data, files, and environments.
|
||||
|
||||
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 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.
|
||||
Compared to :func:`print`, this does the following:
|
||||
|
||||
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:
|
||||
- Ensures that the output encoding is not misconfigured on Linux.
|
||||
- Supports Unicode in the Windows console.
|
||||
- Supports writing to binary outputs, and supports writing bytes
|
||||
to text outputs.
|
||||
- Supports colors and styles on Windows.
|
||||
- Removes ANSI color and style codes if the output does not look
|
||||
like an interactive terminal.
|
||||
- Always flushes the output.
|
||||
|
||||
- add transparent handling of ANSI color codes on Windows.
|
||||
- hide ANSI codes automatically if the destination file is not a
|
||||
terminal.
|
||||
|
||||
.. _colorama: https://pypi.org/project/colorama/
|
||||
:param message: The string or bytes to output. Other objects are
|
||||
converted to strings.
|
||||
:param file: The file to write to. Defaults to ``stdout``.
|
||||
:param err: Write to ``stderr`` instead of ``stdout``.
|
||||
:param nl: Print a newline after the message. Enabled by default.
|
||||
:param color: Force showing or hiding colors and other styles. By
|
||||
default Click will remove color if the output does not look like
|
||||
an interactive terminal.
|
||||
|
||||
.. 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.
|
||||
Support Unicode output on the Windows console. Click does not
|
||||
modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()``
|
||||
will still not support Unicode.
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
Added the `color` flag.
|
||||
Added the ``color`` parameter.
|
||||
|
||||
: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.
|
||||
.. versionadded:: 3.0
|
||||
Added the ``err`` parameter.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Support colors on Windows if colorama is installed.
|
||||
"""
|
||||
if file is None:
|
||||
if err:
|
||||
@ -229,71 +267,79 @@ def echo(message=None, file=None, nl=True, err=False, color=None):
|
||||
else:
|
||||
file = _default_text_stdout()
|
||||
|
||||
# There are no standard streams attached to write to. For example,
|
||||
# pythonw on Windows.
|
||||
if file is None:
|
||||
return
|
||||
|
||||
# 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 message is not None and not isinstance(message, (str, bytes, bytearray)):
|
||||
out: t.Optional[t.Union[str, bytes]] = str(message)
|
||||
else:
|
||||
out = message
|
||||
|
||||
if nl:
|
||||
message = message or u""
|
||||
if isinstance(message, text_type):
|
||||
message += u"\n"
|
||||
out = out or ""
|
||||
if isinstance(out, str):
|
||||
out += "\n"
|
||||
else:
|
||||
message += b"\n"
|
||||
out += 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):
|
||||
if not out:
|
||||
file.flush()
|
||||
return
|
||||
|
||||
# If there is a message 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 isinstance(out, (bytes, bytearray)):
|
||||
binary_file = _find_binary_writer(file)
|
||||
|
||||
if binary_file is not None:
|
||||
file.flush()
|
||||
binary_file.write(message)
|
||||
binary_file.write(out)
|
||||
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):
|
||||
# ANSI style code support. For no message or bytes, nothing happens.
|
||||
# When outputting to a file instead of a terminal, strip codes.
|
||||
else:
|
||||
color = resolve_color_default(color)
|
||||
|
||||
if should_strip_ansi(file, color):
|
||||
message = strip_ansi(message)
|
||||
out = strip_ansi(out)
|
||||
elif WIN:
|
||||
if auto_wrap_for_ansi is not None:
|
||||
file = auto_wrap_for_ansi(file)
|
||||
file = auto_wrap_for_ansi(file) # type: ignore
|
||||
elif not color:
|
||||
message = strip_ansi(message)
|
||||
out = strip_ansi(out)
|
||||
|
||||
if message:
|
||||
file.write(message)
|
||||
file.write(out) # type: ignore
|
||||
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.
|
||||
def get_binary_stream(name: "te.Literal['stdin', 'stdout', 'stderr']") -> t.BinaryIO:
|
||||
"""Returns a system stream for byte processing.
|
||||
|
||||
: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 '{}'".format(name))
|
||||
raise TypeError(f"Unknown standard stream '{name}'")
|
||||
return opener()
|
||||
|
||||
|
||||
def get_text_stream(name, encoding=None, errors="strict"):
|
||||
def get_text_stream(
|
||||
name: "te.Literal['stdin', 'stdout', 'stderr']",
|
||||
encoding: t.Optional[str] = None,
|
||||
errors: t.Optional[str] = "strict",
|
||||
) -> t.TextIO:
|
||||
"""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.
|
||||
:func:`get_binary_stream` but it also can take shortcuts for already
|
||||
correctly configured streams.
|
||||
|
||||
:param name: the name of the stream to open. Valid names are ``'stdin'``,
|
||||
``'stdout'`` and ``'stderr'``
|
||||
@ -302,70 +348,80 @@ def get_text_stream(name, encoding=None, errors="strict"):
|
||||
"""
|
||||
opener = text_streams.get(name)
|
||||
if opener is None:
|
||||
raise TypeError("Unknown standard stream '{}'".format(name))
|
||||
raise TypeError(f"Unknown standard stream '{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.
|
||||
filename: str,
|
||||
mode: str = "r",
|
||||
encoding: t.Optional[str] = None,
|
||||
errors: t.Optional[str] = "strict",
|
||||
lazy: bool = False,
|
||||
atomic: bool = False,
|
||||
) -> t.IO[t.Any]:
|
||||
"""Open a file, with extra behavior to handle ``'-'`` to indicate
|
||||
a standard stream, lazy open on write, and atomic write. Similar to
|
||||
the behavior of the :class:`~click.File` param type.
|
||||
|
||||
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::
|
||||
If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is
|
||||
wrapped so that using it in a context manager will not close it.
|
||||
This makes it possible to use the function without accidentally
|
||||
closing a standard stream:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
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 decode or encode a file opened in
|
||||
text mode.
|
||||
:param errors: The error handling mode.
|
||||
:param lazy: Wait to open the file until it is accessed. For read
|
||||
mode, the file is temporarily opened to raise access errors
|
||||
early, then closed until it is read again.
|
||||
:param atomic: Write to a temporary file and replace the given file
|
||||
on close.
|
||||
|
||||
: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.
|
||||
.. versionadded:: 3.0
|
||||
"""
|
||||
if lazy:
|
||||
return LazyFile(filename, mode, encoding, errors, atomic=atomic)
|
||||
return t.cast(
|
||||
t.IO[t.Any], 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)
|
||||
f = t.cast(t.IO[t.Any], 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.
|
||||
def format_filename(
|
||||
filename: "t.Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]",
|
||||
shorten: bool = False,
|
||||
) -> str:
|
||||
"""Format a filename as a string for display. Ensures the filename can be
|
||||
displayed by replacing any invalid bytes or surrogate escapes in the name
|
||||
with the replacement character ``<EFBFBD>``.
|
||||
|
||||
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.
|
||||
Invalid bytes or surrogate escapes will raise an error when written to a
|
||||
stream with ``errors="strict". This will typically happen with ``stdout``
|
||||
when the locale is something like ``en_GB.UTF-8``.
|
||||
|
||||
.. 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:]
|
||||
Many scenarios *are* safe to write surrogates though, due to PEP 538 and
|
||||
PEP 540, including:
|
||||
|
||||
|
||||
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.
|
||||
- Writing to ``stderr``, which uses ``errors="backslashreplace"``.
|
||||
- The system has ``LANG=C.UTF-8``, ``C``, or ``POSIX``. Python opens
|
||||
stdout and stderr with ``errors="surrogateescape"``.
|
||||
- None of ``LANG/LC_*`` are set. Python assumes ``LANG=C.UTF-8``.
|
||||
- Python is started in UTF-8 mode with ``PYTHONUTF8=1`` or ``-X utf8``.
|
||||
Python opens stdout and stderr with ``errors="surrogateescape"``.
|
||||
|
||||
:param filename: formats a filename for UI display. This will also convert
|
||||
the filename into unicode without failing.
|
||||
@ -374,10 +430,20 @@ def format_filename(filename, shorten=False):
|
||||
"""
|
||||
if shorten:
|
||||
filename = os.path.basename(filename)
|
||||
return filename_to_ui(filename)
|
||||
else:
|
||||
filename = os.fspath(filename)
|
||||
|
||||
if isinstance(filename, bytes):
|
||||
filename = filename.decode(sys.getfilesystemencoding(), "replace")
|
||||
else:
|
||||
filename = filename.encode("utf-8", "surrogateescape").decode(
|
||||
"utf-8", "replace"
|
||||
)
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def get_app_dir(app_name, roaming=True, force_posix=False):
|
||||
def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str:
|
||||
r"""Returns the config folder for the application. The default behavior
|
||||
is to return whatever is most appropriate for the operating system.
|
||||
|
||||
@ -392,13 +458,9 @@ def get_app_dir(app_name, roaming=True, force_posix=False):
|
||||
``~/.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):
|
||||
Windows (roaming):
|
||||
``C:\Users\<user>\AppData\Roaming\Foo Bar``
|
||||
Win 7 (not roaming):
|
||||
Windows (not roaming):
|
||||
``C:\Users\<user>\AppData\Local\Foo Bar``
|
||||
|
||||
.. versionadded:: 2.0
|
||||
@ -406,7 +468,7 @@ def get_app_dir(app_name, roaming=True, force_posix=False):
|
||||
: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.
|
||||
Has no effect 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
|
||||
@ -419,7 +481,7 @@ def get_app_dir(app_name, roaming=True, force_posix=False):
|
||||
folder = os.path.expanduser("~")
|
||||
return os.path.join(folder, app_name)
|
||||
if force_posix:
|
||||
return os.path.join(os.path.expanduser("~/.{}".format(_posixify(app_name))))
|
||||
return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}"))
|
||||
if sys.platform == "darwin":
|
||||
return os.path.join(
|
||||
os.path.expanduser("~/Library/Application Support"), app_name
|
||||
@ -430,7 +492,7 @@ def get_app_dir(app_name, roaming=True, force_posix=False):
|
||||
)
|
||||
|
||||
|
||||
class PacifyFlushWrapper(object):
|
||||
class PacifyFlushWrapper:
|
||||
"""This wrapper is used to catch and suppress BrokenPipeErrors resulting
|
||||
from ``.flush()`` being called on broken pipe during the shutdown/final-GC
|
||||
of the Python interpreter. Notably ``.flush()`` is always called on
|
||||
@ -439,17 +501,124 @@ class PacifyFlushWrapper(object):
|
||||
pipe, all calls and attributes are proxied.
|
||||
"""
|
||||
|
||||
def __init__(self, wrapped):
|
||||
def __init__(self, wrapped: t.IO[t.Any]) -> None:
|
||||
self.wrapped = wrapped
|
||||
|
||||
def flush(self):
|
||||
def flush(self) -> None:
|
||||
try:
|
||||
self.wrapped.flush()
|
||||
except IOError as e:
|
||||
except OSError as e:
|
||||
import errno
|
||||
|
||||
if e.errno != errno.EPIPE:
|
||||
raise
|
||||
|
||||
def __getattr__(self, attr):
|
||||
def __getattr__(self, attr: str) -> t.Any:
|
||||
return getattr(self.wrapped, attr)
|
||||
|
||||
|
||||
def _detect_program_name(
|
||||
path: t.Optional[str] = None, _main: t.Optional[ModuleType] = None
|
||||
) -> str:
|
||||
"""Determine the command used to run the program, for use in help
|
||||
text. If a file or entry point was executed, the file name is
|
||||
returned. If ``python -m`` was used to execute a module or package,
|
||||
``python -m name`` is returned.
|
||||
|
||||
This doesn't try to be too precise, the goal is to give a concise
|
||||
name for help text. Files are only shown as their name without the
|
||||
path. ``python`` is only shown for modules, and the full path to
|
||||
``sys.executable`` is not shown.
|
||||
|
||||
:param path: The Python file being executed. Python puts this in
|
||||
``sys.argv[0]``, which is used by default.
|
||||
:param _main: The ``__main__`` module. This should only be passed
|
||||
during internal testing.
|
||||
|
||||
.. versionadded:: 8.0
|
||||
Based on command args detection in the Werkzeug reloader.
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
if _main is None:
|
||||
_main = sys.modules["__main__"]
|
||||
|
||||
if not path:
|
||||
path = sys.argv[0]
|
||||
|
||||
# The value of __package__ indicates how Python was called. It may
|
||||
# not exist if a setuptools script is installed as an egg. It may be
|
||||
# set incorrectly for entry points created with pip on Windows.
|
||||
# It is set to "" inside a Shiv or PEX zipapp.
|
||||
if getattr(_main, "__package__", None) in {None, ""} or (
|
||||
os.name == "nt"
|
||||
and _main.__package__ == ""
|
||||
and not os.path.exists(path)
|
||||
and os.path.exists(f"{path}.exe")
|
||||
):
|
||||
# Executed a file, like "python app.py".
|
||||
return os.path.basename(path)
|
||||
|
||||
# Executed a module, like "python -m example".
|
||||
# Rewritten by Python from "-m script" to "/path/to/script.py".
|
||||
# Need to look at main module to determine how it was executed.
|
||||
py_module = t.cast(str, _main.__package__)
|
||||
name = os.path.splitext(os.path.basename(path))[0]
|
||||
|
||||
# A submodule like "example.cli".
|
||||
if name != "__main__":
|
||||
py_module = f"{py_module}.{name}"
|
||||
|
||||
return f"python -m {py_module.lstrip('.')}"
|
||||
|
||||
|
||||
def _expand_args(
|
||||
args: t.Iterable[str],
|
||||
*,
|
||||
user: bool = True,
|
||||
env: bool = True,
|
||||
glob_recursive: bool = True,
|
||||
) -> t.List[str]:
|
||||
"""Simulate Unix shell expansion with Python functions.
|
||||
|
||||
See :func:`glob.glob`, :func:`os.path.expanduser`, and
|
||||
:func:`os.path.expandvars`.
|
||||
|
||||
This is intended for use on Windows, where the shell does not do any
|
||||
expansion. It may not exactly match what a Unix shell would do.
|
||||
|
||||
:param args: List of command line arguments to expand.
|
||||
:param user: Expand user home directory.
|
||||
:param env: Expand environment variables.
|
||||
:param glob_recursive: ``**`` matches directories recursively.
|
||||
|
||||
.. versionchanged:: 8.1
|
||||
Invalid glob patterns are treated as empty expansions rather
|
||||
than raising an error.
|
||||
|
||||
.. versionadded:: 8.0
|
||||
|
||||
:meta private:
|
||||
"""
|
||||
from glob import glob
|
||||
|
||||
out = []
|
||||
|
||||
for arg in args:
|
||||
if user:
|
||||
arg = os.path.expanduser(arg)
|
||||
|
||||
if env:
|
||||
arg = os.path.expandvars(arg)
|
||||
|
||||
try:
|
||||
matches = glob(arg, recursive=glob_recursive)
|
||||
except re.error:
|
||||
matches = []
|
||||
|
||||
if not matches:
|
||||
out.append(arg)
|
||||
else:
|
||||
out.extend(matches)
|
||||
|
||||
return out
|
||||
|
@ -1,453 +0,0 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: packaging
|
||||
Version: 21.3
|
||||
Summary: Core utilities for Python packages
|
||||
Home-page: https://github.com/pypa/packaging
|
||||
Author: Donald Stufft and individual contributors
|
||||
Author-email: donald@stufft.io
|
||||
License: BSD-2-Clause or Apache-2.0
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Requires-Python: >=3.6
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE
|
||||
License-File: LICENSE.APACHE
|
||||
License-File: LICENSE.BSD
|
||||
Requires-Dist: pyparsing (!=3.0.5,>=2.0.2)
|
||||
|
||||
packaging
|
||||
=========
|
||||
|
||||
.. start-intro
|
||||
|
||||
Reusable core utilities for various Python Packaging
|
||||
`interoperability specifications <https://packaging.python.org/specifications/>`_.
|
||||
|
||||
This library provides utilities that implement the interoperability
|
||||
specifications which have clearly one correct behaviour (eg: :pep:`440`)
|
||||
or benefit greatly from having a single shared implementation (eg: :pep:`425`).
|
||||
|
||||
.. end-intro
|
||||
|
||||
The ``packaging`` project includes the following: version handling, specifiers,
|
||||
markers, requirements, tags, utilities.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
The `documentation`_ provides information and the API for the following:
|
||||
|
||||
- Version Handling
|
||||
- Specifiers
|
||||
- Markers
|
||||
- Requirements
|
||||
- Tags
|
||||
- Utilities
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Use ``pip`` to install these utilities::
|
||||
|
||||
pip install packaging
|
||||
|
||||
Discussion
|
||||
----------
|
||||
|
||||
If you run into bugs, you can file them in our `issue tracker`_.
|
||||
|
||||
You can also join ``#pypa`` on Freenode to ask questions or get involved.
|
||||
|
||||
|
||||
.. _`documentation`: https://packaging.pypa.io/
|
||||
.. _`issue tracker`: https://github.com/pypa/packaging/issues
|
||||
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
Everyone interacting in the packaging project's codebases, issue trackers, chat
|
||||
rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
|
||||
|
||||
.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
The ``CONTRIBUTING.rst`` file outlines how to contribute to this project as
|
||||
well as how to report a potential security issue. The documentation for this
|
||||
project also covers information about `project development`_ and `security`_.
|
||||
|
||||
.. _`project development`: https://packaging.pypa.io/en/latest/development/
|
||||
.. _`security`: https://packaging.pypa.io/en/latest/security/
|
||||
|
||||
Project History
|
||||
---------------
|
||||
|
||||
Please review the ``CHANGELOG.rst`` file or the `Changelog documentation`_ for
|
||||
recent changes and project history.
|
||||
|
||||
.. _`Changelog documentation`: https://packaging.pypa.io/en/latest/changelog/
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
21.3 - 2021-11-17
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Add a ``pp3-none-any`` tag (`#311 <https://github.com/pypa/packaging/issues/311>`__)
|
||||
* Replace the blank pyparsing 3 exclusion with a 3.0.5 exclusion (`#481 <https://github.com/pypa/packaging/issues/481>`__, `#486 <https://github.com/pypa/packaging/issues/486>`__)
|
||||
* Fix a spelling mistake (`#479 <https://github.com/pypa/packaging/issues/479>`__)
|
||||
|
||||
21.2 - 2021-10-29
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Update documentation entry for 21.1.
|
||||
|
||||
21.1 - 2021-10-29
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Update pin to pyparsing to exclude 3.0.0.
|
||||
|
||||
21.0 - 2021-07-03
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* PEP 656: musllinux support (`#411 <https://github.com/pypa/packaging/issues/411>`__)
|
||||
* Drop support for Python 2.7, Python 3.4 and Python 3.5.
|
||||
* Replace distutils usage with sysconfig (`#396 <https://github.com/pypa/packaging/issues/396>`__)
|
||||
* Add support for zip files in ``parse_sdist_filename`` (`#429 <https://github.com/pypa/packaging/issues/429>`__)
|
||||
* Use cached ``_hash`` attribute to short-circuit tag equality comparisons (`#417 <https://github.com/pypa/packaging/issues/417>`__)
|
||||
* Specify the default value for the ``specifier`` argument to ``SpecifierSet`` (`#437 <https://github.com/pypa/packaging/issues/437>`__)
|
||||
* Proper keyword-only "warn" argument in packaging.tags (`#403 <https://github.com/pypa/packaging/issues/403>`__)
|
||||
* Correctly remove prerelease suffixes from ~= check (`#366 <https://github.com/pypa/packaging/issues/366>`__)
|
||||
* Fix type hints for ``Version.post`` and ``Version.dev`` (`#393 <https://github.com/pypa/packaging/issues/393>`__)
|
||||
* Use typing alias ``UnparsedVersion`` (`#398 <https://github.com/pypa/packaging/issues/398>`__)
|
||||
* Improve type inference for ``packaging.specifiers.filter()`` (`#430 <https://github.com/pypa/packaging/issues/430>`__)
|
||||
* Tighten the return type of ``canonicalize_version()`` (`#402 <https://github.com/pypa/packaging/issues/402>`__)
|
||||
|
||||
20.9 - 2021-01-29
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Run `isort <https://pypi.org/project/isort/>`_ over the code base (`#377 <https://github.com/pypa/packaging/issues/377>`__)
|
||||
* Add support for the ``macosx_10_*_universal2`` platform tags (`#379 <https://github.com/pypa/packaging/issues/379>`__)
|
||||
* Introduce ``packaging.utils.parse_wheel_filename()`` and ``parse_sdist_filename()``
|
||||
(`#387 <https://github.com/pypa/packaging/issues/387>`__ and `#389 <https://github.com/pypa/packaging/issues/389>`__)
|
||||
|
||||
20.8 - 2020-12-11
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Revert back to setuptools for compatibility purposes for some Linux distros (`#363 <https://github.com/pypa/packaging/issues/363>`__)
|
||||
* Do not insert an underscore in wheel tags when the interpreter version number
|
||||
is more than 2 digits (`#372 <https://github.com/pypa/packaging/issues/372>`__)
|
||||
|
||||
20.7 - 2020-11-28
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
No unreleased changes.
|
||||
|
||||
20.6 - 2020-11-28
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. note:: This release was subsequently yanked, and these changes were included in 20.7.
|
||||
|
||||
* Fix flit configuration, to include LICENSE files (`#357 <https://github.com/pypa/packaging/issues/357>`__)
|
||||
* Make `intel` a recognized CPU architecture for the `universal` macOS platform tag (`#361 <https://github.com/pypa/packaging/issues/361>`__)
|
||||
* Add some missing type hints to `packaging.requirements` (issue:`350`)
|
||||
|
||||
20.5 - 2020-11-27
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Officially support Python 3.9 (`#343 <https://github.com/pypa/packaging/issues/343>`__)
|
||||
* Deprecate the ``LegacyVersion`` and ``LegacySpecifier`` classes (`#321 <https://github.com/pypa/packaging/issues/321>`__)
|
||||
* Handle ``OSError`` on non-dynamic executables when attempting to resolve
|
||||
the glibc version string.
|
||||
|
||||
20.4 - 2020-05-19
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Canonicalize version before comparing specifiers. (`#282 <https://github.com/pypa/packaging/issues/282>`__)
|
||||
* Change type hint for ``canonicalize_name`` to return
|
||||
``packaging.utils.NormalizedName``.
|
||||
This enables the use of static typing tools (like mypy) to detect mixing of
|
||||
normalized and un-normalized names.
|
||||
|
||||
20.3 - 2020-03-05
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fix changelog for 20.2.
|
||||
|
||||
20.2 - 2020-03-05
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fix a bug that caused a 32-bit OS that runs on a 64-bit ARM CPU (e.g. ARM-v8,
|
||||
aarch64), to report the wrong bitness.
|
||||
|
||||
20.1 - 2020-01-24
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fix a bug caused by reuse of an exhausted iterator. (`#257 <https://github.com/pypa/packaging/issues/257>`__)
|
||||
|
||||
20.0 - 2020-01-06
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Add type hints (`#191 <https://github.com/pypa/packaging/issues/191>`__)
|
||||
|
||||
* Add proper trove classifiers for PyPy support (`#198 <https://github.com/pypa/packaging/issues/198>`__)
|
||||
|
||||
* Scale back depending on ``ctypes`` for manylinux support detection (`#171 <https://github.com/pypa/packaging/issues/171>`__)
|
||||
|
||||
* Use ``sys.implementation.name`` where appropriate for ``packaging.tags`` (`#193 <https://github.com/pypa/packaging/issues/193>`__)
|
||||
|
||||
* Expand upon the API provided by ``packaging.tags``: ``interpreter_name()``, ``mac_platforms()``, ``compatible_tags()``, ``cpython_tags()``, ``generic_tags()`` (`#187 <https://github.com/pypa/packaging/issues/187>`__)
|
||||
|
||||
* Officially support Python 3.8 (`#232 <https://github.com/pypa/packaging/issues/232>`__)
|
||||
|
||||
* Add ``major``, ``minor``, and ``micro`` aliases to ``packaging.version.Version`` (`#226 <https://github.com/pypa/packaging/issues/226>`__)
|
||||
|
||||
* Properly mark ``packaging`` has being fully typed by adding a `py.typed` file (`#226 <https://github.com/pypa/packaging/issues/226>`__)
|
||||
|
||||
19.2 - 2019-09-18
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Remove dependency on ``attrs`` (`#178 <https://github.com/pypa/packaging/issues/178>`__, `#179 <https://github.com/pypa/packaging/issues/179>`__)
|
||||
|
||||
* Use appropriate fallbacks for CPython ABI tag (`#181 <https://github.com/pypa/packaging/issues/181>`__, `#185 <https://github.com/pypa/packaging/issues/185>`__)
|
||||
|
||||
* Add manylinux2014 support (`#186 <https://github.com/pypa/packaging/issues/186>`__)
|
||||
|
||||
* Improve ABI detection (`#181 <https://github.com/pypa/packaging/issues/181>`__)
|
||||
|
||||
* Properly handle debug wheels for Python 3.8 (`#172 <https://github.com/pypa/packaging/issues/172>`__)
|
||||
|
||||
* Improve detection of debug builds on Windows (`#194 <https://github.com/pypa/packaging/issues/194>`__)
|
||||
|
||||
19.1 - 2019-07-30
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Add the ``packaging.tags`` module. (`#156 <https://github.com/pypa/packaging/issues/156>`__)
|
||||
|
||||
* Correctly handle two-digit versions in ``python_version`` (`#119 <https://github.com/pypa/packaging/issues/119>`__)
|
||||
|
||||
|
||||
19.0 - 2019-01-20
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fix string representation of PEP 508 direct URL requirements with markers.
|
||||
|
||||
* Better handling of file URLs
|
||||
|
||||
This allows for using ``file:///absolute/path``, which was previously
|
||||
prevented due to the missing ``netloc``.
|
||||
|
||||
This allows for all file URLs that ``urlunparse`` turns back into the
|
||||
original URL to be valid.
|
||||
|
||||
|
||||
18.0 - 2018-09-26
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Improve error messages when invalid requirements are given. (`#129 <https://github.com/pypa/packaging/issues/129>`__)
|
||||
|
||||
|
||||
17.1 - 2017-02-28
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fix ``utils.canonicalize_version`` when supplying non PEP 440 versions.
|
||||
|
||||
|
||||
17.0 - 2017-02-28
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Drop support for python 2.6, 3.2, and 3.3.
|
||||
|
||||
* Define minimal pyparsing version to 2.0.2 (`#91 <https://github.com/pypa/packaging/issues/91>`__).
|
||||
|
||||
* Add ``epoch``, ``release``, ``pre``, ``dev``, and ``post`` attributes to
|
||||
``Version`` and ``LegacyVersion`` (`#34 <https://github.com/pypa/packaging/issues/34>`__).
|
||||
|
||||
* Add ``Version().is_devrelease`` and ``LegacyVersion().is_devrelease`` to
|
||||
make it easy to determine if a release is a development release.
|
||||
|
||||
* Add ``utils.canonicalize_version`` to canonicalize version strings or
|
||||
``Version`` instances (`#121 <https://github.com/pypa/packaging/issues/121>`__).
|
||||
|
||||
|
||||
16.8 - 2016-10-29
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fix markers that utilize ``in`` so that they render correctly.
|
||||
|
||||
* Fix an erroneous test on Python RC releases.
|
||||
|
||||
|
||||
16.7 - 2016-04-23
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Add support for the deprecated ``python_implementation`` marker which was
|
||||
an undocumented setuptools marker in addition to the newer markers.
|
||||
|
||||
|
||||
16.6 - 2016-03-29
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Add support for the deprecated, PEP 345 environment markers in addition to
|
||||
the newer markers.
|
||||
|
||||
|
||||
16.5 - 2016-02-26
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fix a regression in parsing requirements with whitespaces between the comma
|
||||
separators.
|
||||
|
||||
|
||||
16.4 - 2016-02-22
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fix a regression in parsing requirements like ``foo (==4)``.
|
||||
|
||||
|
||||
16.3 - 2016-02-21
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fix a bug where ``packaging.requirements:Requirement`` was overly strict when
|
||||
matching legacy requirements.
|
||||
|
||||
|
||||
16.2 - 2016-02-09
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Add a function that implements the name canonicalization from PEP 503.
|
||||
|
||||
|
||||
16.1 - 2016-02-07
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Implement requirement specifiers from PEP 508.
|
||||
|
||||
|
||||
16.0 - 2016-01-19
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Relicense so that packaging is available under *either* the Apache License,
|
||||
Version 2.0 or a 2 Clause BSD license.
|
||||
|
||||
* Support installation of packaging when only distutils is available.
|
||||
|
||||
* Fix ``==`` comparison when there is a prefix and a local version in play.
|
||||
(`#41 <https://github.com/pypa/packaging/issues/41>`__).
|
||||
|
||||
* Implement environment markers from PEP 508.
|
||||
|
||||
|
||||
15.3 - 2015-08-01
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Normalize post-release spellings for rev/r prefixes. `#35 <https://github.com/pypa/packaging/issues/35>`__
|
||||
|
||||
|
||||
15.2 - 2015-05-13
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fix an error where the arbitrary specifier (``===``) was not correctly
|
||||
allowing pre-releases when it was being used.
|
||||
|
||||
* Expose the specifier and version parts through properties on the
|
||||
``Specifier`` classes.
|
||||
|
||||
* Allow iterating over the ``SpecifierSet`` to get access to all of the
|
||||
``Specifier`` instances.
|
||||
|
||||
* Allow testing if a version is contained within a specifier via the ``in``
|
||||
operator.
|
||||
|
||||
|
||||
15.1 - 2015-04-13
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fix a logic error that was causing inconsistent answers about whether or not
|
||||
a pre-release was contained within a ``SpecifierSet`` or not.
|
||||
|
||||
|
||||
15.0 - 2015-01-02
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Add ``Version().is_postrelease`` and ``LegacyVersion().is_postrelease`` to
|
||||
make it easy to determine if a release is a post release.
|
||||
|
||||
* Add ``Version().base_version`` and ``LegacyVersion().base_version`` to make
|
||||
it easy to get the public version without any pre or post release markers.
|
||||
|
||||
* Support the update to PEP 440 which removed the implied ``!=V.*`` when using
|
||||
either ``>V`` or ``<V`` and which instead special cased the handling of
|
||||
pre-releases, post-releases, and local versions when using ``>V`` or ``<V``.
|
||||
|
||||
|
||||
14.5 - 2014-12-17
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Normalize release candidates as ``rc`` instead of ``c``.
|
||||
|
||||
* Expose the ``VERSION_PATTERN`` constant, a regular expression matching
|
||||
a valid version.
|
||||
|
||||
|
||||
14.4 - 2014-12-15
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Ensure that versions are normalized before comparison when used in a
|
||||
specifier with a less than (``<``) or greater than (``>``) operator.
|
||||
|
||||
|
||||
14.3 - 2014-11-19
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* **BACKWARDS INCOMPATIBLE** Refactor specifier support so that it can sanely
|
||||
handle legacy specifiers as well as PEP 440 specifiers.
|
||||
|
||||
* **BACKWARDS INCOMPATIBLE** Move the specifier support out of
|
||||
``packaging.version`` into ``packaging.specifiers``.
|
||||
|
||||
|
||||
14.2 - 2014-09-10
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Add prerelease support to ``Specifier``.
|
||||
* Remove the ability to do ``item in Specifier()`` and replace it with
|
||||
``Specifier().contains(item)`` in order to allow flags that signal if a
|
||||
prerelease should be accepted or not.
|
||||
* Add a method ``Specifier().filter()`` which will take an iterable and returns
|
||||
an iterable with items that do not match the specifier filtered out.
|
||||
|
||||
|
||||
14.1 - 2014-09-08
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Allow ``LegacyVersion`` and ``Version`` to be sorted together.
|
||||
* Add ``packaging.version.parse()`` to enable easily parsing a version string
|
||||
as either a ``Version`` or a ``LegacyVersion`` depending on it's PEP 440
|
||||
validity.
|
||||
|
||||
|
||||
14.0 - 2014-09-05
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Initial release.
|
||||
|
||||
|
||||
.. _`master`: https://github.com/pypa/packaging/
|
||||
|
||||
|
@ -1,19 +0,0 @@
|
||||
packaging/__about__.py,sha256=ugASIO2w1oUyH8_COqQ2X_s0rDhjbhQC3yJocD03h2c,661
|
||||
packaging/__init__.py,sha256=b9Kk5MF7KxhhLgcDmiUWukN-LatWFxPdNug0joPhHSk,497
|
||||
packaging/_manylinux.py,sha256=XcbiXB-qcjv3bcohp6N98TMpOP4_j3m-iOA8ptK2GWY,11488
|
||||
packaging/_musllinux.py,sha256=_KGgY_qc7vhMGpoqss25n2hiLCNKRtvz9mCrS7gkqyc,4378
|
||||
packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
|
||||
packaging/markers.py,sha256=Fygi3_eZnjQ-3VJizW5AhI5wvo0Hb6RMk4DidsKpOC0,8475
|
||||
packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
packaging/requirements.py,sha256=rjaGRCMepZS1mlYMjJ5Qh6rfq3gtsCRQUQmftGZ_bu8,4664
|
||||
packaging/specifiers.py,sha256=LRQ0kFsHrl5qfcFNEEJrIFYsnIHQUJXY9fIsakTrrqE,30110
|
||||
packaging/tags.py,sha256=lmsnGNiJ8C4D_Pf9PbM0qgbZvD9kmB9lpZBQUZa3R_Y,15699
|
||||
packaging/utils.py,sha256=dJjeat3BS-TYn1RrUFVwufUMasbtzLfYRoy_HXENeFQ,4200
|
||||
packaging/version.py,sha256=_fLRNrFrxYcHVfyo8vk9j8s6JM8N_xsSxVFr6RJyco8,14665
|
||||
packaging-21.3.dist-info/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
|
||||
packaging-21.3.dist-info/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
|
||||
packaging-21.3.dist-info/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
|
||||
packaging-21.3.dist-info/METADATA,sha256=KuKIy6qDLP3svIt6ejCbxBDhvq11ebkgUN55MeyKFyc,15147
|
||||
packaging-21.3.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
|
||||
packaging-21.3.dist-info/top_level.txt,sha256=zFdHrhWnPslzsiP455HutQsqPB6v0KCtNUMtUtrefDw,10
|
||||
packaging-21.3.dist-info/RECORD,,
|
@ -1 +0,0 @@
|
||||
packaging
|
99
third_party/python/packaging/packaging-23.1.dist-info/METADATA
vendored
Normal file
99
third_party/python/packaging/packaging-23.1.dist-info/METADATA
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: packaging
|
||||
Version: 23.1
|
||||
Summary: Core utilities for Python packages
|
||||
Author-email: Donald Stufft <donald@stufft.io>
|
||||
Requires-Python: >=3.7
|
||||
Description-Content-Type: text/x-rst
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Typing :: Typed
|
||||
Project-URL: Documentation, https://packaging.pypa.io/
|
||||
Project-URL: Source, https://github.com/pypa/packaging
|
||||
|
||||
packaging
|
||||
=========
|
||||
|
||||
.. start-intro
|
||||
|
||||
Reusable core utilities for various Python Packaging
|
||||
`interoperability specifications <https://packaging.python.org/specifications/>`_.
|
||||
|
||||
This library provides utilities that implement the interoperability
|
||||
specifications which have clearly one correct behaviour (eg: :pep:`440`)
|
||||
or benefit greatly from having a single shared implementation (eg: :pep:`425`).
|
||||
|
||||
.. end-intro
|
||||
|
||||
The ``packaging`` project includes the following: version handling, specifiers,
|
||||
markers, requirements, tags, utilities.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
The `documentation`_ provides information and the API for the following:
|
||||
|
||||
- Version Handling
|
||||
- Specifiers
|
||||
- Markers
|
||||
- Requirements
|
||||
- Tags
|
||||
- Utilities
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Use ``pip`` to install these utilities::
|
||||
|
||||
pip install packaging
|
||||
|
||||
Discussion
|
||||
----------
|
||||
|
||||
If you run into bugs, you can file them in our `issue tracker`_.
|
||||
|
||||
You can also join ``#pypa`` on Freenode to ask questions or get involved.
|
||||
|
||||
|
||||
.. _`documentation`: https://packaging.pypa.io/
|
||||
.. _`issue tracker`: https://github.com/pypa/packaging/issues
|
||||
|
||||
|
||||
Code of Conduct
|
||||
---------------
|
||||
|
||||
Everyone interacting in the packaging project's codebases, issue trackers, chat
|
||||
rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
|
||||
|
||||
.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
The ``CONTRIBUTING.rst`` file outlines how to contribute to this project as
|
||||
well as how to report a potential security issue. The documentation for this
|
||||
project also covers information about `project development`_ and `security`_.
|
||||
|
||||
.. _`project development`: https://packaging.pypa.io/en/latest/development/
|
||||
.. _`security`: https://packaging.pypa.io/en/latest/security/
|
||||
|
||||
Project History
|
||||
---------------
|
||||
|
||||
Please review the ``CHANGELOG.rst`` file or the `Changelog documentation`_ for
|
||||
recent changes and project history.
|
||||
|
||||
.. _`Changelog documentation`: https://packaging.pypa.io/en/latest/changelog/
|
||||
|
21
third_party/python/packaging/packaging-23.1.dist-info/RECORD
vendored
Normal file
21
third_party/python/packaging/packaging-23.1.dist-info/RECORD
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
packaging/__init__.py,sha256=kYVZSmXT6CWInT4UJPDtrSQBAZu8fMuFBxpv5GsDTLk,501
|
||||
packaging/_elffile.py,sha256=hbmK8OD6Z7fY6hwinHEUcD1by7czkGiNYu7ShnFEk2k,3266
|
||||
packaging/_manylinux.py,sha256=ESGrDEVmBc8jYTtdZRAWiLk72lOzAKWeezFgoJ_MuBc,8926
|
||||
packaging/_musllinux.py,sha256=mvPk7FNjjILKRLIdMxR7IvJ1uggLgCszo-L9rjfpi0M,2524
|
||||
packaging/_parser.py,sha256=KJQkBh_Xbfb-qsB560YIEItrTpCZaOh4_YMfBtd5XIY,10194
|
||||
packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
|
||||
packaging/_tokenizer.py,sha256=alCtbwXhOFAmFGZ6BQ-wCTSFoRAJ2z-ysIf7__MTJ_k,5292
|
||||
packaging/markers.py,sha256=eH-txS2zq1HdNpTd9LcZUcVIwewAiNU0grmq5wjKnOk,8208
|
||||
packaging/metadata.py,sha256=PjELMLxKG_iu3HWjKAOdKhuNrHfWgpdTF2Q4nObsZeM,16397
|
||||
packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
packaging/requirements.py,sha256=hJzvtJyAvENc_VfwfhnOZV1851-VW8JCGh-R96NE4Pc,3287
|
||||
packaging/specifiers.py,sha256=ZOpqL_w_Kj6ZF_OWdliQUzhEyHlDbi6989kr-sF5GHs,39206
|
||||
packaging/tags.py,sha256=_1gLX8h1SgpjAdYCP9XqU37zRjXtU5ZliGy3IM-WcSM,18106
|
||||
packaging/utils.py,sha256=es0cCezKspzriQ-3V88h3yJzxz028euV2sUwM61kE-o,4355
|
||||
packaging/version.py,sha256=2NH3E57hzRhn0BV9boUBvgPsxlTqLJeI0EpYQoNvGi0,16326
|
||||
packaging-23.1.dist-info/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
|
||||
packaging-23.1.dist-info/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
|
||||
packaging-23.1.dist-info/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
|
||||
packaging-23.1.dist-info/WHEEL,sha256=rSgq_JpHF9fHR1lx53qwg_1-2LypZE_qmcuXbVUq948,81
|
||||
packaging-23.1.dist-info/METADATA,sha256=JnduJDlxs2IVeB-nIqAC3-HyNcPhP_MADd9_k_MjmaI,3082
|
||||
packaging-23.1.dist-info/RECORD,,
|
@ -1,6 +1,4 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.34.2)
|
||||
Generator: flit 3.8.0
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
@ -1,26 +0,0 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
__all__ = [
|
||||
"__title__",
|
||||
"__summary__",
|
||||
"__uri__",
|
||||
"__version__",
|
||||
"__author__",
|
||||
"__email__",
|
||||
"__license__",
|
||||
"__copyright__",
|
||||
]
|
||||
|
||||
__title__ = "packaging"
|
||||
__summary__ = "Core utilities for Python packages"
|
||||
__uri__ = "https://github.com/pypa/packaging"
|
||||
|
||||
__version__ = "21.3"
|
||||
|
||||
__author__ = "Donald Stufft and individual contributors"
|
||||
__email__ = "donald@stufft.io"
|
||||
|
||||
__license__ = "BSD-2-Clause or Apache-2.0"
|
||||
__copyright__ = "2014-2019 %s" % __author__
|
@ -2,24 +2,14 @@
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
from .__about__ import (
|
||||
__author__,
|
||||
__copyright__,
|
||||
__email__,
|
||||
__license__,
|
||||
__summary__,
|
||||
__title__,
|
||||
__uri__,
|
||||
__version__,
|
||||
)
|
||||
__title__ = "packaging"
|
||||
__summary__ = "Core utilities for Python packages"
|
||||
__uri__ = "https://github.com/pypa/packaging"
|
||||
|
||||
__all__ = [
|
||||
"__title__",
|
||||
"__summary__",
|
||||
"__uri__",
|
||||
"__version__",
|
||||
"__author__",
|
||||
"__email__",
|
||||
"__license__",
|
||||
"__copyright__",
|
||||
]
|
||||
__version__ = "23.1"
|
||||
|
||||
__author__ = "Donald Stufft and individual contributors"
|
||||
__email__ = "donald@stufft.io"
|
||||
|
||||
__license__ = "BSD-2-Clause or Apache-2.0"
|
||||
__copyright__ = "2014-2019 %s" % __author__
|
||||
|
108
third_party/python/packaging/packaging/_elffile.py
vendored
Normal file
108
third_party/python/packaging/packaging/_elffile.py
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
"""
|
||||
ELF file parser.
|
||||
|
||||
This provides a class ``ELFFile`` that parses an ELF executable in a similar
|
||||
interface to ``ZipFile``. Only the read interface is implemented.
|
||||
|
||||
Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
|
||||
ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
|
||||
"""
|
||||
|
||||
import enum
|
||||
import os
|
||||
import struct
|
||||
from typing import IO, Optional, Tuple
|
||||
|
||||
|
||||
class ELFInvalid(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class EIClass(enum.IntEnum):
|
||||
C32 = 1
|
||||
C64 = 2
|
||||
|
||||
|
||||
class EIData(enum.IntEnum):
|
||||
Lsb = 1
|
||||
Msb = 2
|
||||
|
||||
|
||||
class EMachine(enum.IntEnum):
|
||||
I386 = 3
|
||||
S390 = 22
|
||||
Arm = 40
|
||||
X8664 = 62
|
||||
AArc64 = 183
|
||||
|
||||
|
||||
class ELFFile:
|
||||
"""
|
||||
Representation of an ELF executable.
|
||||
"""
|
||||
|
||||
def __init__(self, f: IO[bytes]) -> None:
|
||||
self._f = f
|
||||
|
||||
try:
|
||||
ident = self._read("16B")
|
||||
except struct.error:
|
||||
raise ELFInvalid("unable to parse identification")
|
||||
magic = bytes(ident[:4])
|
||||
if magic != b"\x7fELF":
|
||||
raise ELFInvalid(f"invalid magic: {magic!r}")
|
||||
|
||||
self.capacity = ident[4] # Format for program header (bitness).
|
||||
self.encoding = ident[5] # Data structure encoding (endianness).
|
||||
|
||||
try:
|
||||
# e_fmt: Format for program header.
|
||||
# p_fmt: Format for section header.
|
||||
# p_idx: Indexes to find p_type, p_offset, and p_filesz.
|
||||
e_fmt, self._p_fmt, self._p_idx = {
|
||||
(1, 1): ("<HHIIIIIHHH", "<IIIIIIII", (0, 1, 4)), # 32-bit LSB.
|
||||
(1, 2): (">HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB.
|
||||
(2, 1): ("<HHIQQQIHHH", "<IIQQQQQQ", (0, 2, 5)), # 64-bit LSB.
|
||||
(2, 2): (">HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB.
|
||||
}[(self.capacity, self.encoding)]
|
||||
except KeyError:
|
||||
raise ELFInvalid(
|
||||
f"unrecognized capacity ({self.capacity}) or "
|
||||
f"encoding ({self.encoding})"
|
||||
)
|
||||
|
||||
try:
|
||||
(
|
||||
_,
|
||||
self.machine, # Architecture type.
|
||||
_,
|
||||
_,
|
||||
self._e_phoff, # Offset of program header.
|
||||
_,
|
||||
self.flags, # Processor-specific flags.
|
||||
_,
|
||||
self._e_phentsize, # Size of section.
|
||||
self._e_phnum, # Number of sections.
|
||||
) = self._read(e_fmt)
|
||||
except struct.error as e:
|
||||
raise ELFInvalid("unable to parse machine and section information") from e
|
||||
|
||||
def _read(self, fmt: str) -> Tuple[int, ...]:
|
||||
return struct.unpack(fmt, self._f.read(struct.calcsize(fmt)))
|
||||
|
||||
@property
|
||||
def interpreter(self) -> Optional[str]:
|
||||
"""
|
||||
The path recorded in the ``PT_INTERP`` section header.
|
||||
"""
|
||||
for index in range(self._e_phnum):
|
||||
self._f.seek(self._e_phoff + self._e_phentsize * index)
|
||||
try:
|
||||
data = self._read(self._p_fmt)
|
||||
except struct.error:
|
||||
continue
|
||||
if data[self._p_idx[0]] != 3: # Not PT_INTERP.
|
||||
continue
|
||||
self._f.seek(data[self._p_idx[1]])
|
||||
return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0")
|
||||
return None
|
147
third_party/python/packaging/packaging/_manylinux.py
vendored
147
third_party/python/packaging/packaging/_manylinux.py
vendored
@ -1,121 +1,60 @@
|
||||
import collections
|
||||
import contextlib
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
import warnings
|
||||
from typing import IO, Dict, Iterator, NamedTuple, Optional, Tuple
|
||||
from typing import Dict, Generator, Iterator, NamedTuple, Optional, Tuple
|
||||
|
||||
from ._elffile import EIClass, EIData, ELFFile, EMachine
|
||||
|
||||
EF_ARM_ABIMASK = 0xFF000000
|
||||
EF_ARM_ABI_VER5 = 0x05000000
|
||||
EF_ARM_ABI_FLOAT_HARD = 0x00000400
|
||||
|
||||
|
||||
# Python does not provide platform information at sufficient granularity to
|
||||
# identify the architecture of the running executable in some cases, so we
|
||||
# determine it dynamically by reading the information from the running
|
||||
# process. This only applies on Linux, which uses the ELF format.
|
||||
class _ELFFileHeader:
|
||||
# https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
|
||||
class _InvalidELFFileHeader(ValueError):
|
||||
"""
|
||||
An invalid ELF file header was found.
|
||||
"""
|
||||
|
||||
ELF_MAGIC_NUMBER = 0x7F454C46
|
||||
ELFCLASS32 = 1
|
||||
ELFCLASS64 = 2
|
||||
ELFDATA2LSB = 1
|
||||
ELFDATA2MSB = 2
|
||||
EM_386 = 3
|
||||
EM_S390 = 22
|
||||
EM_ARM = 40
|
||||
EM_X86_64 = 62
|
||||
EF_ARM_ABIMASK = 0xFF000000
|
||||
EF_ARM_ABI_VER5 = 0x05000000
|
||||
EF_ARM_ABI_FLOAT_HARD = 0x00000400
|
||||
|
||||
def __init__(self, file: IO[bytes]) -> None:
|
||||
def unpack(fmt: str) -> int:
|
||||
try:
|
||||
data = file.read(struct.calcsize(fmt))
|
||||
result: Tuple[int, ...] = struct.unpack(fmt, data)
|
||||
except struct.error:
|
||||
raise _ELFFileHeader._InvalidELFFileHeader()
|
||||
return result[0]
|
||||
|
||||
self.e_ident_magic = unpack(">I")
|
||||
if self.e_ident_magic != self.ELF_MAGIC_NUMBER:
|
||||
raise _ELFFileHeader._InvalidELFFileHeader()
|
||||
self.e_ident_class = unpack("B")
|
||||
if self.e_ident_class not in {self.ELFCLASS32, self.ELFCLASS64}:
|
||||
raise _ELFFileHeader._InvalidELFFileHeader()
|
||||
self.e_ident_data = unpack("B")
|
||||
if self.e_ident_data not in {self.ELFDATA2LSB, self.ELFDATA2MSB}:
|
||||
raise _ELFFileHeader._InvalidELFFileHeader()
|
||||
self.e_ident_version = unpack("B")
|
||||
self.e_ident_osabi = unpack("B")
|
||||
self.e_ident_abiversion = unpack("B")
|
||||
self.e_ident_pad = file.read(7)
|
||||
format_h = "<H" if self.e_ident_data == self.ELFDATA2LSB else ">H"
|
||||
format_i = "<I" if self.e_ident_data == self.ELFDATA2LSB else ">I"
|
||||
format_q = "<Q" if self.e_ident_data == self.ELFDATA2LSB else ">Q"
|
||||
format_p = format_i if self.e_ident_class == self.ELFCLASS32 else format_q
|
||||
self.e_type = unpack(format_h)
|
||||
self.e_machine = unpack(format_h)
|
||||
self.e_version = unpack(format_i)
|
||||
self.e_entry = unpack(format_p)
|
||||
self.e_phoff = unpack(format_p)
|
||||
self.e_shoff = unpack(format_p)
|
||||
self.e_flags = unpack(format_i)
|
||||
self.e_ehsize = unpack(format_h)
|
||||
self.e_phentsize = unpack(format_h)
|
||||
self.e_phnum = unpack(format_h)
|
||||
self.e_shentsize = unpack(format_h)
|
||||
self.e_shnum = unpack(format_h)
|
||||
self.e_shstrndx = unpack(format_h)
|
||||
|
||||
|
||||
def _get_elf_header() -> Optional[_ELFFileHeader]:
|
||||
# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
|
||||
# as the type for `path` until then.
|
||||
@contextlib.contextmanager
|
||||
def _parse_elf(path: str) -> Generator[Optional[ELFFile], None, None]:
|
||||
try:
|
||||
with open(sys.executable, "rb") as f:
|
||||
elf_header = _ELFFileHeader(f)
|
||||
except (OSError, TypeError, _ELFFileHeader._InvalidELFFileHeader):
|
||||
return None
|
||||
return elf_header
|
||||
with open(path, "rb") as f:
|
||||
yield ELFFile(f)
|
||||
except (OSError, TypeError, ValueError):
|
||||
yield None
|
||||
|
||||
|
||||
def _is_linux_armhf() -> bool:
|
||||
def _is_linux_armhf(executable: str) -> bool:
|
||||
# hard-float ABI can be detected from the ELF header of the running
|
||||
# process
|
||||
# https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
|
||||
elf_header = _get_elf_header()
|
||||
if elf_header is None:
|
||||
return False
|
||||
result = elf_header.e_ident_class == elf_header.ELFCLASS32
|
||||
result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB
|
||||
result &= elf_header.e_machine == elf_header.EM_ARM
|
||||
result &= (
|
||||
elf_header.e_flags & elf_header.EF_ARM_ABIMASK
|
||||
) == elf_header.EF_ARM_ABI_VER5
|
||||
result &= (
|
||||
elf_header.e_flags & elf_header.EF_ARM_ABI_FLOAT_HARD
|
||||
) == elf_header.EF_ARM_ABI_FLOAT_HARD
|
||||
return result
|
||||
with _parse_elf(executable) as f:
|
||||
return (
|
||||
f is not None
|
||||
and f.capacity == EIClass.C32
|
||||
and f.encoding == EIData.Lsb
|
||||
and f.machine == EMachine.Arm
|
||||
and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5
|
||||
and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD
|
||||
)
|
||||
|
||||
|
||||
def _is_linux_i686() -> bool:
|
||||
elf_header = _get_elf_header()
|
||||
if elf_header is None:
|
||||
return False
|
||||
result = elf_header.e_ident_class == elf_header.ELFCLASS32
|
||||
result &= elf_header.e_ident_data == elf_header.ELFDATA2LSB
|
||||
result &= elf_header.e_machine == elf_header.EM_386
|
||||
return result
|
||||
def _is_linux_i686(executable: str) -> bool:
|
||||
with _parse_elf(executable) as f:
|
||||
return (
|
||||
f is not None
|
||||
and f.capacity == EIClass.C32
|
||||
and f.encoding == EIData.Lsb
|
||||
and f.machine == EMachine.I386
|
||||
)
|
||||
|
||||
|
||||
def _have_compatible_abi(arch: str) -> bool:
|
||||
def _have_compatible_abi(executable: str, arch: str) -> bool:
|
||||
if arch == "armv7l":
|
||||
return _is_linux_armhf()
|
||||
return _is_linux_armhf(executable)
|
||||
if arch == "i686":
|
||||
return _is_linux_i686()
|
||||
return _is_linux_i686(executable)
|
||||
return arch in {"x86_64", "aarch64", "ppc64", "ppc64le", "s390x"}
|
||||
|
||||
|
||||
@ -141,10 +80,10 @@ def _glibc_version_string_confstr() -> Optional[str]:
|
||||
# platform module.
|
||||
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
|
||||
try:
|
||||
# os.confstr("CS_GNU_LIBC_VERSION") returns a string like "glibc 2.17".
|
||||
version_string = os.confstr("CS_GNU_LIBC_VERSION")
|
||||
# Should be a string like "glibc 2.17".
|
||||
version_string: str = getattr(os, "confstr")("CS_GNU_LIBC_VERSION")
|
||||
assert version_string is not None
|
||||
_, version = version_string.split()
|
||||
_, version = version_string.rsplit()
|
||||
except (AssertionError, AttributeError, OSError, ValueError):
|
||||
# os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
|
||||
return None
|
||||
@ -211,8 +150,8 @@ def _parse_glibc_version(version_str: str) -> Tuple[int, int]:
|
||||
m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
|
||||
if not m:
|
||||
warnings.warn(
|
||||
"Expected glibc version with 2 components major.minor,"
|
||||
" got: %s" % version_str,
|
||||
f"Expected glibc version with 2 components major.minor,"
|
||||
f" got: {version_str}",
|
||||
RuntimeWarning,
|
||||
)
|
||||
return -1, -1
|
||||
@ -265,7 +204,7 @@ _LEGACY_MANYLINUX_MAP = {
|
||||
|
||||
|
||||
def platform_tags(linux: str, arch: str) -> Iterator[str]:
|
||||
if not _have_compatible_abi(arch):
|
||||
if not _have_compatible_abi(sys.executable, arch):
|
||||
return
|
||||
# Oldest glibc to be supported regardless of architecture is (2, 17).
|
||||
too_old_glibc2 = _GLibCVersion(2, 16)
|
||||
|
@ -4,68 +4,13 @@ This module implements logic to detect if the currently running Python is
|
||||
linked against musl, and what musl version is used.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import operator
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import IO, Iterator, NamedTuple, Optional, Tuple
|
||||
from typing import Iterator, NamedTuple, Optional
|
||||
|
||||
|
||||
def _read_unpacked(f: IO[bytes], fmt: str) -> Tuple[int, ...]:
|
||||
return struct.unpack(fmt, f.read(struct.calcsize(fmt)))
|
||||
|
||||
|
||||
def _parse_ld_musl_from_elf(f: IO[bytes]) -> Optional[str]:
|
||||
"""Detect musl libc location by parsing the Python executable.
|
||||
|
||||
Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
|
||||
ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
|
||||
"""
|
||||
f.seek(0)
|
||||
try:
|
||||
ident = _read_unpacked(f, "16B")
|
||||
except struct.error:
|
||||
return None
|
||||
if ident[:4] != tuple(b"\x7fELF"): # Invalid magic, not ELF.
|
||||
return None
|
||||
f.seek(struct.calcsize("HHI"), 1) # Skip file type, machine, and version.
|
||||
|
||||
try:
|
||||
# e_fmt: Format for program header.
|
||||
# p_fmt: Format for section header.
|
||||
# p_idx: Indexes to find p_type, p_offset, and p_filesz.
|
||||
e_fmt, p_fmt, p_idx = {
|
||||
1: ("IIIIHHH", "IIIIIIII", (0, 1, 4)), # 32-bit.
|
||||
2: ("QQQIHHH", "IIQQQQQQ", (0, 2, 5)), # 64-bit.
|
||||
}[ident[4]]
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
p_get = operator.itemgetter(*p_idx)
|
||||
|
||||
# Find the interpreter section and return its content.
|
||||
try:
|
||||
_, e_phoff, _, _, _, e_phentsize, e_phnum = _read_unpacked(f, e_fmt)
|
||||
except struct.error:
|
||||
return None
|
||||
for i in range(e_phnum + 1):
|
||||
f.seek(e_phoff + e_phentsize * i)
|
||||
try:
|
||||
p_type, p_offset, p_filesz = p_get(_read_unpacked(f, p_fmt))
|
||||
except struct.error:
|
||||
return None
|
||||
if p_type != 3: # Not PT_INTERP.
|
||||
continue
|
||||
f.seek(p_offset)
|
||||
interpreter = os.fsdecode(f.read(p_filesz)).strip("\0")
|
||||
if "musl" not in interpreter:
|
||||
return None
|
||||
return interpreter
|
||||
return None
|
||||
from ._elffile import ELFFile
|
||||
|
||||
|
||||
class _MuslVersion(NamedTuple):
|
||||
@ -95,13 +40,12 @@ def _get_musl_version(executable: str) -> Optional[_MuslVersion]:
|
||||
Version 1.2.2
|
||||
Dynamic Program Loader
|
||||
"""
|
||||
with contextlib.ExitStack() as stack:
|
||||
try:
|
||||
f = stack.enter_context(open(executable, "rb"))
|
||||
except OSError:
|
||||
return None
|
||||
ld = _parse_ld_musl_from_elf(f)
|
||||
if not ld:
|
||||
try:
|
||||
with open(executable, "rb") as f:
|
||||
ld = ELFFile(f).interpreter
|
||||
except (OSError, TypeError, ValueError):
|
||||
return None
|
||||
if ld is None or "musl" not in ld:
|
||||
return None
|
||||
proc = subprocess.run([ld], stderr=subprocess.PIPE, universal_newlines=True)
|
||||
return _parse_musl_version(proc.stderr)
|
||||
|
353
third_party/python/packaging/packaging/_parser.py
vendored
Normal file
353
third_party/python/packaging/packaging/_parser.py
vendored
Normal file
@ -0,0 +1,353 @@
|
||||
"""Handwritten parser of dependency specifiers.
|
||||
|
||||
The docstring for each __parse_* function contains ENBF-inspired grammar representing
|
||||
the implementation.
|
||||
"""
|
||||
|
||||
import ast
|
||||
from typing import Any, List, NamedTuple, Optional, Tuple, Union
|
||||
|
||||
from ._tokenizer import DEFAULT_RULES, Tokenizer
|
||||
|
||||
|
||||
class Node:
|
||||
def __init__(self, value: str) -> None:
|
||||
self.value = value
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self.__class__.__name__}('{self}')>"
|
||||
|
||||
def serialize(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Variable(Node):
|
||||
def serialize(self) -> str:
|
||||
return str(self)
|
||||
|
||||
|
||||
class Value(Node):
|
||||
def serialize(self) -> str:
|
||||
return f'"{self}"'
|
||||
|
||||
|
||||
class Op(Node):
|
||||
def serialize(self) -> str:
|
||||
return str(self)
|
||||
|
||||
|
||||
MarkerVar = Union[Variable, Value]
|
||||
MarkerItem = Tuple[MarkerVar, Op, MarkerVar]
|
||||
# MarkerAtom = Union[MarkerItem, List["MarkerAtom"]]
|
||||
# MarkerList = List[Union["MarkerList", MarkerAtom, str]]
|
||||
# mypy does not support recursive type definition
|
||||
# https://github.com/python/mypy/issues/731
|
||||
MarkerAtom = Any
|
||||
MarkerList = List[Any]
|
||||
|
||||
|
||||
class ParsedRequirement(NamedTuple):
|
||||
name: str
|
||||
url: str
|
||||
extras: List[str]
|
||||
specifier: str
|
||||
marker: Optional[MarkerList]
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
# Recursive descent parser for dependency specifier
|
||||
# --------------------------------------------------------------------------------------
|
||||
def parse_requirement(source: str) -> ParsedRequirement:
|
||||
return _parse_requirement(Tokenizer(source, rules=DEFAULT_RULES))
|
||||
|
||||
|
||||
def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement:
|
||||
"""
|
||||
requirement = WS? IDENTIFIER WS? extras WS? requirement_details
|
||||
"""
|
||||
tokenizer.consume("WS")
|
||||
|
||||
name_token = tokenizer.expect(
|
||||
"IDENTIFIER", expected="package name at the start of dependency specifier"
|
||||
)
|
||||
name = name_token.text
|
||||
tokenizer.consume("WS")
|
||||
|
||||
extras = _parse_extras(tokenizer)
|
||||
tokenizer.consume("WS")
|
||||
|
||||
url, specifier, marker = _parse_requirement_details(tokenizer)
|
||||
tokenizer.expect("END", expected="end of dependency specifier")
|
||||
|
||||
return ParsedRequirement(name, url, extras, specifier, marker)
|
||||
|
||||
|
||||
def _parse_requirement_details(
|
||||
tokenizer: Tokenizer,
|
||||
) -> Tuple[str, str, Optional[MarkerList]]:
|
||||
"""
|
||||
requirement_details = AT URL (WS requirement_marker?)?
|
||||
| specifier WS? (requirement_marker)?
|
||||
"""
|
||||
|
||||
specifier = ""
|
||||
url = ""
|
||||
marker = None
|
||||
|
||||
if tokenizer.check("AT"):
|
||||
tokenizer.read()
|
||||
tokenizer.consume("WS")
|
||||
|
||||
url_start = tokenizer.position
|
||||
url = tokenizer.expect("URL", expected="URL after @").text
|
||||
if tokenizer.check("END", peek=True):
|
||||
return (url, specifier, marker)
|
||||
|
||||
tokenizer.expect("WS", expected="whitespace after URL")
|
||||
|
||||
# The input might end after whitespace.
|
||||
if tokenizer.check("END", peek=True):
|
||||
return (url, specifier, marker)
|
||||
|
||||
marker = _parse_requirement_marker(
|
||||
tokenizer, span_start=url_start, after="URL and whitespace"
|
||||
)
|
||||
else:
|
||||
specifier_start = tokenizer.position
|
||||
specifier = _parse_specifier(tokenizer)
|
||||
tokenizer.consume("WS")
|
||||
|
||||
if tokenizer.check("END", peek=True):
|
||||
return (url, specifier, marker)
|
||||
|
||||
marker = _parse_requirement_marker(
|
||||
tokenizer,
|
||||
span_start=specifier_start,
|
||||
after=(
|
||||
"version specifier"
|
||||
if specifier
|
||||
else "name and no valid version specifier"
|
||||
),
|
||||
)
|
||||
|
||||
return (url, specifier, marker)
|
||||
|
||||
|
||||
def _parse_requirement_marker(
|
||||
tokenizer: Tokenizer, *, span_start: int, after: str
|
||||
) -> MarkerList:
|
||||
"""
|
||||
requirement_marker = SEMICOLON marker WS?
|
||||
"""
|
||||
|
||||
if not tokenizer.check("SEMICOLON"):
|
||||
tokenizer.raise_syntax_error(
|
||||
f"Expected end or semicolon (after {after})",
|
||||
span_start=span_start,
|
||||
)
|
||||
tokenizer.read()
|
||||
|
||||
marker = _parse_marker(tokenizer)
|
||||
tokenizer.consume("WS")
|
||||
|
||||
return marker
|
||||
|
||||
|
||||
def _parse_extras(tokenizer: Tokenizer) -> List[str]:
|
||||
"""
|
||||
extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)?
|
||||
"""
|
||||
if not tokenizer.check("LEFT_BRACKET", peek=True):
|
||||
return []
|
||||
|
||||
with tokenizer.enclosing_tokens(
|
||||
"LEFT_BRACKET",
|
||||
"RIGHT_BRACKET",
|
||||
around="extras",
|
||||
):
|
||||
tokenizer.consume("WS")
|
||||
extras = _parse_extras_list(tokenizer)
|
||||
tokenizer.consume("WS")
|
||||
|
||||
return extras
|
||||
|
||||
|
||||
def _parse_extras_list(tokenizer: Tokenizer) -> List[str]:
|
||||
"""
|
||||
extras_list = identifier (wsp* ',' wsp* identifier)*
|
||||
"""
|
||||
extras: List[str] = []
|
||||
|
||||
if not tokenizer.check("IDENTIFIER"):
|
||||
return extras
|
||||
|
||||
extras.append(tokenizer.read().text)
|
||||
|
||||
while True:
|
||||
tokenizer.consume("WS")
|
||||
if tokenizer.check("IDENTIFIER", peek=True):
|
||||
tokenizer.raise_syntax_error("Expected comma between extra names")
|
||||
elif not tokenizer.check("COMMA"):
|
||||
break
|
||||
|
||||
tokenizer.read()
|
||||
tokenizer.consume("WS")
|
||||
|
||||
extra_token = tokenizer.expect("IDENTIFIER", expected="extra name after comma")
|
||||
extras.append(extra_token.text)
|
||||
|
||||
return extras
|
||||
|
||||
|
||||
def _parse_specifier(tokenizer: Tokenizer) -> str:
|
||||
"""
|
||||
specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS
|
||||
| WS? version_many WS?
|
||||
"""
|
||||
with tokenizer.enclosing_tokens(
|
||||
"LEFT_PARENTHESIS",
|
||||
"RIGHT_PARENTHESIS",
|
||||
around="version specifier",
|
||||
):
|
||||
tokenizer.consume("WS")
|
||||
parsed_specifiers = _parse_version_many(tokenizer)
|
||||
tokenizer.consume("WS")
|
||||
|
||||
return parsed_specifiers
|
||||
|
||||
|
||||
def _parse_version_many(tokenizer: Tokenizer) -> str:
|
||||
"""
|
||||
version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)?
|
||||
"""
|
||||
parsed_specifiers = ""
|
||||
while tokenizer.check("SPECIFIER"):
|
||||
span_start = tokenizer.position
|
||||
parsed_specifiers += tokenizer.read().text
|
||||
if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True):
|
||||
tokenizer.raise_syntax_error(
|
||||
".* suffix can only be used with `==` or `!=` operators",
|
||||
span_start=span_start,
|
||||
span_end=tokenizer.position + 1,
|
||||
)
|
||||
if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True):
|
||||
tokenizer.raise_syntax_error(
|
||||
"Local version label can only be used with `==` or `!=` operators",
|
||||
span_start=span_start,
|
||||
span_end=tokenizer.position,
|
||||
)
|
||||
tokenizer.consume("WS")
|
||||
if not tokenizer.check("COMMA"):
|
||||
break
|
||||
parsed_specifiers += tokenizer.read().text
|
||||
tokenizer.consume("WS")
|
||||
|
||||
return parsed_specifiers
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
# Recursive descent parser for marker expression
|
||||
# --------------------------------------------------------------------------------------
|
||||
def parse_marker(source: str) -> MarkerList:
|
||||
return _parse_marker(Tokenizer(source, rules=DEFAULT_RULES))
|
||||
|
||||
|
||||
def _parse_marker(tokenizer: Tokenizer) -> MarkerList:
|
||||
"""
|
||||
marker = marker_atom (BOOLOP marker_atom)+
|
||||
"""
|
||||
expression = [_parse_marker_atom(tokenizer)]
|
||||
while tokenizer.check("BOOLOP"):
|
||||
token = tokenizer.read()
|
||||
expr_right = _parse_marker_atom(tokenizer)
|
||||
expression.extend((token.text, expr_right))
|
||||
return expression
|
||||
|
||||
|
||||
def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:
|
||||
"""
|
||||
marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS?
|
||||
| WS? marker_item WS?
|
||||
"""
|
||||
|
||||
tokenizer.consume("WS")
|
||||
if tokenizer.check("LEFT_PARENTHESIS", peek=True):
|
||||
with tokenizer.enclosing_tokens(
|
||||
"LEFT_PARENTHESIS",
|
||||
"RIGHT_PARENTHESIS",
|
||||
around="marker expression",
|
||||
):
|
||||
tokenizer.consume("WS")
|
||||
marker: MarkerAtom = _parse_marker(tokenizer)
|
||||
tokenizer.consume("WS")
|
||||
else:
|
||||
marker = _parse_marker_item(tokenizer)
|
||||
tokenizer.consume("WS")
|
||||
return marker
|
||||
|
||||
|
||||
def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem:
|
||||
"""
|
||||
marker_item = WS? marker_var WS? marker_op WS? marker_var WS?
|
||||
"""
|
||||
tokenizer.consume("WS")
|
||||
marker_var_left = _parse_marker_var(tokenizer)
|
||||
tokenizer.consume("WS")
|
||||
marker_op = _parse_marker_op(tokenizer)
|
||||
tokenizer.consume("WS")
|
||||
marker_var_right = _parse_marker_var(tokenizer)
|
||||
tokenizer.consume("WS")
|
||||
return (marker_var_left, marker_op, marker_var_right)
|
||||
|
||||
|
||||
def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar:
|
||||
"""
|
||||
marker_var = VARIABLE | QUOTED_STRING
|
||||
"""
|
||||
if tokenizer.check("VARIABLE"):
|
||||
return process_env_var(tokenizer.read().text.replace(".", "_"))
|
||||
elif tokenizer.check("QUOTED_STRING"):
|
||||
return process_python_str(tokenizer.read().text)
|
||||
else:
|
||||
tokenizer.raise_syntax_error(
|
||||
message="Expected a marker variable or quoted string"
|
||||
)
|
||||
|
||||
|
||||
def process_env_var(env_var: str) -> Variable:
|
||||
if (
|
||||
env_var == "platform_python_implementation"
|
||||
or env_var == "python_implementation"
|
||||
):
|
||||
return Variable("platform_python_implementation")
|
||||
else:
|
||||
return Variable(env_var)
|
||||
|
||||
|
||||
def process_python_str(python_str: str) -> Value:
|
||||
value = ast.literal_eval(python_str)
|
||||
return Value(str(value))
|
||||
|
||||
|
||||
def _parse_marker_op(tokenizer: Tokenizer) -> Op:
|
||||
"""
|
||||
marker_op = IN | NOT IN | OP
|
||||
"""
|
||||
if tokenizer.check("IN"):
|
||||
tokenizer.read()
|
||||
return Op("in")
|
||||
elif tokenizer.check("NOT"):
|
||||
tokenizer.read()
|
||||
tokenizer.expect("WS", expected="whitespace after 'not'")
|
||||
tokenizer.expect("IN", expected="'in' after 'not'")
|
||||
return Op("not in")
|
||||
elif tokenizer.check("OP"):
|
||||
return Op(tokenizer.read().text)
|
||||
else:
|
||||
return tokenizer.raise_syntax_error(
|
||||
"Expected marker operator, one of "
|
||||
"<=, <, !=, ==, >=, >, ~=, ===, in, not in"
|
||||
)
|
192
third_party/python/packaging/packaging/_tokenizer.py
vendored
Normal file
192
third_party/python/packaging/packaging/_tokenizer.py
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
import contextlib
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Iterator, NoReturn, Optional, Tuple, Union
|
||||
|
||||
from .specifiers import Specifier
|
||||
|
||||
|
||||
@dataclass
|
||||
class Token:
|
||||
name: str
|
||||
text: str
|
||||
position: int
|
||||
|
||||
|
||||
class ParserSyntaxError(Exception):
|
||||
"""The provided source text could not be parsed correctly."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
*,
|
||||
source: str,
|
||||
span: Tuple[int, int],
|
||||
) -> None:
|
||||
self.span = span
|
||||
self.message = message
|
||||
self.source = source
|
||||
|
||||
super().__init__()
|
||||
|
||||
def __str__(self) -> str:
|
||||
marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^"
|
||||
return "\n ".join([self.message, self.source, marker])
|
||||
|
||||
|
||||
DEFAULT_RULES: "Dict[str, Union[str, re.Pattern[str]]]" = {
|
||||
"LEFT_PARENTHESIS": r"\(",
|
||||
"RIGHT_PARENTHESIS": r"\)",
|
||||
"LEFT_BRACKET": r"\[",
|
||||
"RIGHT_BRACKET": r"\]",
|
||||
"SEMICOLON": r";",
|
||||
"COMMA": r",",
|
||||
"QUOTED_STRING": re.compile(
|
||||
r"""
|
||||
(
|
||||
('[^']*')
|
||||
|
|
||||
("[^"]*")
|
||||
)
|
||||
""",
|
||||
re.VERBOSE,
|
||||
),
|
||||
"OP": r"(===|==|~=|!=|<=|>=|<|>)",
|
||||
"BOOLOP": r"\b(or|and)\b",
|
||||
"IN": r"\bin\b",
|
||||
"NOT": r"\bnot\b",
|
||||
"VARIABLE": re.compile(
|
||||
r"""
|
||||
\b(
|
||||
python_version
|
||||
|python_full_version
|
||||
|os[._]name
|
||||
|sys[._]platform
|
||||
|platform_(release|system)
|
||||
|platform[._](version|machine|python_implementation)
|
||||
|python_implementation
|
||||
|implementation_(name|version)
|
||||
|extra
|
||||
)\b
|
||||
""",
|
||||
re.VERBOSE,
|
||||
),
|
||||
"SPECIFIER": re.compile(
|
||||
Specifier._operator_regex_str + Specifier._version_regex_str,
|
||||
re.VERBOSE | re.IGNORECASE,
|
||||
),
|
||||
"AT": r"\@",
|
||||
"URL": r"[^ \t]+",
|
||||
"IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b",
|
||||
"VERSION_PREFIX_TRAIL": r"\.\*",
|
||||
"VERSION_LOCAL_LABEL_TRAIL": r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*",
|
||||
"WS": r"[ \t]+",
|
||||
"END": r"$",
|
||||
}
|
||||
|
||||
|
||||
class Tokenizer:
|
||||
"""Context-sensitive token parsing.
|
||||
|
||||
Provides methods to examine the input stream to check whether the next token
|
||||
matches.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source: str,
|
||||
*,
|
||||
rules: "Dict[str, Union[str, re.Pattern[str]]]",
|
||||
) -> None:
|
||||
self.source = source
|
||||
self.rules: Dict[str, re.Pattern[str]] = {
|
||||
name: re.compile(pattern) for name, pattern in rules.items()
|
||||
}
|
||||
self.next_token: Optional[Token] = None
|
||||
self.position = 0
|
||||
|
||||
def consume(self, name: str) -> None:
|
||||
"""Move beyond provided token name, if at current position."""
|
||||
if self.check(name):
|
||||
self.read()
|
||||
|
||||
def check(self, name: str, *, peek: bool = False) -> bool:
|
||||
"""Check whether the next token has the provided name.
|
||||
|
||||
By default, if the check succeeds, the token *must* be read before
|
||||
another check. If `peek` is set to `True`, the token is not loaded and
|
||||
would need to be checked again.
|
||||
"""
|
||||
assert (
|
||||
self.next_token is None
|
||||
), f"Cannot check for {name!r}, already have {self.next_token!r}"
|
||||
assert name in self.rules, f"Unknown token name: {name!r}"
|
||||
|
||||
expression = self.rules[name]
|
||||
|
||||
match = expression.match(self.source, self.position)
|
||||
if match is None:
|
||||
return False
|
||||
if not peek:
|
||||
self.next_token = Token(name, match[0], self.position)
|
||||
return True
|
||||
|
||||
def expect(self, name: str, *, expected: str) -> Token:
|
||||
"""Expect a certain token name next, failing with a syntax error otherwise.
|
||||
|
||||
The token is *not* read.
|
||||
"""
|
||||
if not self.check(name):
|
||||
raise self.raise_syntax_error(f"Expected {expected}")
|
||||
return self.read()
|
||||
|
||||
def read(self) -> Token:
|
||||
"""Consume the next token and return it."""
|
||||
token = self.next_token
|
||||
assert token is not None
|
||||
|
||||
self.position += len(token.text)
|
||||
self.next_token = None
|
||||
|
||||
return token
|
||||
|
||||
def raise_syntax_error(
|
||||
self,
|
||||
message: str,
|
||||
*,
|
||||
span_start: Optional[int] = None,
|
||||
span_end: Optional[int] = None,
|
||||
) -> NoReturn:
|
||||
"""Raise ParserSyntaxError at the given position."""
|
||||
span = (
|
||||
self.position if span_start is None else span_start,
|
||||
self.position if span_end is None else span_end,
|
||||
)
|
||||
raise ParserSyntaxError(
|
||||
message,
|
||||
source=self.source,
|
||||
span=span,
|
||||
)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def enclosing_tokens(
|
||||
self, open_token: str, close_token: str, *, around: str
|
||||
) -> Iterator[None]:
|
||||
if self.check(open_token):
|
||||
open_position = self.position
|
||||
self.read()
|
||||
else:
|
||||
open_position = None
|
||||
|
||||
yield
|
||||
|
||||
if open_position is None:
|
||||
return
|
||||
|
||||
if not self.check(close_token):
|
||||
self.raise_syntax_error(
|
||||
f"Expected matching {close_token} for {open_token}, after {around}",
|
||||
span_start=open_position,
|
||||
)
|
||||
|
||||
self.read()
|
206
third_party/python/packaging/packaging/markers.py
vendored
206
third_party/python/packaging/packaging/markers.py
vendored
@ -8,19 +8,17 @@ import platform
|
||||
import sys
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||
|
||||
from pyparsing import ( # noqa: N817
|
||||
Forward,
|
||||
Group,
|
||||
Literal as L,
|
||||
ParseException,
|
||||
ParseResults,
|
||||
QuotedString,
|
||||
ZeroOrMore,
|
||||
stringEnd,
|
||||
stringStart,
|
||||
from ._parser import (
|
||||
MarkerAtom,
|
||||
MarkerList,
|
||||
Op,
|
||||
Value,
|
||||
Variable,
|
||||
parse_marker as _parse_marker,
|
||||
)
|
||||
|
||||
from ._tokenizer import ParserSyntaxError
|
||||
from .specifiers import InvalidSpecifier, Specifier
|
||||
from .utils import canonicalize_name
|
||||
|
||||
__all__ = [
|
||||
"InvalidMarker",
|
||||
@ -52,101 +50,24 @@ class UndefinedEnvironmentName(ValueError):
|
||||
"""
|
||||
|
||||
|
||||
class Node:
|
||||
def __init__(self, value: Any) -> None:
|
||||
self.value = value
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.value)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self.__class__.__name__}('{self}')>"
|
||||
|
||||
def serialize(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Variable(Node):
|
||||
def serialize(self) -> str:
|
||||
return str(self)
|
||||
|
||||
|
||||
class Value(Node):
|
||||
def serialize(self) -> str:
|
||||
return f'"{self}"'
|
||||
|
||||
|
||||
class Op(Node):
|
||||
def serialize(self) -> str:
|
||||
return str(self)
|
||||
|
||||
|
||||
VARIABLE = (
|
||||
L("implementation_version")
|
||||
| L("platform_python_implementation")
|
||||
| L("implementation_name")
|
||||
| L("python_full_version")
|
||||
| L("platform_release")
|
||||
| L("platform_version")
|
||||
| L("platform_machine")
|
||||
| L("platform_system")
|
||||
| L("python_version")
|
||||
| L("sys_platform")
|
||||
| L("os_name")
|
||||
| L("os.name") # PEP-345
|
||||
| L("sys.platform") # PEP-345
|
||||
| L("platform.version") # PEP-345
|
||||
| L("platform.machine") # PEP-345
|
||||
| L("platform.python_implementation") # PEP-345
|
||||
| L("python_implementation") # undocumented setuptools legacy
|
||||
| L("extra") # PEP-508
|
||||
)
|
||||
ALIASES = {
|
||||
"os.name": "os_name",
|
||||
"sys.platform": "sys_platform",
|
||||
"platform.version": "platform_version",
|
||||
"platform.machine": "platform_machine",
|
||||
"platform.python_implementation": "platform_python_implementation",
|
||||
"python_implementation": "platform_python_implementation",
|
||||
}
|
||||
VARIABLE.setParseAction(lambda s, l, t: Variable(ALIASES.get(t[0], t[0])))
|
||||
|
||||
VERSION_CMP = (
|
||||
L("===") | L("==") | L(">=") | L("<=") | L("!=") | L("~=") | L(">") | L("<")
|
||||
)
|
||||
|
||||
MARKER_OP = VERSION_CMP | L("not in") | L("in")
|
||||
MARKER_OP.setParseAction(lambda s, l, t: Op(t[0]))
|
||||
|
||||
MARKER_VALUE = QuotedString("'") | QuotedString('"')
|
||||
MARKER_VALUE.setParseAction(lambda s, l, t: Value(t[0]))
|
||||
|
||||
BOOLOP = L("and") | L("or")
|
||||
|
||||
MARKER_VAR = VARIABLE | MARKER_VALUE
|
||||
|
||||
MARKER_ITEM = Group(MARKER_VAR + MARKER_OP + MARKER_VAR)
|
||||
MARKER_ITEM.setParseAction(lambda s, l, t: tuple(t[0]))
|
||||
|
||||
LPAREN = L("(").suppress()
|
||||
RPAREN = L(")").suppress()
|
||||
|
||||
MARKER_EXPR = Forward()
|
||||
MARKER_ATOM = MARKER_ITEM | Group(LPAREN + MARKER_EXPR + RPAREN)
|
||||
MARKER_EXPR << MARKER_ATOM + ZeroOrMore(BOOLOP + MARKER_EXPR)
|
||||
|
||||
MARKER = stringStart + MARKER_EXPR + stringEnd
|
||||
|
||||
|
||||
def _coerce_parse_result(results: Union[ParseResults, List[Any]]) -> List[Any]:
|
||||
if isinstance(results, ParseResults):
|
||||
return [_coerce_parse_result(i) for i in results]
|
||||
else:
|
||||
return results
|
||||
def _normalize_extra_values(results: Any) -> Any:
|
||||
"""
|
||||
Normalize extra values.
|
||||
"""
|
||||
if isinstance(results[0], tuple):
|
||||
lhs, op, rhs = results[0]
|
||||
if isinstance(lhs, Variable) and lhs.value == "extra":
|
||||
normalized_extra = canonicalize_name(rhs.value)
|
||||
rhs = Value(normalized_extra)
|
||||
elif isinstance(rhs, Variable) and rhs.value == "extra":
|
||||
normalized_extra = canonicalize_name(lhs.value)
|
||||
lhs = Value(normalized_extra)
|
||||
results[0] = lhs, op, rhs
|
||||
return results
|
||||
|
||||
|
||||
def _format_marker(
|
||||
marker: Union[List[str], Tuple[Node, ...], str], first: Optional[bool] = True
|
||||
marker: Union[List[str], MarkerAtom, str], first: Optional[bool] = True
|
||||
) -> str:
|
||||
|
||||
assert isinstance(marker, (list, tuple, str))
|
||||
@ -192,7 +113,7 @@ def _eval_op(lhs: str, op: Op, rhs: str) -> bool:
|
||||
except InvalidSpecifier:
|
||||
pass
|
||||
else:
|
||||
return spec.contains(lhs)
|
||||
return spec.contains(lhs, prereleases=True)
|
||||
|
||||
oper: Optional[Operator] = _operators.get(op.serialize())
|
||||
if oper is None:
|
||||
@ -201,25 +122,19 @@ def _eval_op(lhs: str, op: Op, rhs: str) -> bool:
|
||||
return oper(lhs, rhs)
|
||||
|
||||
|
||||
class Undefined:
|
||||
pass
|
||||
def _normalize(*values: str, key: str) -> Tuple[str, ...]:
|
||||
# PEP 685 – Comparison of extra names for optional distribution dependencies
|
||||
# https://peps.python.org/pep-0685/
|
||||
# > When comparing extra names, tools MUST normalize the names being
|
||||
# > compared using the semantics outlined in PEP 503 for names
|
||||
if key == "extra":
|
||||
return tuple(canonicalize_name(v) for v in values)
|
||||
|
||||
# other environment markers don't have such standards
|
||||
return values
|
||||
|
||||
|
||||
_undefined = Undefined()
|
||||
|
||||
|
||||
def _get_env(environment: Dict[str, str], name: str) -> str:
|
||||
value: Union[str, Undefined] = environment.get(name, _undefined)
|
||||
|
||||
if isinstance(value, Undefined):
|
||||
raise UndefinedEnvironmentName(
|
||||
f"{name!r} does not exist in evaluation environment."
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def _evaluate_markers(markers: List[Any], environment: Dict[str, str]) -> bool:
|
||||
def _evaluate_markers(markers: MarkerList, environment: Dict[str, str]) -> bool:
|
||||
groups: List[List[bool]] = [[]]
|
||||
|
||||
for marker in markers:
|
||||
@ -231,12 +146,15 @@ def _evaluate_markers(markers: List[Any], environment: Dict[str, str]) -> bool:
|
||||
lhs, op, rhs = marker
|
||||
|
||||
if isinstance(lhs, Variable):
|
||||
lhs_value = _get_env(environment, lhs.value)
|
||||
environment_key = lhs.value
|
||||
lhs_value = environment[environment_key]
|
||||
rhs_value = rhs.value
|
||||
else:
|
||||
lhs_value = lhs.value
|
||||
rhs_value = _get_env(environment, rhs.value)
|
||||
environment_key = rhs.value
|
||||
rhs_value = environment[environment_key]
|
||||
|
||||
lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
|
||||
groups[-1].append(_eval_op(lhs_value, op, rhs_value))
|
||||
else:
|
||||
assert marker in ["and", "or"]
|
||||
@ -274,13 +192,29 @@ def default_environment() -> Dict[str, str]:
|
||||
|
||||
class Marker:
|
||||
def __init__(self, marker: str) -> None:
|
||||
# Note: We create a Marker object without calling this constructor in
|
||||
# packaging.requirements.Requirement. If any additional logic is
|
||||
# added here, make sure to mirror/adapt Requirement.
|
||||
try:
|
||||
self._markers = _coerce_parse_result(MARKER.parseString(marker))
|
||||
except ParseException as e:
|
||||
raise InvalidMarker(
|
||||
f"Invalid marker: {marker!r}, parse error at "
|
||||
f"{marker[e.loc : e.loc + 8]!r}"
|
||||
)
|
||||
self._markers = _normalize_extra_values(_parse_marker(marker))
|
||||
# The attribute `_markers` can be described in terms of a recursive type:
|
||||
# MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]]
|
||||
#
|
||||
# For example, the following expression:
|
||||
# python_version > "3.6" or (python_version == "3.6" and os_name == "unix")
|
||||
#
|
||||
# is parsed into:
|
||||
# [
|
||||
# (<Variable('python_version')>, <Op('>')>, <Value('3.6')>),
|
||||
# 'and',
|
||||
# [
|
||||
# (<Variable('python_version')>, <Op('==')>, <Value('3.6')>),
|
||||
# 'or',
|
||||
# (<Variable('os_name')>, <Op('==')>, <Value('unix')>)
|
||||
# ]
|
||||
# ]
|
||||
except ParserSyntaxError as e:
|
||||
raise InvalidMarker(str(e)) from e
|
||||
|
||||
def __str__(self) -> str:
|
||||
return _format_marker(self._markers)
|
||||
@ -288,6 +222,15 @@ class Marker:
|
||||
def __repr__(self) -> str:
|
||||
return f"<Marker('{self}')>"
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.__class__.__name__, str(self)))
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, Marker):
|
||||
return NotImplemented
|
||||
|
||||
return str(self) == str(other)
|
||||
|
||||
def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool:
|
||||
"""Evaluate a marker.
|
||||
|
||||
@ -298,7 +241,12 @@ class Marker:
|
||||
The environment is determined from the current Python process.
|
||||
"""
|
||||
current_environment = default_environment()
|
||||
current_environment["extra"] = ""
|
||||
if environment is not None:
|
||||
current_environment.update(environment)
|
||||
# The API used to allow setting extra to None. We need to handle this
|
||||
# case for backwards compatibility.
|
||||
if current_environment["extra"] is None:
|
||||
current_environment["extra"] = ""
|
||||
|
||||
return _evaluate_markers(self._markers, current_environment)
|
||||
|
408
third_party/python/packaging/packaging/metadata.py
vendored
Normal file
408
third_party/python/packaging/packaging/metadata.py
vendored
Normal file
@ -0,0 +1,408 @@
|
||||
import email.feedparser
|
||||
import email.header
|
||||
import email.message
|
||||
import email.parser
|
||||
import email.policy
|
||||
import sys
|
||||
import typing
|
||||
from typing import Dict, List, Optional, Tuple, Union, cast
|
||||
|
||||
if sys.version_info >= (3, 8): # pragma: no cover
|
||||
from typing import TypedDict
|
||||
else: # pragma: no cover
|
||||
if typing.TYPE_CHECKING:
|
||||
from typing_extensions import TypedDict
|
||||
else:
|
||||
try:
|
||||
from typing_extensions import TypedDict
|
||||
except ImportError:
|
||||
|
||||
class TypedDict:
|
||||
def __init_subclass__(*_args, **_kwargs):
|
||||
pass
|
||||
|
||||
|
||||
# The RawMetadata class attempts to make as few assumptions about the underlying
|
||||
# serialization formats as possible. The idea is that as long as a serialization
|
||||
# formats offer some very basic primitives in *some* way then we can support
|
||||
# serializing to and from that format.
|
||||
class RawMetadata(TypedDict, total=False):
|
||||
"""A dictionary of raw core metadata.
|
||||
|
||||
Each field in core metadata maps to a key of this dictionary (when data is
|
||||
provided). The key is lower-case and underscores are used instead of dashes
|
||||
compared to the equivalent core metadata field. Any core metadata field that
|
||||
can be specified multiple times or can hold multiple values in a single
|
||||
field have a key with a plural name.
|
||||
|
||||
Core metadata fields that can be specified multiple times are stored as a
|
||||
list or dict depending on which is appropriate for the field. Any fields
|
||||
which hold multiple values in a single field are stored as a list.
|
||||
|
||||
"""
|
||||
|
||||
# Metadata 1.0 - PEP 241
|
||||
metadata_version: str
|
||||
name: str
|
||||
version: str
|
||||
platforms: List[str]
|
||||
summary: str
|
||||
description: str
|
||||
keywords: List[str]
|
||||
home_page: str
|
||||
author: str
|
||||
author_email: str
|
||||
license: str
|
||||
|
||||
# Metadata 1.1 - PEP 314
|
||||
supported_platforms: List[str]
|
||||
download_url: str
|
||||
classifiers: List[str]
|
||||
requires: List[str]
|
||||
provides: List[str]
|
||||
obsoletes: List[str]
|
||||
|
||||
# Metadata 1.2 - PEP 345
|
||||
maintainer: str
|
||||
maintainer_email: str
|
||||
requires_dist: List[str]
|
||||
provides_dist: List[str]
|
||||
obsoletes_dist: List[str]
|
||||
requires_python: str
|
||||
requires_external: List[str]
|
||||
project_urls: Dict[str, str]
|
||||
|
||||
# Metadata 2.0
|
||||
# PEP 426 attempted to completely revamp the metadata format
|
||||
# but got stuck without ever being able to build consensus on
|
||||
# it and ultimately ended up withdrawn.
|
||||
#
|
||||
# However, a number of tools had started emiting METADATA with
|
||||
# `2.0` Metadata-Version, so for historical reasons, this version
|
||||
# was skipped.
|
||||
|
||||
# Metadata 2.1 - PEP 566
|
||||
description_content_type: str
|
||||
provides_extra: List[str]
|
||||
|
||||
# Metadata 2.2 - PEP 643
|
||||
dynamic: List[str]
|
||||
|
||||
# Metadata 2.3 - PEP 685
|
||||
# No new fields were added in PEP 685, just some edge case were
|
||||
# tightened up to provide better interoptability.
|
||||
|
||||
|
||||
_STRING_FIELDS = {
|
||||
"author",
|
||||
"author_email",
|
||||
"description",
|
||||
"description_content_type",
|
||||
"download_url",
|
||||
"home_page",
|
||||
"license",
|
||||
"maintainer",
|
||||
"maintainer_email",
|
||||
"metadata_version",
|
||||
"name",
|
||||
"requires_python",
|
||||
"summary",
|
||||
"version",
|
||||
}
|
||||
|
||||
_LIST_STRING_FIELDS = {
|
||||
"classifiers",
|
||||
"dynamic",
|
||||
"obsoletes",
|
||||
"obsoletes_dist",
|
||||
"platforms",
|
||||
"provides",
|
||||
"provides_dist",
|
||||
"provides_extra",
|
||||
"requires",
|
||||
"requires_dist",
|
||||
"requires_external",
|
||||
"supported_platforms",
|
||||
}
|
||||
|
||||
|
||||
def _parse_keywords(data: str) -> List[str]:
|
||||
"""Split a string of comma-separate keyboards into a list of keywords."""
|
||||
return [k.strip() for k in data.split(",")]
|
||||
|
||||
|
||||
def _parse_project_urls(data: List[str]) -> Dict[str, str]:
|
||||
"""Parse a list of label/URL string pairings separated by a comma."""
|
||||
urls = {}
|
||||
for pair in data:
|
||||
# Our logic is slightly tricky here as we want to try and do
|
||||
# *something* reasonable with malformed data.
|
||||
#
|
||||
# The main thing that we have to worry about, is data that does
|
||||
# not have a ',' at all to split the label from the Value. There
|
||||
# isn't a singular right answer here, and we will fail validation
|
||||
# later on (if the caller is validating) so it doesn't *really*
|
||||
# matter, but since the missing value has to be an empty str
|
||||
# and our return value is dict[str, str], if we let the key
|
||||
# be the missing value, then they'd have multiple '' values that
|
||||
# overwrite each other in a accumulating dict.
|
||||
#
|
||||
# The other potentional issue is that it's possible to have the
|
||||
# same label multiple times in the metadata, with no solid "right"
|
||||
# answer with what to do in that case. As such, we'll do the only
|
||||
# thing we can, which is treat the field as unparseable and add it
|
||||
# to our list of unparsed fields.
|
||||
parts = [p.strip() for p in pair.split(",", 1)]
|
||||
parts.extend([""] * (max(0, 2 - len(parts)))) # Ensure 2 items
|
||||
|
||||
# TODO: The spec doesn't say anything about if the keys should be
|
||||
# considered case sensitive or not... logically they should
|
||||
# be case-preserving and case-insensitive, but doing that
|
||||
# would open up more cases where we might have duplicate
|
||||
# entries.
|
||||
label, url = parts
|
||||
if label in urls:
|
||||
# The label already exists in our set of urls, so this field
|
||||
# is unparseable, and we can just add the whole thing to our
|
||||
# unparseable data and stop processing it.
|
||||
raise KeyError("duplicate labels in project urls")
|
||||
urls[label] = url
|
||||
|
||||
return urls
|
||||
|
||||
|
||||
def _get_payload(msg: email.message.Message, source: Union[bytes, str]) -> str:
|
||||
"""Get the body of the message."""
|
||||
# If our source is a str, then our caller has managed encodings for us,
|
||||
# and we don't need to deal with it.
|
||||
if isinstance(source, str):
|
||||
payload: str = msg.get_payload()
|
||||
return payload
|
||||
# If our source is a bytes, then we're managing the encoding and we need
|
||||
# to deal with it.
|
||||
else:
|
||||
bpayload: bytes = msg.get_payload(decode=True)
|
||||
try:
|
||||
return bpayload.decode("utf8", "strict")
|
||||
except UnicodeDecodeError:
|
||||
raise ValueError("payload in an invalid encoding")
|
||||
|
||||
|
||||
# The various parse_FORMAT functions here are intended to be as lenient as
|
||||
# possible in their parsing, while still returning a correctly typed
|
||||
# RawMetadata.
|
||||
#
|
||||
# To aid in this, we also generally want to do as little touching of the
|
||||
# data as possible, except where there are possibly some historic holdovers
|
||||
# that make valid data awkward to work with.
|
||||
#
|
||||
# While this is a lower level, intermediate format than our ``Metadata``
|
||||
# class, some light touch ups can make a massive difference in usability.
|
||||
|
||||
# Map METADATA fields to RawMetadata.
|
||||
_EMAIL_TO_RAW_MAPPING = {
|
||||
"author": "author",
|
||||
"author-email": "author_email",
|
||||
"classifier": "classifiers",
|
||||
"description": "description",
|
||||
"description-content-type": "description_content_type",
|
||||
"download-url": "download_url",
|
||||
"dynamic": "dynamic",
|
||||
"home-page": "home_page",
|
||||
"keywords": "keywords",
|
||||
"license": "license",
|
||||
"maintainer": "maintainer",
|
||||
"maintainer-email": "maintainer_email",
|
||||
"metadata-version": "metadata_version",
|
||||
"name": "name",
|
||||
"obsoletes": "obsoletes",
|
||||
"obsoletes-dist": "obsoletes_dist",
|
||||
"platform": "platforms",
|
||||
"project-url": "project_urls",
|
||||
"provides": "provides",
|
||||
"provides-dist": "provides_dist",
|
||||
"provides-extra": "provides_extra",
|
||||
"requires": "requires",
|
||||
"requires-dist": "requires_dist",
|
||||
"requires-external": "requires_external",
|
||||
"requires-python": "requires_python",
|
||||
"summary": "summary",
|
||||
"supported-platform": "supported_platforms",
|
||||
"version": "version",
|
||||
}
|
||||
|
||||
|
||||
def parse_email(data: Union[bytes, str]) -> Tuple[RawMetadata, Dict[str, List[str]]]:
|
||||
"""Parse a distribution's metadata.
|
||||
|
||||
This function returns a two-item tuple of dicts. The first dict is of
|
||||
recognized fields from the core metadata specification. Fields that can be
|
||||
parsed and translated into Python's built-in types are converted
|
||||
appropriately. All other fields are left as-is. Fields that are allowed to
|
||||
appear multiple times are stored as lists.
|
||||
|
||||
The second dict contains all other fields from the metadata. This includes
|
||||
any unrecognized fields. It also includes any fields which are expected to
|
||||
be parsed into a built-in type but were not formatted appropriately. Finally,
|
||||
any fields that are expected to appear only once but are repeated are
|
||||
included in this dict.
|
||||
|
||||
"""
|
||||
raw: Dict[str, Union[str, List[str], Dict[str, str]]] = {}
|
||||
unparsed: Dict[str, List[str]] = {}
|
||||
|
||||
if isinstance(data, str):
|
||||
parsed = email.parser.Parser(policy=email.policy.compat32).parsestr(data)
|
||||
else:
|
||||
parsed = email.parser.BytesParser(policy=email.policy.compat32).parsebytes(data)
|
||||
|
||||
# We have to wrap parsed.keys() in a set, because in the case of multiple
|
||||
# values for a key (a list), the key will appear multiple times in the
|
||||
# list of keys, but we're avoiding that by using get_all().
|
||||
for name in frozenset(parsed.keys()):
|
||||
# Header names in RFC are case insensitive, so we'll normalize to all
|
||||
# lower case to make comparisons easier.
|
||||
name = name.lower()
|
||||
|
||||
# We use get_all() here, even for fields that aren't multiple use,
|
||||
# because otherwise someone could have e.g. two Name fields, and we
|
||||
# would just silently ignore it rather than doing something about it.
|
||||
headers = parsed.get_all(name)
|
||||
|
||||
# The way the email module works when parsing bytes is that it
|
||||
# unconditionally decodes the bytes as ascii using the surrogateescape
|
||||
# handler. When you pull that data back out (such as with get_all() ),
|
||||
# it looks to see if the str has any surrogate escapes, and if it does
|
||||
# it wraps it in a Header object instead of returning the string.
|
||||
#
|
||||
# As such, we'll look for those Header objects, and fix up the encoding.
|
||||
value = []
|
||||
# Flag if we have run into any issues processing the headers, thus
|
||||
# signalling that the data belongs in 'unparsed'.
|
||||
valid_encoding = True
|
||||
for h in headers:
|
||||
# It's unclear if this can return more types than just a Header or
|
||||
# a str, so we'll just assert here to make sure.
|
||||
assert isinstance(h, (email.header.Header, str))
|
||||
|
||||
# If it's a header object, we need to do our little dance to get
|
||||
# the real data out of it. In cases where there is invalid data
|
||||
# we're going to end up with mojibake, but there's no obvious, good
|
||||
# way around that without reimplementing parts of the Header object
|
||||
# ourselves.
|
||||
#
|
||||
# That should be fine since, if mojibacked happens, this key is
|
||||
# going into the unparsed dict anyways.
|
||||
if isinstance(h, email.header.Header):
|
||||
# The Header object stores it's data as chunks, and each chunk
|
||||
# can be independently encoded, so we'll need to check each
|
||||
# of them.
|
||||
chunks: List[Tuple[bytes, Optional[str]]] = []
|
||||
for bin, encoding in email.header.decode_header(h):
|
||||
try:
|
||||
bin.decode("utf8", "strict")
|
||||
except UnicodeDecodeError:
|
||||
# Enable mojibake.
|
||||
encoding = "latin1"
|
||||
valid_encoding = False
|
||||
else:
|
||||
encoding = "utf8"
|
||||
chunks.append((bin, encoding))
|
||||
|
||||
# Turn our chunks back into a Header object, then let that
|
||||
# Header object do the right thing to turn them into a
|
||||
# string for us.
|
||||
value.append(str(email.header.make_header(chunks)))
|
||||
# This is already a string, so just add it.
|
||||
else:
|
||||
value.append(h)
|
||||
|
||||
# We've processed all of our values to get them into a list of str,
|
||||
# but we may have mojibake data, in which case this is an unparsed
|
||||
# field.
|
||||
if not valid_encoding:
|
||||
unparsed[name] = value
|
||||
continue
|
||||
|
||||
raw_name = _EMAIL_TO_RAW_MAPPING.get(name)
|
||||
if raw_name is None:
|
||||
# This is a bit of a weird situation, we've encountered a key that
|
||||
# we don't know what it means, so we don't know whether it's meant
|
||||
# to be a list or not.
|
||||
#
|
||||
# Since we can't really tell one way or another, we'll just leave it
|
||||
# as a list, even though it may be a single item list, because that's
|
||||
# what makes the most sense for email headers.
|
||||
unparsed[name] = value
|
||||
continue
|
||||
|
||||
# If this is one of our string fields, then we'll check to see if our
|
||||
# value is a list of a single item. If it is then we'll assume that
|
||||
# it was emitted as a single string, and unwrap the str from inside
|
||||
# the list.
|
||||
#
|
||||
# If it's any other kind of data, then we haven't the faintest clue
|
||||
# what we should parse it as, and we have to just add it to our list
|
||||
# of unparsed stuff.
|
||||
if raw_name in _STRING_FIELDS and len(value) == 1:
|
||||
raw[raw_name] = value[0]
|
||||
# If this is one of our list of string fields, then we can just assign
|
||||
# the value, since email *only* has strings, and our get_all() call
|
||||
# above ensures that this is a list.
|
||||
elif raw_name in _LIST_STRING_FIELDS:
|
||||
raw[raw_name] = value
|
||||
# Special Case: Keywords
|
||||
# The keywords field is implemented in the metadata spec as a str,
|
||||
# but it conceptually is a list of strings, and is serialized using
|
||||
# ", ".join(keywords), so we'll do some light data massaging to turn
|
||||
# this into what it logically is.
|
||||
elif raw_name == "keywords" and len(value) == 1:
|
||||
raw[raw_name] = _parse_keywords(value[0])
|
||||
# Special Case: Project-URL
|
||||
# The project urls is implemented in the metadata spec as a list of
|
||||
# specially-formatted strings that represent a key and a value, which
|
||||
# is fundamentally a mapping, however the email format doesn't support
|
||||
# mappings in a sane way, so it was crammed into a list of strings
|
||||
# instead.
|
||||
#
|
||||
# We will do a little light data massaging to turn this into a map as
|
||||
# it logically should be.
|
||||
elif raw_name == "project_urls":
|
||||
try:
|
||||
raw[raw_name] = _parse_project_urls(value)
|
||||
except KeyError:
|
||||
unparsed[name] = value
|
||||
# Nothing that we've done has managed to parse this, so it'll just
|
||||
# throw it in our unparseable data and move on.
|
||||
else:
|
||||
unparsed[name] = value
|
||||
|
||||
# We need to support getting the Description from the message payload in
|
||||
# addition to getting it from the the headers. This does mean, though, there
|
||||
# is the possibility of it being set both ways, in which case we put both
|
||||
# in 'unparsed' since we don't know which is right.
|
||||
try:
|
||||
payload = _get_payload(parsed, data)
|
||||
except ValueError:
|
||||
unparsed.setdefault("description", []).append(
|
||||
parsed.get_payload(decode=isinstance(data, bytes))
|
||||
)
|
||||
else:
|
||||
if payload:
|
||||
# Check to see if we've already got a description, if so then both
|
||||
# it, and this body move to unparseable.
|
||||
if "description" in raw:
|
||||
description_header = cast(str, raw.pop("description"))
|
||||
unparsed.setdefault("description", []).extend(
|
||||
[description_header, payload]
|
||||
)
|
||||
elif "description" in unparsed:
|
||||
unparsed["description"].append(payload)
|
||||
else:
|
||||
raw["description"] = payload
|
||||
|
||||
# We need to cast our `raw` to a metadata, because a TypedDict only support
|
||||
# literal key names, but we're computing our key names on purpose, but the
|
||||
# way this function is implemented, our `TypedDict` can only have valid key
|
||||
# names.
|
||||
return cast(RawMetadata, raw), unparsed
|
@ -2,26 +2,13 @@
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
|
||||
import re
|
||||
import string
|
||||
import urllib.parse
|
||||
from typing import List, Optional as TOptional, Set
|
||||
from typing import Any, List, Optional, Set
|
||||
|
||||
from pyparsing import ( # noqa
|
||||
Combine,
|
||||
Literal as L,
|
||||
Optional,
|
||||
ParseException,
|
||||
Regex,
|
||||
Word,
|
||||
ZeroOrMore,
|
||||
originalTextFor,
|
||||
stringEnd,
|
||||
stringStart,
|
||||
)
|
||||
|
||||
from .markers import MARKER_EXPR, Marker
|
||||
from .specifiers import LegacySpecifier, Specifier, SpecifierSet
|
||||
from ._parser import parse_requirement as _parse_requirement
|
||||
from ._tokenizer import ParserSyntaxError
|
||||
from .markers import Marker, _normalize_extra_values
|
||||
from .specifiers import SpecifierSet
|
||||
|
||||
|
||||
class InvalidRequirement(ValueError):
|
||||
@ -30,60 +17,6 @@ class InvalidRequirement(ValueError):
|
||||
"""
|
||||
|
||||
|
||||
ALPHANUM = Word(string.ascii_letters + string.digits)
|
||||
|
||||
LBRACKET = L("[").suppress()
|
||||
RBRACKET = L("]").suppress()
|
||||
LPAREN = L("(").suppress()
|
||||
RPAREN = L(")").suppress()
|
||||
COMMA = L(",").suppress()
|
||||
SEMICOLON = L(";").suppress()
|
||||
AT = L("@").suppress()
|
||||
|
||||
PUNCTUATION = Word("-_.")
|
||||
IDENTIFIER_END = ALPHANUM | (ZeroOrMore(PUNCTUATION) + ALPHANUM)
|
||||
IDENTIFIER = Combine(ALPHANUM + ZeroOrMore(IDENTIFIER_END))
|
||||
|
||||
NAME = IDENTIFIER("name")
|
||||
EXTRA = IDENTIFIER
|
||||
|
||||
URI = Regex(r"[^ ]+")("url")
|
||||
URL = AT + URI
|
||||
|
||||
EXTRAS_LIST = EXTRA + ZeroOrMore(COMMA + EXTRA)
|
||||
EXTRAS = (LBRACKET + Optional(EXTRAS_LIST) + RBRACKET)("extras")
|
||||
|
||||
VERSION_PEP440 = Regex(Specifier._regex_str, re.VERBOSE | re.IGNORECASE)
|
||||
VERSION_LEGACY = Regex(LegacySpecifier._regex_str, re.VERBOSE | re.IGNORECASE)
|
||||
|
||||
VERSION_ONE = VERSION_PEP440 ^ VERSION_LEGACY
|
||||
VERSION_MANY = Combine(
|
||||
VERSION_ONE + ZeroOrMore(COMMA + VERSION_ONE), joinString=",", adjacent=False
|
||||
)("_raw_spec")
|
||||
_VERSION_SPEC = Optional((LPAREN + VERSION_MANY + RPAREN) | VERSION_MANY)
|
||||
_VERSION_SPEC.setParseAction(lambda s, l, t: t._raw_spec or "")
|
||||
|
||||
VERSION_SPEC = originalTextFor(_VERSION_SPEC)("specifier")
|
||||
VERSION_SPEC.setParseAction(lambda s, l, t: t[1])
|
||||
|
||||
MARKER_EXPR = originalTextFor(MARKER_EXPR())("marker")
|
||||
MARKER_EXPR.setParseAction(
|
||||
lambda s, l, t: Marker(s[t._original_start : t._original_end])
|
||||
)
|
||||
MARKER_SEPARATOR = SEMICOLON
|
||||
MARKER = MARKER_SEPARATOR + MARKER_EXPR
|
||||
|
||||
VERSION_AND_MARKER = VERSION_SPEC + Optional(MARKER)
|
||||
URL_AND_MARKER = URL + Optional(MARKER)
|
||||
|
||||
NAMED_REQUIREMENT = NAME + Optional(EXTRAS) + (URL_AND_MARKER | VERSION_AND_MARKER)
|
||||
|
||||
REQUIREMENT = stringStart + NAMED_REQUIREMENT + stringEnd
|
||||
# pyparsing isn't thread safe during initialization, so we do it eagerly, see
|
||||
# issue #104
|
||||
REQUIREMENT.parseString("x[]")
|
||||
|
||||
|
||||
class Requirement:
|
||||
"""Parse a requirement.
|
||||
|
||||
@ -99,28 +32,29 @@ class Requirement:
|
||||
|
||||
def __init__(self, requirement_string: str) -> None:
|
||||
try:
|
||||
req = REQUIREMENT.parseString(requirement_string)
|
||||
except ParseException as e:
|
||||
raise InvalidRequirement(
|
||||
f'Parse error at "{ requirement_string[e.loc : e.loc + 8]!r}": {e.msg}'
|
||||
)
|
||||
parsed = _parse_requirement(requirement_string)
|
||||
except ParserSyntaxError as e:
|
||||
raise InvalidRequirement(str(e)) from e
|
||||
|
||||
self.name: str = req.name
|
||||
if req.url:
|
||||
parsed_url = urllib.parse.urlparse(req.url)
|
||||
self.name: str = parsed.name
|
||||
if parsed.url:
|
||||
parsed_url = urllib.parse.urlparse(parsed.url)
|
||||
if parsed_url.scheme == "file":
|
||||
if urllib.parse.urlunparse(parsed_url) != req.url:
|
||||
if urllib.parse.urlunparse(parsed_url) != parsed.url:
|
||||
raise InvalidRequirement("Invalid URL given")
|
||||
elif not (parsed_url.scheme and parsed_url.netloc) or (
|
||||
not parsed_url.scheme and not parsed_url.netloc
|
||||
):
|
||||
raise InvalidRequirement(f"Invalid URL: {req.url}")
|
||||
self.url: TOptional[str] = req.url
|
||||
raise InvalidRequirement(f"Invalid URL: {parsed.url}")
|
||||
self.url: Optional[str] = parsed.url
|
||||
else:
|
||||
self.url = None
|
||||
self.extras: Set[str] = set(req.extras.asList() if req.extras else [])
|
||||
self.specifier: SpecifierSet = SpecifierSet(req.specifier)
|
||||
self.marker: TOptional[Marker] = req.marker if req.marker else None
|
||||
self.extras: Set[str] = set(parsed.extras if parsed.extras else [])
|
||||
self.specifier: SpecifierSet = SpecifierSet(parsed.specifier)
|
||||
self.marker: Optional[Marker] = None
|
||||
if parsed.marker is not None:
|
||||
self.marker = Marker.__new__(Marker)
|
||||
self.marker._markers = _normalize_extra_values(parsed.marker)
|
||||
|
||||
def __str__(self) -> str:
|
||||
parts: List[str] = [self.name]
|
||||
@ -144,3 +78,18 @@ class Requirement:
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Requirement('{self}')>"
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.__class__.__name__, str(self)))
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, Requirement):
|
||||
return NotImplemented
|
||||
|
||||
return (
|
||||
self.name == other.name
|
||||
and self.extras == other.extras
|
||||
and self.specifier == other.specifier
|
||||
and self.url == other.url
|
||||
and self.marker == other.marker
|
||||
)
|
||||
|
914
third_party/python/packaging/packaging/specifiers.py
vendored
914
third_party/python/packaging/packaging/specifiers.py
vendored
File diff suppressed because it is too large
Load Diff
79
third_party/python/packaging/packaging/tags.py
vendored
79
third_party/python/packaging/packaging/tags.py
vendored
@ -4,6 +4,7 @@
|
||||
|
||||
import logging
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
from importlib.machinery import EXTENSION_SUFFIXES
|
||||
@ -36,7 +37,7 @@ INTERPRETER_SHORT_NAMES: Dict[str, str] = {
|
||||
}
|
||||
|
||||
|
||||
_32_BIT_INTERPRETER = sys.maxsize <= 2 ** 32
|
||||
_32_BIT_INTERPRETER = sys.maxsize <= 2**32
|
||||
|
||||
|
||||
class Tag:
|
||||
@ -110,7 +111,7 @@ def parse_tag(tag: str) -> FrozenSet[Tag]:
|
||||
|
||||
|
||||
def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]:
|
||||
value = sysconfig.get_config_var(name)
|
||||
value: Union[int, str, None] = sysconfig.get_config_var(name)
|
||||
if value is None and warn:
|
||||
logger.debug(
|
||||
"Config variable '%s' is unset, Python ABI tag may be incorrect", name
|
||||
@ -119,7 +120,7 @@ def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]:
|
||||
|
||||
|
||||
def _normalize_string(string: str) -> str:
|
||||
return string.replace(".", "_").replace("-", "_")
|
||||
return string.replace(".", "_").replace("-", "_").replace(" ", "_")
|
||||
|
||||
|
||||
def _abi3_applies(python_version: PythonVersion) -> bool:
|
||||
@ -224,10 +225,45 @@ def cpython_tags(
|
||||
yield Tag(interpreter, "abi3", platform_)
|
||||
|
||||
|
||||
def _generic_abi() -> Iterator[str]:
|
||||
abi = sysconfig.get_config_var("SOABI")
|
||||
if abi:
|
||||
yield _normalize_string(abi)
|
||||
def _generic_abi() -> List[str]:
|
||||
"""
|
||||
Return the ABI tag based on EXT_SUFFIX.
|
||||
"""
|
||||
# The following are examples of `EXT_SUFFIX`.
|
||||
# We want to keep the parts which are related to the ABI and remove the
|
||||
# parts which are related to the platform:
|
||||
# - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310
|
||||
# - mac: '.cpython-310-darwin.so' => cp310
|
||||
# - win: '.cp310-win_amd64.pyd' => cp310
|
||||
# - win: '.pyd' => cp37 (uses _cpython_abis())
|
||||
# - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73
|
||||
# - graalpy: '.graalpy-38-native-x86_64-darwin.dylib'
|
||||
# => graalpy_38_native
|
||||
|
||||
ext_suffix = _get_config_var("EXT_SUFFIX", warn=True)
|
||||
if not isinstance(ext_suffix, str) or ext_suffix[0] != ".":
|
||||
raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')")
|
||||
parts = ext_suffix.split(".")
|
||||
if len(parts) < 3:
|
||||
# CPython3.7 and earlier uses ".pyd" on Windows.
|
||||
return _cpython_abis(sys.version_info[:2])
|
||||
soabi = parts[1]
|
||||
if soabi.startswith("cpython"):
|
||||
# non-windows
|
||||
abi = "cp" + soabi.split("-")[1]
|
||||
elif soabi.startswith("cp"):
|
||||
# windows
|
||||
abi = soabi.split("-")[0]
|
||||
elif soabi.startswith("pypy"):
|
||||
abi = "-".join(soabi.split("-")[:2])
|
||||
elif soabi.startswith("graalpy"):
|
||||
abi = "-".join(soabi.split("-")[:3])
|
||||
elif soabi:
|
||||
# pyston, ironpython, others?
|
||||
abi = soabi
|
||||
else:
|
||||
return []
|
||||
return [_normalize_string(abi)]
|
||||
|
||||
|
||||
def generic_tags(
|
||||
@ -251,8 +287,9 @@ def generic_tags(
|
||||
interpreter = "".join([interp_name, interp_version])
|
||||
if abis is None:
|
||||
abis = _generic_abi()
|
||||
else:
|
||||
abis = list(abis)
|
||||
platforms = list(platforms or platform_tags())
|
||||
abis = list(abis)
|
||||
if "none" not in abis:
|
||||
abis.append("none")
|
||||
for abi in abis:
|
||||
@ -356,6 +393,22 @@ def mac_platforms(
|
||||
version_str, _, cpu_arch = platform.mac_ver()
|
||||
if version is None:
|
||||
version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
|
||||
if version == (10, 16):
|
||||
# When built against an older macOS SDK, Python will report macOS 10.16
|
||||
# instead of the real version.
|
||||
version_str = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
"-sS",
|
||||
"-c",
|
||||
"import platform; print(platform.mac_ver()[0])",
|
||||
],
|
||||
check=True,
|
||||
env={"SYSTEM_VERSION_COMPAT": "0"},
|
||||
stdout=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
).stdout
|
||||
version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2])))
|
||||
else:
|
||||
version = version
|
||||
if arch is None:
|
||||
@ -446,6 +499,9 @@ def platform_tags() -> Iterator[str]:
|
||||
def interpreter_name() -> str:
|
||||
"""
|
||||
Returns the name of the running interpreter.
|
||||
|
||||
Some implementations have a reserved, two-letter abbreviation which will
|
||||
be returned when appropriate.
|
||||
"""
|
||||
name = sys.implementation.name
|
||||
return INTERPRETER_SHORT_NAMES.get(name) or name
|
||||
@ -482,6 +538,9 @@ def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
|
||||
yield from generic_tags()
|
||||
|
||||
if interp_name == "pp":
|
||||
yield from compatible_tags(interpreter="pp3")
|
||||
interp = "pp3"
|
||||
elif interp_name == "cp":
|
||||
interp = "cp" + interpreter_version(warn=warn)
|
||||
else:
|
||||
yield from compatible_tags()
|
||||
interp = None
|
||||
yield from compatible_tags(interpreter=interp)
|
||||
|
11
third_party/python/packaging/packaging/utils.py
vendored
11
third_party/python/packaging/packaging/utils.py
vendored
@ -35,7 +35,9 @@ def canonicalize_name(name: str) -> NormalizedName:
|
||||
return cast(NormalizedName, value)
|
||||
|
||||
|
||||
def canonicalize_version(version: Union[Version, str]) -> str:
|
||||
def canonicalize_version(
|
||||
version: Union[Version, str], *, strip_trailing_zero: bool = True
|
||||
) -> str:
|
||||
"""
|
||||
This is very similar to Version.__str__, but has one subtle difference
|
||||
with the way it handles the release segment.
|
||||
@ -56,8 +58,11 @@ def canonicalize_version(version: Union[Version, str]) -> str:
|
||||
parts.append(f"{parsed.epoch}!")
|
||||
|
||||
# Release segment
|
||||
# NB: This strips trailing '.0's to normalize
|
||||
parts.append(re.sub(r"(\.0)+$", "", ".".join(str(x) for x in parsed.release)))
|
||||
release_segment = ".".join(str(x) for x in parsed.release)
|
||||
if strip_trailing_zero:
|
||||
# NB: This strips trailing '.0's to normalize
|
||||
release_segment = re.sub(r"(\.0)+$", "", release_segment)
|
||||
parts.append(release_segment)
|
||||
|
||||
# Pre-release
|
||||
if parsed.pre is not None:
|
||||
|
334
third_party/python/packaging/packaging/version.py
vendored
334
third_party/python/packaging/packaging/version.py
vendored
@ -1,16 +1,20 @@
|
||||
# This file is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||
# for complete details.
|
||||
"""
|
||||
.. testsetup::
|
||||
|
||||
from packaging.version import parse, Version
|
||||
"""
|
||||
|
||||
import collections
|
||||
import itertools
|
||||
import re
|
||||
import warnings
|
||||
from typing import Callable, Iterator, List, Optional, SupportsInt, Tuple, Union
|
||||
from typing import Any, Callable, Optional, SupportsInt, Tuple, Union
|
||||
|
||||
from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType
|
||||
|
||||
__all__ = ["parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN"]
|
||||
__all__ = ["VERSION_PATTERN", "parse", "Version", "InvalidVersion"]
|
||||
|
||||
InfiniteTypes = Union[InfinityType, NegativeInfinityType]
|
||||
PrePostDevType = Union[InfiniteTypes, Tuple[str, int]]
|
||||
@ -29,36 +33,37 @@ LocalType = Union[
|
||||
CmpKey = Tuple[
|
||||
int, Tuple[int, ...], PrePostDevType, PrePostDevType, PrePostDevType, LocalType
|
||||
]
|
||||
LegacyCmpKey = Tuple[int, Tuple[str, ...]]
|
||||
VersionComparisonMethod = Callable[
|
||||
[Union[CmpKey, LegacyCmpKey], Union[CmpKey, LegacyCmpKey]], bool
|
||||
]
|
||||
VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
|
||||
|
||||
_Version = collections.namedtuple(
|
||||
"_Version", ["epoch", "release", "dev", "pre", "post", "local"]
|
||||
)
|
||||
|
||||
|
||||
def parse(version: str) -> Union["LegacyVersion", "Version"]:
|
||||
def parse(version: str) -> "Version":
|
||||
"""Parse the given version string.
|
||||
|
||||
>>> parse('1.0.dev1')
|
||||
<Version('1.0.dev1')>
|
||||
|
||||
:param version: The version string to parse.
|
||||
:raises InvalidVersion: When the version string is not a valid version.
|
||||
"""
|
||||
Parse the given version string and return either a :class:`Version` object
|
||||
or a :class:`LegacyVersion` object depending on if the given version is
|
||||
a valid PEP 440 version or a legacy version.
|
||||
"""
|
||||
try:
|
||||
return Version(version)
|
||||
except InvalidVersion:
|
||||
return LegacyVersion(version)
|
||||
return Version(version)
|
||||
|
||||
|
||||
class InvalidVersion(ValueError):
|
||||
"""
|
||||
An invalid version was found, users should refer to PEP 440.
|
||||
"""Raised when a version string is not a valid version.
|
||||
|
||||
>>> Version("invalid")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
packaging.version.InvalidVersion: Invalid version: 'invalid'
|
||||
"""
|
||||
|
||||
|
||||
class _BaseVersion:
|
||||
_key: Union[CmpKey, LegacyCmpKey]
|
||||
_key: Tuple[Any, ...]
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._key)
|
||||
@ -103,126 +108,9 @@ class _BaseVersion:
|
||||
return self._key != other._key
|
||||
|
||||
|
||||
class LegacyVersion(_BaseVersion):
|
||||
def __init__(self, version: str) -> None:
|
||||
self._version = str(version)
|
||||
self._key = _legacy_cmpkey(self._version)
|
||||
|
||||
warnings.warn(
|
||||
"Creating a LegacyVersion has been deprecated and will be "
|
||||
"removed in the next major release",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self._version
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<LegacyVersion('{self}')>"
|
||||
|
||||
@property
|
||||
def public(self) -> str:
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def base_version(self) -> str:
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def epoch(self) -> int:
|
||||
return -1
|
||||
|
||||
@property
|
||||
def release(self) -> None:
|
||||
return None
|
||||
|
||||
@property
|
||||
def pre(self) -> None:
|
||||
return None
|
||||
|
||||
@property
|
||||
def post(self) -> None:
|
||||
return None
|
||||
|
||||
@property
|
||||
def dev(self) -> None:
|
||||
return None
|
||||
|
||||
@property
|
||||
def local(self) -> None:
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_prerelease(self) -> bool:
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_postrelease(self) -> bool:
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_devrelease(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE)
|
||||
|
||||
_legacy_version_replacement_map = {
|
||||
"pre": "c",
|
||||
"preview": "c",
|
||||
"-": "final-",
|
||||
"rc": "c",
|
||||
"dev": "@",
|
||||
}
|
||||
|
||||
|
||||
def _parse_version_parts(s: str) -> Iterator[str]:
|
||||
for part in _legacy_version_component_re.split(s):
|
||||
part = _legacy_version_replacement_map.get(part, part)
|
||||
|
||||
if not part or part == ".":
|
||||
continue
|
||||
|
||||
if part[:1] in "0123456789":
|
||||
# pad for numeric comparison
|
||||
yield part.zfill(8)
|
||||
else:
|
||||
yield "*" + part
|
||||
|
||||
# ensure that alpha/beta/candidate are before final
|
||||
yield "*final"
|
||||
|
||||
|
||||
def _legacy_cmpkey(version: str) -> LegacyCmpKey:
|
||||
|
||||
# We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch
|
||||
# greater than or equal to 0. This will effectively put the LegacyVersion,
|
||||
# which uses the defacto standard originally implemented by setuptools,
|
||||
# as before all PEP 440 versions.
|
||||
epoch = -1
|
||||
|
||||
# This scheme is taken from pkg_resources.parse_version setuptools prior to
|
||||
# it's adoption of the packaging library.
|
||||
parts: List[str] = []
|
||||
for part in _parse_version_parts(version.lower()):
|
||||
if part.startswith("*"):
|
||||
# remove "-" before a prerelease tag
|
||||
if part < "*final":
|
||||
while parts and parts[-1] == "*final-":
|
||||
parts.pop()
|
||||
|
||||
# remove trailing zeros from each series of numeric parts
|
||||
while parts and parts[-1] == "00000000":
|
||||
parts.pop()
|
||||
|
||||
parts.append(part)
|
||||
|
||||
return epoch, tuple(parts)
|
||||
|
||||
|
||||
# Deliberately not anchored to the start and end of the string, to make it
|
||||
# easier for 3rd party code to reuse
|
||||
VERSION_PATTERN = r"""
|
||||
_VERSION_PATTERN = r"""
|
||||
v?
|
||||
(?:
|
||||
(?:(?P<epoch>[0-9]+)!)? # epoch
|
||||
@ -253,12 +141,56 @@ VERSION_PATTERN = r"""
|
||||
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
|
||||
"""
|
||||
|
||||
VERSION_PATTERN = _VERSION_PATTERN
|
||||
"""
|
||||
A string containing the regular expression used to match a valid version.
|
||||
|
||||
The pattern is not anchored at either end, and is intended for embedding in larger
|
||||
expressions (for example, matching a version number as part of a file name). The
|
||||
regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
|
||||
flags set.
|
||||
|
||||
:meta hide-value:
|
||||
"""
|
||||
|
||||
|
||||
class Version(_BaseVersion):
|
||||
"""This class abstracts handling of a project's versions.
|
||||
|
||||
A :class:`Version` instance is comparison aware and can be compared and
|
||||
sorted using the standard Python interfaces.
|
||||
|
||||
>>> v1 = Version("1.0a5")
|
||||
>>> v2 = Version("1.0")
|
||||
>>> v1
|
||||
<Version('1.0a5')>
|
||||
>>> v2
|
||||
<Version('1.0')>
|
||||
>>> v1 < v2
|
||||
True
|
||||
>>> v1 == v2
|
||||
False
|
||||
>>> v1 > v2
|
||||
False
|
||||
>>> v1 >= v2
|
||||
False
|
||||
>>> v1 <= v2
|
||||
True
|
||||
"""
|
||||
|
||||
_regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
|
||||
_key: CmpKey
|
||||
|
||||
def __init__(self, version: str) -> None:
|
||||
"""Initialize a Version object.
|
||||
|
||||
:param version:
|
||||
The string representation of a version which will be parsed and normalized
|
||||
before use.
|
||||
:raises InvalidVersion:
|
||||
If the ``version`` does not conform to PEP 440 in any way then this
|
||||
exception will be raised.
|
||||
"""
|
||||
|
||||
# Validate the version and parse it into pieces
|
||||
match = self._regex.search(version)
|
||||
@ -288,9 +220,19 @@ class Version(_BaseVersion):
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""A representation of the Version that shows all internal state.
|
||||
|
||||
>>> Version('1.0.0')
|
||||
<Version('1.0.0')>
|
||||
"""
|
||||
return f"<Version('{self}')>"
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""A string representation of the version that can be rounded-tripped.
|
||||
|
||||
>>> str(Version("1.0a5"))
|
||||
'1.0a5'
|
||||
"""
|
||||
parts = []
|
||||
|
||||
# Epoch
|
||||
@ -320,29 +262,80 @@ class Version(_BaseVersion):
|
||||
|
||||
@property
|
||||
def epoch(self) -> int:
|
||||
"""The epoch of the version.
|
||||
|
||||
>>> Version("2.0.0").epoch
|
||||
0
|
||||
>>> Version("1!2.0.0").epoch
|
||||
1
|
||||
"""
|
||||
_epoch: int = self._version.epoch
|
||||
return _epoch
|
||||
|
||||
@property
|
||||
def release(self) -> Tuple[int, ...]:
|
||||
"""The components of the "release" segment of the version.
|
||||
|
||||
>>> Version("1.2.3").release
|
||||
(1, 2, 3)
|
||||
>>> Version("2.0.0").release
|
||||
(2, 0, 0)
|
||||
>>> Version("1!2.0.0.post0").release
|
||||
(2, 0, 0)
|
||||
|
||||
Includes trailing zeroes but not the epoch or any pre-release / development /
|
||||
post-release suffixes.
|
||||
"""
|
||||
_release: Tuple[int, ...] = self._version.release
|
||||
return _release
|
||||
|
||||
@property
|
||||
def pre(self) -> Optional[Tuple[str, int]]:
|
||||
"""The pre-release segment of the version.
|
||||
|
||||
>>> print(Version("1.2.3").pre)
|
||||
None
|
||||
>>> Version("1.2.3a1").pre
|
||||
('a', 1)
|
||||
>>> Version("1.2.3b1").pre
|
||||
('b', 1)
|
||||
>>> Version("1.2.3rc1").pre
|
||||
('rc', 1)
|
||||
"""
|
||||
_pre: Optional[Tuple[str, int]] = self._version.pre
|
||||
return _pre
|
||||
|
||||
@property
|
||||
def post(self) -> Optional[int]:
|
||||
"""The post-release number of the version.
|
||||
|
||||
>>> print(Version("1.2.3").post)
|
||||
None
|
||||
>>> Version("1.2.3.post1").post
|
||||
1
|
||||
"""
|
||||
return self._version.post[1] if self._version.post else None
|
||||
|
||||
@property
|
||||
def dev(self) -> Optional[int]:
|
||||
"""The development number of the version.
|
||||
|
||||
>>> print(Version("1.2.3").dev)
|
||||
None
|
||||
>>> Version("1.2.3.dev1").dev
|
||||
1
|
||||
"""
|
||||
return self._version.dev[1] if self._version.dev else None
|
||||
|
||||
@property
|
||||
def local(self) -> Optional[str]:
|
||||
"""The local version segment of the version.
|
||||
|
||||
>>> print(Version("1.2.3").local)
|
||||
None
|
||||
>>> Version("1.2.3+abc").local
|
||||
'abc'
|
||||
"""
|
||||
if self._version.local:
|
||||
return ".".join(str(x) for x in self._version.local)
|
||||
else:
|
||||
@ -350,10 +343,31 @@ class Version(_BaseVersion):
|
||||
|
||||
@property
|
||||
def public(self) -> str:
|
||||
"""The public portion of the version.
|
||||
|
||||
>>> Version("1.2.3").public
|
||||
'1.2.3'
|
||||
>>> Version("1.2.3+abc").public
|
||||
'1.2.3'
|
||||
>>> Version("1.2.3+abc.dev1").public
|
||||
'1.2.3'
|
||||
"""
|
||||
return str(self).split("+", 1)[0]
|
||||
|
||||
@property
|
||||
def base_version(self) -> str:
|
||||
"""The "base version" of the version.
|
||||
|
||||
>>> Version("1.2.3").base_version
|
||||
'1.2.3'
|
||||
>>> Version("1.2.3+abc").base_version
|
||||
'1.2.3'
|
||||
>>> Version("1!1.2.3+abc.dev1").base_version
|
||||
'1!1.2.3'
|
||||
|
||||
The "base version" is the public version of the project without any pre or post
|
||||
release markers.
|
||||
"""
|
||||
parts = []
|
||||
|
||||
# Epoch
|
||||
@ -367,26 +381,72 @@ class Version(_BaseVersion):
|
||||
|
||||
@property
|
||||
def is_prerelease(self) -> bool:
|
||||
"""Whether this version is a pre-release.
|
||||
|
||||
>>> Version("1.2.3").is_prerelease
|
||||
False
|
||||
>>> Version("1.2.3a1").is_prerelease
|
||||
True
|
||||
>>> Version("1.2.3b1").is_prerelease
|
||||
True
|
||||
>>> Version("1.2.3rc1").is_prerelease
|
||||
True
|
||||
>>> Version("1.2.3dev1").is_prerelease
|
||||
True
|
||||
"""
|
||||
return self.dev is not None or self.pre is not None
|
||||
|
||||
@property
|
||||
def is_postrelease(self) -> bool:
|
||||
"""Whether this version is a post-release.
|
||||
|
||||
>>> Version("1.2.3").is_postrelease
|
||||
False
|
||||
>>> Version("1.2.3.post1").is_postrelease
|
||||
True
|
||||
"""
|
||||
return self.post is not None
|
||||
|
||||
@property
|
||||
def is_devrelease(self) -> bool:
|
||||
"""Whether this version is a development release.
|
||||
|
||||
>>> Version("1.2.3").is_devrelease
|
||||
False
|
||||
>>> Version("1.2.3.dev1").is_devrelease
|
||||
True
|
||||
"""
|
||||
return self.dev is not None
|
||||
|
||||
@property
|
||||
def major(self) -> int:
|
||||
"""The first item of :attr:`release` or ``0`` if unavailable.
|
||||
|
||||
>>> Version("1.2.3").major
|
||||
1
|
||||
"""
|
||||
return self.release[0] if len(self.release) >= 1 else 0
|
||||
|
||||
@property
|
||||
def minor(self) -> int:
|
||||
"""The second item of :attr:`release` or ``0`` if unavailable.
|
||||
|
||||
>>> Version("1.2.3").minor
|
||||
2
|
||||
>>> Version("1").minor
|
||||
0
|
||||
"""
|
||||
return self.release[1] if len(self.release) >= 2 else 0
|
||||
|
||||
@property
|
||||
def micro(self) -> int:
|
||||
"""The third item of :attr:`release` or ``0`` if unavailable.
|
||||
|
||||
>>> Version("1.2.3").micro
|
||||
3
|
||||
>>> Version("1").micro
|
||||
0
|
||||
"""
|
||||
return self.release[2] if len(self.release) >= 3 else 0
|
||||
|
||||
|
||||
|
37
third_party/python/poetry.lock
generated
vendored
37
third_party/python/poetry.lock
generated
vendored
@ -205,16 +205,20 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "7.1.2"
|
||||
version = "8.1.6"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
|
||||
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
|
||||
{file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"},
|
||||
{file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.5"
|
||||
@ -745,19 +749,16 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "21.3"
|
||||
version = "23.1"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||
{file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
|
||||
{file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.9.0"
|
||||
@ -863,18 +864,6 @@ files = [
|
||||
{file = "pylru-1.0.9.tar.gz", hash = "sha256:71376192671f0ad1690b2a7427d39a29b1df994c8469a9b46b03ed7e28c0172c"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "2.4.7"
|
||||
description = "Python parsing module"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
files = [
|
||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyrsistent"
|
||||
version = "0.16.0"
|
||||
@ -1415,4 +1404,4 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "195fb2c45ba5de3c5c98f5d2fa44b5ce64fd3dd45c324d528477637245413d0f"
|
||||
content-hash = "8623b01fb798d70285240fd5709396279a2229b3816ccb9db99dcb4e35c8c885"
|
||||
|
@ -1,18 +0,0 @@
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@ -1,104 +0,0 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: pyparsing
|
||||
Version: 2.4.7
|
||||
Summary: Python parsing module
|
||||
Home-page: https://github.com/pyparsing/pyparsing/
|
||||
Author: Paul McGuire
|
||||
Author-email: ptmcg@users.sourceforge.net
|
||||
License: MIT License
|
||||
Download-URL: https://pypi.org/project/pyparsing/
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Intended Audience :: Information Technology
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.*
|
||||
|
||||
PyParsing -- A Python Parsing Module
|
||||
====================================
|
||||
|
||||
|Build Status|
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
The pyparsing module is an alternative approach to creating and
|
||||
executing simple grammars, vs. the traditional lex/yacc approach, or the
|
||||
use of regular expressions. The pyparsing module provides a library of
|
||||
classes that client code uses to construct the grammar directly in
|
||||
Python code.
|
||||
|
||||
*[Since first writing this description of pyparsing in late 2003, this
|
||||
technique for developing parsers has become more widespread, under the
|
||||
name Parsing Expression Grammars - PEGs. See more information on PEGs at*
|
||||
https://en.wikipedia.org/wiki/Parsing_expression_grammar *.]*
|
||||
|
||||
Here is a program to parse ``"Hello, World!"`` (or any greeting of the form
|
||||
``"salutation, addressee!"``):
|
||||
|
||||
.. code:: python
|
||||
|
||||
from pyparsing import Word, alphas
|
||||
greet = Word(alphas) + "," + Word(alphas) + "!"
|
||||
hello = "Hello, World!"
|
||||
print(hello, "->", greet.parseString(hello))
|
||||
|
||||
The program outputs the following::
|
||||
|
||||
Hello, World! -> ['Hello', ',', 'World', '!']
|
||||
|
||||
The Python representation of the grammar is quite readable, owing to the
|
||||
self-explanatory class names, and the use of '+', '|' and '^' operator
|
||||
definitions.
|
||||
|
||||
The parsed results returned from ``parseString()`` can be accessed as a
|
||||
nested list, a dictionary, or an object with named attributes.
|
||||
|
||||
The pyparsing module handles some of the problems that are typically
|
||||
vexing when writing text parsers:
|
||||
|
||||
- extra or missing whitespace (the above program will also handle ``"Hello,World!"``, ``"Hello , World !"``, etc.)
|
||||
- quoted strings
|
||||
- embedded comments
|
||||
|
||||
The examples directory includes a simple SQL parser, simple CORBA IDL
|
||||
parser, a config file parser, a chemical formula parser, and a four-
|
||||
function algebraic notation parser, among many others.
|
||||
|
||||
Documentation
|
||||
=============
|
||||
|
||||
There are many examples in the online docstrings of the classes
|
||||
and methods in pyparsing. You can find them compiled into online docs
|
||||
at https://pyparsing-docs.readthedocs.io/en/latest/. Additional
|
||||
documentation resources and project info are listed in the online
|
||||
GitHub wiki, at https://github.com/pyparsing/pyparsing/wiki. An
|
||||
entire directory of examples is at
|
||||
https://github.com/pyparsing/pyparsing/tree/master/examples.
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
MIT License. See header of pyparsing.py
|
||||
|
||||
History
|
||||
=======
|
||||
|
||||
See CHANGES file.
|
||||
|
||||
.. |Build Status| image:: https://travis-ci.org/pyparsing/pyparsing.svg?branch=master
|
||||
:target: https://travis-ci.org/pyparsing/pyparsing
|
||||
|
||||
|
@ -1,6 +0,0 @@
|
||||
pyparsing.py,sha256=oxX_ZOz8t-eros-UWY7nJgcdUgD-rQ53Ck0qp7_v3Ig,273365
|
||||
pyparsing-2.4.7.dist-info/LICENSE,sha256=ENUSChaAWAT_2otojCIL-06POXQbVzIGBNRVowngGXI,1023
|
||||
pyparsing-2.4.7.dist-info/METADATA,sha256=Ry40soZZiZrAkSMQT_KU1_1REe6FKa5UWzbT6YA8Mxs,3636
|
||||
pyparsing-2.4.7.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110
|
||||
pyparsing-2.4.7.dist-info/top_level.txt,sha256=eUOjGzJVhlQ3WS2rFAy2mN3LX_7FKTM5GSJ04jfnLmU,10
|
||||
pyparsing-2.4.7.dist-info/RECORD,,
|
@ -1 +0,0 @@
|
||||
pyparsing
|
7107
third_party/python/pyparsing/pyparsing.py
vendored
7107
third_party/python/pyparsing/pyparsing.py
vendored
File diff suppressed because it is too large
Load Diff
3
third_party/python/requirements.in
vendored
3
third_party/python/requirements.in
vendored
@ -5,6 +5,7 @@ attrs==23.1.0
|
||||
blessed==1.19.1
|
||||
cbor2==4.0.1
|
||||
certifi==2022.12.7
|
||||
click==8.1.6
|
||||
colorama==0.4.5
|
||||
compare-locales==9.0.1
|
||||
cookies==2.2.1
|
||||
@ -22,7 +23,7 @@ jsonschema==4.17.3
|
||||
looseversion==1.0.1
|
||||
mozilla-repo-urls==0.1.1
|
||||
mozilla-version==2.0.0
|
||||
packaging==21.3
|
||||
packaging==23.1
|
||||
pathspec==0.9.0
|
||||
pip==23.0.1
|
||||
pip-tools==5.5.0
|
||||
|
15
third_party/python/requirements.txt
vendored
15
third_party/python/requirements.txt
vendored
@ -66,9 +66,9 @@ certifi==2022.12.7 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
chardet==4.0.0 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
--hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \
|
||||
--hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5
|
||||
click==7.1.2 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
--hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a \
|
||||
--hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc
|
||||
click==8.1.6 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
--hash=sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd \
|
||||
--hash=sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5
|
||||
colorama==0.4.5 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
--hash=sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da \
|
||||
--hash=sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4
|
||||
@ -252,9 +252,9 @@ multidict==5.1.0 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
--hash=sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5 \
|
||||
--hash=sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281 \
|
||||
--hash=sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80
|
||||
packaging==21.3 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
--hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \
|
||||
--hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522
|
||||
packaging==23.1 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
--hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \
|
||||
--hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f
|
||||
pathspec==0.9.0 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
--hash=sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a \
|
||||
--hash=sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1
|
||||
@ -277,9 +277,6 @@ pyasn1==0.4.8 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
--hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba
|
||||
pylru==1.0.9 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
--hash=sha256:71376192671f0ad1690b2a7427d39a29b1df994c8469a9b46b03ed7e28c0172c
|
||||
pyparsing==2.4.7 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
--hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \
|
||||
--hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b
|
||||
pyrsistent==0.16.0 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
--hash=sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3
|
||||
python-dateutil==2.8.2 ; python_version >= "3.7" and python_version < "4.0" \
|
||||
|
@ -1,4 +1,4 @@
|
||||
black==21.11b1
|
||||
black==23.3.0
|
||||
typing-extensions==3.10.0.2
|
||||
dataclasses==0.6
|
||||
|
||||
|
@ -1,12 +1,35 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile
|
||||
# To update, run:
|
||||
# This file is autogenerated by pip-compile with Python 3.7
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --generate-hashes --output-file=tools/lint/python/black_requirements.txt tools/lint/python/black_requirements.in
|
||||
# pip-compile --config=pyproject.toml --generate-hashes --output-file=tools/lint/python/black_requirements.txt tools/lint/python/black_requirements.in
|
||||
#
|
||||
black==21.11b1 \
|
||||
--hash=sha256:802c6c30b637b28645b7fde282ed2569c0cd777dbe493a41b6a03c1d903f99ac \
|
||||
--hash=sha256:a042adbb18b3262faad5aff4e834ff186bb893f95ba3a8013f09de1e5569def2
|
||||
black==23.3.0 \
|
||||
--hash=sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5 \
|
||||
--hash=sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915 \
|
||||
--hash=sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326 \
|
||||
--hash=sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940 \
|
||||
--hash=sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b \
|
||||
--hash=sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30 \
|
||||
--hash=sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c \
|
||||
--hash=sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c \
|
||||
--hash=sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab \
|
||||
--hash=sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27 \
|
||||
--hash=sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2 \
|
||||
--hash=sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961 \
|
||||
--hash=sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9 \
|
||||
--hash=sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb \
|
||||
--hash=sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70 \
|
||||
--hash=sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331 \
|
||||
--hash=sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2 \
|
||||
--hash=sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266 \
|
||||
--hash=sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d \
|
||||
--hash=sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6 \
|
||||
--hash=sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b \
|
||||
--hash=sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925 \
|
||||
--hash=sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8 \
|
||||
--hash=sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4 \
|
||||
--hash=sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3
|
||||
# via -r tools/lint/python/black_requirements.in
|
||||
click==8.0.3 \
|
||||
--hash=sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3 \
|
||||
@ -16,10 +39,18 @@ dataclasses==0.6 \
|
||||
--hash=sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f \
|
||||
--hash=sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84
|
||||
# via -r tools/lint/python/black_requirements.in
|
||||
importlib-metadata==6.7.0 \
|
||||
--hash=sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4 \
|
||||
--hash=sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5
|
||||
# via click
|
||||
mypy-extensions==0.4.3 \
|
||||
--hash=sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d \
|
||||
--hash=sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8
|
||||
# via black
|
||||
packaging==23.1 \
|
||||
--hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \
|
||||
--hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f
|
||||
# via black
|
||||
pathspec==0.9.0 \
|
||||
--hash=sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a \
|
||||
--hash=sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1
|
||||
@ -28,86 +59,53 @@ platformdirs==2.4.0 \
|
||||
--hash=sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2 \
|
||||
--hash=sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d
|
||||
# via black
|
||||
regex==2021.11.10 \
|
||||
--hash=sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f \
|
||||
--hash=sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc \
|
||||
--hash=sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4 \
|
||||
--hash=sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4 \
|
||||
--hash=sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8 \
|
||||
--hash=sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f \
|
||||
--hash=sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a \
|
||||
--hash=sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef \
|
||||
--hash=sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f \
|
||||
--hash=sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc \
|
||||
--hash=sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50 \
|
||||
--hash=sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d \
|
||||
--hash=sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d \
|
||||
--hash=sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733 \
|
||||
--hash=sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36 \
|
||||
--hash=sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345 \
|
||||
--hash=sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0 \
|
||||
--hash=sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12 \
|
||||
--hash=sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646 \
|
||||
--hash=sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667 \
|
||||
--hash=sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244 \
|
||||
--hash=sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29 \
|
||||
--hash=sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec \
|
||||
--hash=sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf \
|
||||
--hash=sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4 \
|
||||
--hash=sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449 \
|
||||
--hash=sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a \
|
||||
--hash=sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d \
|
||||
--hash=sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb \
|
||||
--hash=sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e \
|
||||
--hash=sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83 \
|
||||
--hash=sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e \
|
||||
--hash=sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a \
|
||||
--hash=sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94 \
|
||||
--hash=sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc \
|
||||
--hash=sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e \
|
||||
--hash=sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965 \
|
||||
--hash=sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0 \
|
||||
--hash=sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36 \
|
||||
--hash=sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec \
|
||||
--hash=sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23 \
|
||||
--hash=sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7 \
|
||||
--hash=sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe \
|
||||
--hash=sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6 \
|
||||
--hash=sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b \
|
||||
--hash=sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb \
|
||||
--hash=sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b \
|
||||
--hash=sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30 \
|
||||
--hash=sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e
|
||||
# via black
|
||||
tomli==1.2.2 \
|
||||
--hash=sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee \
|
||||
--hash=sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade
|
||||
# via black
|
||||
typed-ast==1.5.4 \
|
||||
--hash=sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2 \
|
||||
--hash=sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1 \
|
||||
--hash=sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6 \
|
||||
--hash=sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62 \
|
||||
--hash=sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac \
|
||||
--hash=sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d \
|
||||
--hash=sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc \
|
||||
--hash=sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2 \
|
||||
--hash=sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97 \
|
||||
--hash=sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35 \
|
||||
--hash=sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6 \
|
||||
--hash=sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1 \
|
||||
--hash=sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4 \
|
||||
--hash=sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c \
|
||||
--hash=sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e \
|
||||
--hash=sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec \
|
||||
--hash=sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f \
|
||||
--hash=sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72 \
|
||||
--hash=sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47 \
|
||||
--hash=sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72 \
|
||||
--hash=sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe \
|
||||
--hash=sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6 \
|
||||
--hash=sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3 \
|
||||
--hash=sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66
|
||||
typed-ast==1.5.5 \
|
||||
--hash=sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10 \
|
||||
--hash=sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede \
|
||||
--hash=sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e \
|
||||
--hash=sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c \
|
||||
--hash=sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d \
|
||||
--hash=sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8 \
|
||||
--hash=sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e \
|
||||
--hash=sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5 \
|
||||
--hash=sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155 \
|
||||
--hash=sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4 \
|
||||
--hash=sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba \
|
||||
--hash=sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5 \
|
||||
--hash=sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a \
|
||||
--hash=sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b \
|
||||
--hash=sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311 \
|
||||
--hash=sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769 \
|
||||
--hash=sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686 \
|
||||
--hash=sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d \
|
||||
--hash=sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2 \
|
||||
--hash=sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814 \
|
||||
--hash=sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9 \
|
||||
--hash=sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b \
|
||||
--hash=sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b \
|
||||
--hash=sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4 \
|
||||
--hash=sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd \
|
||||
--hash=sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18 \
|
||||
--hash=sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa \
|
||||
--hash=sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6 \
|
||||
--hash=sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee \
|
||||
--hash=sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88 \
|
||||
--hash=sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4 \
|
||||
--hash=sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431 \
|
||||
--hash=sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04 \
|
||||
--hash=sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d \
|
||||
--hash=sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02 \
|
||||
--hash=sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8 \
|
||||
--hash=sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437 \
|
||||
--hash=sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274 \
|
||||
--hash=sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f \
|
||||
--hash=sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a \
|
||||
--hash=sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2
|
||||
# via black
|
||||
typing-extensions==3.10.0.2 \
|
||||
--hash=sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e \
|
||||
--hash=sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7 \
|
||||
@ -115,3 +113,8 @@ typing-extensions==3.10.0.2 \
|
||||
# via
|
||||
# -r tools/lint/python/black_requirements.in
|
||||
# black
|
||||
# importlib-metadata
|
||||
zipp==3.15.0 \
|
||||
--hash=sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b \
|
||||
--hash=sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556
|
||||
# via importlib-metadata
|
||||
|
Loading…
Reference in New Issue
Block a user